github.com/windmeup/goreleaser@v1.21.95/internal/pipe/aur/aur.go (about) 1 package aur 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "os" 9 "path/filepath" 10 "sort" 11 "strings" 12 "text/template" 13 14 "github.com/caarlos0/log" 15 "github.com/windmeup/goreleaser/internal/artifact" 16 "github.com/windmeup/goreleaser/internal/client" 17 "github.com/windmeup/goreleaser/internal/commitauthor" 18 "github.com/windmeup/goreleaser/internal/pipe" 19 "github.com/windmeup/goreleaser/internal/skips" 20 "github.com/windmeup/goreleaser/internal/tmpl" 21 "github.com/windmeup/goreleaser/pkg/config" 22 "github.com/windmeup/goreleaser/pkg/context" 23 ) 24 25 const ( 26 aurExtra = "AURConfig" 27 defaultCommitMsg = "Update to {{ .Tag }}" 28 ) 29 30 var ErrNoArchivesFound = errors.New("no linux archives found") 31 32 // Pipe for arch linux's AUR pkgbuild. 33 type Pipe struct{} 34 35 func (Pipe) String() string { return "arch user repositories" } 36 func (Pipe) ContinueOnError() bool { return true } 37 func (Pipe) Skip(ctx *context.Context) bool { 38 return skips.Any(ctx, skips.AUR) || len(ctx.Config.AURs) == 0 39 } 40 41 func (Pipe) Default(ctx *context.Context) error { 42 for i := range ctx.Config.AURs { 43 pkg := &ctx.Config.AURs[i] 44 45 pkg.CommitAuthor = commitauthor.Default(pkg.CommitAuthor) 46 if pkg.CommitMessageTemplate == "" { 47 pkg.CommitMessageTemplate = defaultCommitMsg 48 } 49 if pkg.Name == "" { 50 pkg.Name = ctx.Config.ProjectName 51 } 52 if !strings.HasSuffix(pkg.Name, "-bin") { 53 pkg.Name += "-bin" 54 } 55 if len(pkg.Conflicts) == 0 { 56 pkg.Conflicts = []string{ctx.Config.ProjectName} 57 } 58 if len(pkg.Provides) == 0 { 59 pkg.Provides = []string{ctx.Config.ProjectName} 60 } 61 if pkg.Rel == "" { 62 pkg.Rel = "1" 63 } 64 if pkg.Goamd64 == "" { 65 pkg.Goamd64 = "v1" 66 } 67 } 68 69 return nil 70 } 71 72 func (Pipe) Run(ctx *context.Context) error { 73 cli, err := client.NewReleaseClient(ctx) 74 if err != nil { 75 return err 76 } 77 78 return runAll(ctx, cli) 79 } 80 81 func runAll(ctx *context.Context, cli client.ReleaseURLTemplater) error { 82 for _, aur := range ctx.Config.AURs { 83 err := doRun(ctx, aur, cli) 84 if err != nil { 85 return err 86 } 87 } 88 return nil 89 } 90 91 func doRun(ctx *context.Context, aur config.AUR, cl client.ReleaseURLTemplater) error { 92 name, err := tmpl.New(ctx).Apply(aur.Name) 93 if err != nil { 94 return err 95 } 96 aur.Name = name 97 98 filters := []artifact.Filter{ 99 artifact.ByGoos("linux"), 100 artifact.Or( 101 artifact.And( 102 artifact.ByGoarch("amd64"), 103 artifact.ByGoamd64(aur.Goamd64), 104 ), 105 artifact.ByGoarch("arm64"), 106 artifact.ByGoarch("386"), 107 artifact.And( 108 artifact.ByGoarch("arm"), 109 artifact.Or( 110 artifact.ByGoarm("7"), 111 artifact.ByGoarm("6"), 112 ), 113 ), 114 ), 115 artifact.Or( 116 artifact.ByType(artifact.UploadableArchive), 117 artifact.ByType(artifact.UploadableBinary), 118 ), 119 } 120 if len(aur.IDs) > 0 { 121 filters = append(filters, artifact.ByIDs(aur.IDs...)) 122 } 123 124 archives := ctx.Artifacts.Filter(artifact.And(filters...)).List() 125 if len(archives) == 0 { 126 return ErrNoArchivesFound 127 } 128 129 pkg, err := tmpl.New(ctx).Apply(aur.Package) 130 if err != nil { 131 return err 132 } 133 if strings.TrimSpace(pkg) == "" { 134 art := archives[0] 135 switch art.Type { 136 case artifact.UploadableBinary: 137 name := art.Name 138 bin := artifact.ExtraOr(*art, artifact.ExtraBinary, art.Name) 139 pkg = fmt.Sprintf(`install -Dm755 "./%s "${pkgdir}/usr/bin/%s"`, name, bin) 140 case artifact.UploadableArchive: 141 for _, bin := range artifact.ExtraOr(*art, artifact.ExtraBinaries, []string{}) { 142 pkg = fmt.Sprintf(`install -Dm755 "./%s" "${pkgdir}/usr/bin/%[1]s"`, bin) 143 break 144 } 145 } 146 log.Warnf("guessing package to be %q", pkg) 147 } 148 aur.Package = pkg 149 150 for _, info := range []struct { 151 name, tpl, ext string 152 kind artifact.Type 153 }{ 154 { 155 name: "PKGBUILD", 156 tpl: aurTemplateData, 157 ext: ".pkgbuild", 158 kind: artifact.PkgBuild, 159 }, 160 { 161 name: ".SRCINFO", 162 tpl: srcInfoTemplate, 163 ext: ".srcinfo", 164 kind: artifact.SrcInfo, 165 }, 166 } { 167 pkgContent, err := buildPkgFile(ctx, aur, cl, archives, info.tpl) 168 if err != nil { 169 return err 170 } 171 172 path := filepath.Join(ctx.Config.Dist, "aur", aur.Name+info.ext) 173 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { 174 return fmt.Errorf("failed to write %s: %w", info.kind, err) 175 } 176 log.WithField("file", path).Info("writing") 177 if err := os.WriteFile(path, []byte(pkgContent), 0o644); err != nil { // nolint: gosec 178 return fmt.Errorf("failed to write %s: %w", info.kind, err) 179 } 180 181 ctx.Artifacts.Add(&artifact.Artifact{ 182 Name: info.name, 183 Path: path, 184 Type: info.kind, 185 Extra: map[string]interface{}{ 186 aurExtra: aur, 187 artifact.ExtraID: aur.Name, 188 }, 189 }) 190 } 191 192 return nil 193 } 194 195 func buildPkgFile(ctx *context.Context, pkg config.AUR, client client.ReleaseURLTemplater, artifacts []*artifact.Artifact, tpl string) (string, error) { 196 data, err := dataFor(ctx, pkg, client, artifacts) 197 if err != nil { 198 return "", err 199 } 200 return applyTemplate(ctx, tpl, data) 201 } 202 203 func fixLines(s string) string { 204 lines := strings.Split(s, "\n") 205 var result []string 206 for _, line := range lines { 207 l := strings.TrimSpace(line) 208 if l == "" { 209 result = append(result, "") 210 continue 211 } 212 result = append(result, " "+l) 213 } 214 return strings.Join(result, "\n") 215 } 216 217 func applyTemplate(ctx *context.Context, tpl string, data templateData) (string, error) { 218 t := template.Must( 219 template.New(data.Name). 220 Funcs(template.FuncMap{ 221 "fixLines": fixLines, 222 "pkgArray": toPkgBuildArray, 223 }). 224 Parse(tpl), 225 ) 226 227 var out bytes.Buffer 228 if err := t.Execute(&out, data); err != nil { 229 return "", err 230 } 231 232 content, err := tmpl.New(ctx).Apply(out.String()) 233 if err != nil { 234 return "", err 235 } 236 out.Reset() 237 238 // Sanitize the template output and get rid of trailing whitespace. 239 var ( 240 r = strings.NewReader(content) 241 s = bufio.NewScanner(r) 242 ) 243 for s.Scan() { 244 l := strings.TrimRight(s.Text(), " ") 245 _, _ = out.WriteString(l) 246 _ = out.WriteByte('\n') 247 } 248 if err := s.Err(); err != nil { 249 return "", err 250 } 251 252 return out.String(), nil 253 } 254 255 func toPkgBuildArray(ss []string) string { 256 result := make([]string, 0, len(ss)) 257 for _, s := range ss { 258 result = append(result, fmt.Sprintf("'%s'", s)) 259 } 260 return strings.Join(result, " ") 261 } 262 263 func toPkgBuildArch(arch string) string { 264 switch arch { 265 case "amd64": 266 return "x86_64" 267 case "386": 268 return "i686" 269 case "arm64": 270 return "aarch64" 271 case "arm6": 272 return "armv6h" 273 case "arm7": 274 return "armv7h" 275 default: 276 return "invalid" // should never get here 277 } 278 } 279 280 func dataFor(ctx *context.Context, cfg config.AUR, cl client.ReleaseURLTemplater, artifacts []*artifact.Artifact) (templateData, error) { 281 result := templateData{ 282 Name: cfg.Name, 283 Desc: cfg.Description, 284 Homepage: cfg.Homepage, 285 Version: fmt.Sprintf("%d.%d.%d", ctx.Semver.Major, ctx.Semver.Minor, ctx.Semver.Patch), 286 License: cfg.License, 287 Rel: cfg.Rel, 288 Maintainers: cfg.Maintainers, 289 Contributors: cfg.Contributors, 290 Provides: cfg.Provides, 291 Conflicts: cfg.Conflicts, 292 Backup: cfg.Backup, 293 Depends: cfg.Depends, 294 OptDepends: cfg.OptDepends, 295 Package: cfg.Package, 296 } 297 298 for _, art := range artifacts { 299 sum, err := art.Checksum("sha256") 300 if err != nil { 301 return result, err 302 } 303 304 if cfg.URLTemplate == "" { 305 url, err := cl.ReleaseURLTemplate(ctx) 306 if err != nil { 307 return result, err 308 } 309 cfg.URLTemplate = url 310 } 311 url, err := tmpl.New(ctx).WithArtifact(art).Apply(cfg.URLTemplate) 312 if err != nil { 313 return result, err 314 } 315 316 releasePackage := releasePackage{ 317 DownloadURL: url, 318 SHA256: sum, 319 Arch: toPkgBuildArch(art.Goarch + art.Goarm), 320 Format: artifact.ExtraOr(*art, artifact.ExtraFormat, ""), 321 } 322 result.ReleasePackages = append(result.ReleasePackages, releasePackage) 323 result.Arches = append(result.Arches, releasePackage.Arch) 324 } 325 326 sort.Strings(result.Arches) 327 sort.Slice(result.ReleasePackages, func(i, j int) bool { 328 return result.ReleasePackages[i].Arch < result.ReleasePackages[j].Arch 329 }) 330 return result, nil 331 } 332 333 // Publish the PKGBUILD and .SRCINFO files to the AUR repository. 334 func (Pipe) Publish(ctx *context.Context) error { 335 skips := pipe.SkipMemento{} 336 for _, pkgs := range ctx.Artifacts.Filter( 337 artifact.Or( 338 artifact.ByType(artifact.PkgBuild), 339 artifact.ByType(artifact.SrcInfo), 340 ), 341 ).GroupByID() { 342 err := doPublish(ctx, pkgs) 343 if err != nil && pipe.IsSkip(err) { 344 skips.Remember(err) 345 continue 346 } 347 if err != nil { 348 return err 349 } 350 } 351 return skips.Evaluate() 352 } 353 354 func doPublish(ctx *context.Context, pkgs []*artifact.Artifact) error { 355 cfg, err := artifact.Extra[config.AUR](*pkgs[0], aurExtra) 356 if err != nil { 357 return err 358 } 359 360 if strings.TrimSpace(cfg.SkipUpload) == "true" { 361 return pipe.Skip("aur.skip_upload is set") 362 } 363 364 if strings.TrimSpace(cfg.SkipUpload) == "auto" && ctx.Semver.Prerelease != "" { 365 return pipe.Skip("prerelease detected with 'auto' upload, skipping aur publish") 366 } 367 368 author, err := commitauthor.Get(ctx, cfg.CommitAuthor) 369 if err != nil { 370 return err 371 } 372 373 msg, err := tmpl.New(ctx).Apply(cfg.CommitMessageTemplate) 374 if err != nil { 375 return err 376 } 377 378 cli := client.NewGitUploadClient("master") 379 repo := client.RepoFromRef(config.RepoRef{ 380 Git: config.GitRepoRef{ 381 PrivateKey: cfg.PrivateKey, 382 URL: cfg.GitURL, 383 SSHCommand: cfg.GitSSHCommand, 384 }, 385 Name: cfg.Name, 386 }) 387 388 files := make([]client.RepoFile, 0, len(pkgs)) 389 for _, pkg := range pkgs { 390 content, err := os.ReadFile(pkg.Path) 391 if err != nil { 392 return err 393 } 394 files = append(files, client.RepoFile{ 395 Path: pkg.Name, 396 Content: content, 397 }) 398 } 399 return cli.CreateFiles(ctx, author, repo, msg, files) 400 }