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