github.com/szyn/goreleaser@v0.76.1-0.20180517112710-333da09a1297/pipeline/snapcraft/snapcraft.go (about) 1 // Package snapcraft implements the Pipe interface providing Snapcraft bindings. 2 package snapcraft 3 4 import ( 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path/filepath" 11 12 "github.com/apex/log" 13 "golang.org/x/sync/errgroup" 14 yaml "gopkg.in/yaml.v2" 15 16 "github.com/goreleaser/goreleaser/context" 17 "github.com/goreleaser/goreleaser/internal/artifact" 18 "github.com/goreleaser/goreleaser/internal/filenametemplate" 19 "github.com/goreleaser/goreleaser/internal/linux" 20 "github.com/goreleaser/goreleaser/pipeline" 21 ) 22 23 // ErrNoSnapcraft is shown when snapcraft cannot be found in $PATH 24 var ErrNoSnapcraft = errors.New("snapcraft not present in $PATH") 25 26 // ErrNoDescription is shown when no description provided 27 var ErrNoDescription = errors.New("no description provided for snapcraft") 28 29 // ErrNoSummary is shown when no summary provided 30 var ErrNoSummary = errors.New("no summary provided for snapcraft") 31 32 // Metadata to generate the snap package 33 type Metadata struct { 34 Name string 35 Version string 36 Summary string 37 Description string 38 Grade string `yaml:",omitempty"` 39 Confinement string `yaml:",omitempty"` 40 Architectures []string 41 Apps map[string]AppMetadata 42 } 43 44 // AppMetadata for the binaries that will be in the snap package 45 type AppMetadata struct { 46 Command string 47 Plugs []string `yaml:",omitempty"` 48 Daemon string `yaml:",omitempty"` 49 } 50 51 const defaultNameTemplate = "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 52 53 // Pipe for snapcraft packaging 54 type Pipe struct{} 55 56 func (Pipe) String() string { 57 return "creating Linux packages with snapcraft" 58 } 59 60 // Default sets the pipe defaults 61 func (Pipe) Default(ctx *context.Context) error { 62 var snap = &ctx.Config.Snapcraft 63 if snap.NameTemplate == "" { 64 snap.NameTemplate = defaultNameTemplate 65 } 66 return nil 67 } 68 69 // Run the pipe 70 func (Pipe) Run(ctx *context.Context) error { 71 if ctx.Config.Snapcraft.Summary == "" && ctx.Config.Snapcraft.Description == "" { 72 return pipeline.Skip("no summary nor description were provided") 73 } 74 if ctx.Config.Snapcraft.Summary == "" { 75 return ErrNoSummary 76 } 77 if ctx.Config.Snapcraft.Description == "" { 78 return ErrNoDescription 79 } 80 _, err := exec.LookPath("snapcraft") 81 if err != nil { 82 return ErrNoSnapcraft 83 } 84 85 var g errgroup.Group 86 for platform, binaries := range ctx.Artifacts.Filter( 87 artifact.And( 88 artifact.ByGoos("linux"), 89 artifact.ByType(artifact.Binary), 90 ), 91 ).GroupByPlatform() { 92 arch := linux.Arch(platform) 93 binaries := binaries 94 g.Go(func() error { 95 return create(ctx, arch, binaries) 96 }) 97 } 98 return g.Wait() 99 } 100 101 func create(ctx *context.Context, arch string, binaries []artifact.Artifact) error { 102 var log = log.WithField("arch", arch) 103 folder, err := filenametemplate.Apply( 104 ctx.Config.Snapcraft.NameTemplate, 105 filenametemplate.NewFields(ctx, ctx.Config.Snapcraft.Replacements, binaries...), 106 ) 107 if err != nil { 108 return err 109 } 110 // prime is the directory that then will be compressed to make the .snap package. 111 var folderDir = filepath.Join(ctx.Config.Dist, folder) 112 var primeDir = filepath.Join(folderDir, "prime") 113 var metaDir = filepath.Join(primeDir, "meta") 114 // #nosec 115 if err = os.MkdirAll(metaDir, 0755); err != nil { 116 return err 117 } 118 119 var file = filepath.Join(primeDir, "meta", "snap.yaml") 120 log.WithField("file", file).Debug("creating snap metadata") 121 122 var metadata = &Metadata{ 123 Version: ctx.Version, 124 Summary: ctx.Config.Snapcraft.Summary, 125 Description: ctx.Config.Snapcraft.Description, 126 Grade: ctx.Config.Snapcraft.Grade, 127 Confinement: ctx.Config.Snapcraft.Confinement, 128 Architectures: []string{arch}, 129 Apps: make(map[string]AppMetadata), 130 } 131 132 metadata.Name = ctx.Config.ProjectName 133 if ctx.Config.Snapcraft.Name != "" { 134 metadata.Name = ctx.Config.Snapcraft.Name 135 } 136 137 for _, binary := range binaries { 138 log.WithField("path", binary.Path). 139 WithField("name", binary.Name). 140 Debug("passed binary to snapcraft") 141 appMetadata := AppMetadata{ 142 Command: binary.Name, 143 } 144 if configAppMetadata, ok := ctx.Config.Snapcraft.Apps[binary.Name]; ok { 145 appMetadata.Plugs = configAppMetadata.Plugs 146 appMetadata.Daemon = configAppMetadata.Daemon 147 } 148 metadata.Apps[binary.Name] = appMetadata 149 150 destBinaryPath := filepath.Join(primeDir, filepath.Base(binary.Path)) 151 if err = os.Link(binary.Path, destBinaryPath); err != nil { 152 return err 153 } 154 } 155 out, err := yaml.Marshal(metadata) 156 if err != nil { 157 return err 158 } 159 160 if err = ioutil.WriteFile(file, out, 0644); err != nil { 161 return err 162 } 163 164 var snap = filepath.Join(ctx.Config.Dist, folder+".snap") 165 /* #nosec */ 166 var cmd = exec.CommandContext(ctx, "snapcraft", "pack", primeDir, "--output", snap) 167 if out, err = cmd.CombinedOutput(); err != nil { 168 return fmt.Errorf("failed to generate snap package: %s", string(out)) 169 } 170 ctx.Artifacts.Add(artifact.Artifact{ 171 Type: artifact.LinuxPackage, 172 Name: folder + ".snap", 173 Path: snap, 174 Goos: binaries[0].Goos, 175 Goarch: binaries[0].Goarch, 176 Goarm: binaries[0].Goarm, 177 }) 178 return nil 179 }