github.phpd.cn/goreleaser/goreleaser@v0.92.0/internal/pipe/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 "strings" 12 13 "github.com/apex/log" 14 "github.com/goreleaser/goreleaser/internal/artifact" 15 "github.com/goreleaser/goreleaser/internal/linux" 16 "github.com/goreleaser/goreleaser/internal/pipe" 17 "github.com/goreleaser/goreleaser/internal/semerrgroup" 18 "github.com/goreleaser/goreleaser/internal/tmpl" 19 "github.com/goreleaser/goreleaser/pkg/context" 20 yaml "gopkg.in/yaml.v2" 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 "Snapcraft Packages" 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 pipe.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 = semerrgroup.New(ctx.Parallelism) 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 if arch == "armel" { 94 log.WithField("arch", arch).Warn("ignored unsupported arch") 95 continue 96 } 97 binaries := binaries 98 g.Go(func() error { 99 return create(ctx, arch, binaries) 100 }) 101 } 102 return g.Wait() 103 } 104 105 // Publish packages 106 func (Pipe) Publish(ctx *context.Context) error { 107 snaps := ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableSnapcraft)).List() 108 var g = semerrgroup.New(ctx.Parallelism) 109 for _, snap := range snaps { 110 snap := snap 111 g.Go(func() error { 112 return push(ctx, snap) 113 }) 114 } 115 return g.Wait() 116 } 117 118 func create(ctx *context.Context, arch string, binaries []artifact.Artifact) error { 119 var log = log.WithField("arch", arch) 120 folder, err := tmpl.New(ctx). 121 WithArtifact(binaries[0], ctx.Config.Snapcraft.Replacements). 122 Apply(ctx.Config.Snapcraft.NameTemplate) 123 if err != nil { 124 return err 125 } 126 // prime is the directory that then will be compressed to make the .snap package. 127 var folderDir = filepath.Join(ctx.Config.Dist, folder) 128 var primeDir = filepath.Join(folderDir, "prime") 129 var metaDir = filepath.Join(primeDir, "meta") 130 // #nosec 131 if err = os.MkdirAll(metaDir, 0755); err != nil { 132 return err 133 } 134 135 var file = filepath.Join(primeDir, "meta", "snap.yaml") 136 log.WithField("file", file).Debug("creating snap metadata") 137 138 var metadata = &Metadata{ 139 Version: ctx.Version, 140 Summary: ctx.Config.Snapcraft.Summary, 141 Description: ctx.Config.Snapcraft.Description, 142 Grade: ctx.Config.Snapcraft.Grade, 143 Confinement: ctx.Config.Snapcraft.Confinement, 144 Architectures: []string{arch}, 145 Apps: make(map[string]AppMetadata), 146 } 147 148 metadata.Name = ctx.Config.ProjectName 149 if ctx.Config.Snapcraft.Name != "" { 150 metadata.Name = ctx.Config.Snapcraft.Name 151 } 152 153 for _, binary := range binaries { 154 log.WithField("path", binary.Path). 155 WithField("name", binary.Name). 156 Debug("passed binary to snapcraft") 157 appMetadata := AppMetadata{ 158 Command: binary.Name, 159 } 160 if configAppMetadata, ok := ctx.Config.Snapcraft.Apps[binary.Name]; ok { 161 appMetadata.Plugs = configAppMetadata.Plugs 162 appMetadata.Daemon = configAppMetadata.Daemon 163 appMetadata.Command = strings.Join([]string{ 164 appMetadata.Command, 165 configAppMetadata.Args, 166 }, " ") 167 } 168 metadata.Apps[binary.Name] = appMetadata 169 170 destBinaryPath := filepath.Join(primeDir, filepath.Base(binary.Path)) 171 if err = os.Link(binary.Path, destBinaryPath); err != nil { 172 return err 173 } 174 } 175 176 if _, ok := metadata.Apps[metadata.Name]; !ok { 177 metadata.Apps[metadata.Name] = metadata.Apps[binaries[0].Name] 178 } 179 180 out, err := yaml.Marshal(metadata) 181 if err != nil { 182 return err 183 } 184 185 if err = ioutil.WriteFile(file, out, 0644); err != nil { 186 return err 187 } 188 189 var snap = filepath.Join(ctx.Config.Dist, folder+".snap") 190 log.WithField("snap", snap).Info("creating") 191 /* #nosec */ 192 var cmd = exec.CommandContext(ctx, "snapcraft", "pack", primeDir, "--output", snap) 193 if out, err = cmd.CombinedOutput(); err != nil { 194 return fmt.Errorf("failed to generate snap package: %s", string(out)) 195 } 196 if !ctx.Config.Snapcraft.Publish { 197 return nil 198 } 199 ctx.Artifacts.Add(artifact.Artifact{ 200 Type: artifact.PublishableSnapcraft, 201 Name: folder + ".snap", 202 Path: snap, 203 Goos: binaries[0].Goos, 204 Goarch: binaries[0].Goarch, 205 Goarm: binaries[0].Goarm, 206 }) 207 return nil 208 } 209 210 func push(ctx *context.Context, snap artifact.Artifact) error { 211 log.WithField("snap", snap.Name).Info("pushing snap") 212 // TODO: customize --release based on snap.Grade? 213 /* #nosec */ 214 var cmd = exec.CommandContext(ctx, "snapcraft", "push", "--release=stable", snap.Path) 215 if out, err := cmd.CombinedOutput(); err != nil { 216 return fmt.Errorf("failed to push %s package: %s", snap.Path, string(out)) 217 } 218 snap.Type = artifact.Snapcraft 219 ctx.Artifacts.Add(snap) 220 return nil 221 }