github.com/triarius/goreleaser@v1.12.5/internal/pipe/archive/archive.go (about) 1 // Package archive implements the pipe interface with the intent of 2 // archiving and compressing the binaries, readme, and other artifacts. It 3 // also provides an Archive interface which represents an archiving format. 4 package archive 5 6 import ( 7 "errors" 8 "fmt" 9 "io/fs" 10 "os" 11 "path/filepath" 12 "strings" 13 "sync" 14 15 "github.com/caarlos0/log" 16 "github.com/triarius/goreleaser/internal/archivefiles" 17 "github.com/triarius/goreleaser/internal/artifact" 18 "github.com/triarius/goreleaser/internal/ids" 19 "github.com/triarius/goreleaser/internal/semerrgroup" 20 "github.com/triarius/goreleaser/internal/tmpl" 21 "github.com/triarius/goreleaser/pkg/archive" 22 "github.com/triarius/goreleaser/pkg/config" 23 "github.com/triarius/goreleaser/pkg/context" 24 ) 25 26 const ( 27 defaultNameTemplateSuffix = `{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}` 28 defaultNameTemplate = "{{ .ProjectName }}_" + defaultNameTemplateSuffix 29 defaultBinaryNameTemplate = "{{ .Binary }}_" + defaultNameTemplateSuffix 30 ) 31 32 // ErrArchiveDifferentBinaryCount happens when an archive uses several builds which have different goos/goarch/etc sets, 33 // causing the archives for some platforms to have more binaries than others. 34 // GoReleaser breaks in these cases as it will only cause confusion to other users. 35 var ErrArchiveDifferentBinaryCount = errors.New("archive has different count of binaries for each platform, which may cause your users confusion.\nLearn more at https://goreleaser.com/errors/multiple-binaries-archive\n") // nolint:revive 36 37 // nolint: gochecknoglobals 38 var lock sync.Mutex 39 40 // Pipe for archive. 41 type Pipe struct{} 42 43 func (Pipe) String() string { 44 return "archives" 45 } 46 47 // Default sets the pipe defaults. 48 func (Pipe) Default(ctx *context.Context) error { 49 ids := ids.New("archives") 50 if len(ctx.Config.Archives) == 0 { 51 ctx.Config.Archives = append(ctx.Config.Archives, config.Archive{}) 52 } 53 for i := range ctx.Config.Archives { 54 archive := &ctx.Config.Archives[i] 55 if archive.Format == "" { 56 archive.Format = "tar.gz" 57 } 58 if archive.ID == "" { 59 archive.ID = "default" 60 } 61 if len(archive.Files) == 0 { 62 archive.Files = []config.File{ 63 {Source: "license*"}, 64 {Source: "LICENSE*"}, 65 {Source: "readme*"}, 66 {Source: "README*"}, 67 {Source: "changelog*"}, 68 {Source: "CHANGELOG*"}, 69 } 70 } 71 if archive.NameTemplate == "" { 72 archive.NameTemplate = defaultNameTemplate 73 if archive.Format == "binary" { 74 archive.NameTemplate = defaultBinaryNameTemplate 75 } 76 } 77 ids.Inc(archive.ID) 78 } 79 return ids.Validate() 80 } 81 82 // Run the pipe. 83 func (Pipe) Run(ctx *context.Context) error { 84 g := semerrgroup.New(ctx.Parallelism) 85 for i, archive := range ctx.Config.Archives { 86 archive := archive 87 if archive.Meta { 88 return createMeta(ctx, archive) 89 } 90 91 filter := []artifact.Filter{artifact.Or( 92 artifact.ByType(artifact.Binary), 93 artifact.ByType(artifact.UniversalBinary), 94 )} 95 if len(archive.Builds) > 0 { 96 filter = append(filter, artifact.ByIDs(archive.Builds...)) 97 } 98 artifacts := ctx.Artifacts.Filter(artifact.And(filter...)).GroupByPlatform() 99 if err := checkArtifacts(artifacts); err != nil && archive.Format != "binary" && !archive.AllowDifferentBinaryCount { 100 return fmt.Errorf("invalid archive: %d: %w", i, ErrArchiveDifferentBinaryCount) 101 } 102 for group, artifacts := range artifacts { 103 log.Debugf("group %s has %d binaries", group, len(artifacts)) 104 artifacts := artifacts 105 if packageFormat(archive, artifacts[0].Goos) == "binary" { 106 g.Go(func() error { 107 return skip(ctx, archive, artifacts) 108 }) 109 continue 110 } 111 g.Go(func() error { 112 if err := create(ctx, archive, artifacts); err != nil { 113 return err 114 } 115 return nil 116 }) 117 } 118 } 119 return g.Wait() 120 } 121 122 func checkArtifacts(artifacts map[string][]*artifact.Artifact) error { 123 lens := map[int]bool{} 124 for _, v := range artifacts { 125 lens[len(v)] = true 126 } 127 if len(lens) <= 1 { 128 return nil 129 } 130 return ErrArchiveDifferentBinaryCount 131 } 132 133 func createMeta(ctx *context.Context, arch config.Archive) error { 134 return doCreate(ctx, arch, nil, arch.Format, tmpl.New(ctx)) 135 } 136 137 func create(ctx *context.Context, arch config.Archive, binaries []*artifact.Artifact) error { 138 template := tmpl.New(ctx).WithArtifact(binaries[0], arch.Replacements) 139 format := packageFormat(arch, binaries[0].Goos) 140 return doCreate(ctx, arch, binaries, format, template) 141 } 142 143 func doCreate(ctx *context.Context, arch config.Archive, binaries []*artifact.Artifact, format string, template *tmpl.Template) error { 144 folder, err := template.Apply(arch.NameTemplate) 145 if err != nil { 146 return err 147 } 148 archivePath := filepath.Join(ctx.Config.Dist, folder+"."+format) 149 lock.Lock() 150 if err := os.MkdirAll(filepath.Dir(archivePath), 0o755|os.ModeDir); err != nil { 151 lock.Unlock() 152 return err 153 } 154 if _, err = os.Stat(archivePath); !errors.Is(err, fs.ErrNotExist) { 155 lock.Unlock() 156 return fmt.Errorf("archive named %s already exists. Check your archive name template", archivePath) 157 } 158 archiveFile, err := os.Create(archivePath) 159 if err != nil { 160 lock.Unlock() 161 return fmt.Errorf("failed to create directory %s: %w", archivePath, err) 162 } 163 lock.Unlock() 164 defer archiveFile.Close() 165 166 log := log.WithField("archive", archivePath) 167 log.Info("creating") 168 169 wrap, err := template.Apply(wrapFolder(arch)) 170 if err != nil { 171 return err 172 } 173 a, err := archive.New(archiveFile, format) 174 if err != nil { 175 return err 176 } 177 a = NewEnhancedArchive(a, wrap) 178 defer a.Close() 179 180 files, err := archivefiles.Eval(template, arch.Files) 181 if err != nil { 182 return fmt.Errorf("failed to find files to archive: %w", err) 183 } 184 if arch.Meta && len(files) == 0 { 185 return fmt.Errorf("no files found") 186 } 187 for _, f := range files { 188 if err = a.Add(f); err != nil { 189 return fmt.Errorf("failed to add: '%s' -> '%s': %w", f.Source, f.Destination, err) 190 } 191 } 192 bins := []string{} 193 for _, binary := range binaries { 194 dst := binary.Name 195 if arch.StripParentBinaryFolder { 196 dst = filepath.Base(dst) 197 } 198 if err := a.Add(config.File{ 199 Source: binary.Path, 200 Destination: dst, 201 }); err != nil { 202 return fmt.Errorf("failed to add: '%s' -> '%s': %w", binary.Path, dst, err) 203 } 204 bins = append(bins, binary.Name) 205 } 206 art := &artifact.Artifact{ 207 Type: artifact.UploadableArchive, 208 Name: folder + "." + format, 209 Path: archivePath, 210 Extra: map[string]interface{}{ 211 artifact.ExtraBuilds: binaries, 212 artifact.ExtraID: arch.ID, 213 artifact.ExtraFormat: arch.Format, 214 artifact.ExtraWrappedIn: wrap, 215 artifact.ExtraBinaries: bins, 216 }, 217 } 218 if len(binaries) > 0 { 219 art.Goos = binaries[0].Goos 220 art.Goarch = binaries[0].Goarch 221 art.Goarm = binaries[0].Goarm 222 art.Gomips = binaries[0].Gomips 223 art.Goamd64 = binaries[0].Goamd64 224 art.Extra[artifact.ExtraReplaces] = binaries[0].Extra[artifact.ExtraReplaces] 225 } 226 227 ctx.Artifacts.Add(art) 228 return nil 229 } 230 231 func wrapFolder(a config.Archive) string { 232 switch a.WrapInDirectory { 233 case "true": 234 return a.NameTemplate 235 case "false": 236 return "" 237 default: 238 return a.WrapInDirectory 239 } 240 } 241 242 func skip(ctx *context.Context, archive config.Archive, binaries []*artifact.Artifact) error { 243 for _, binary := range binaries { 244 name, err := tmpl.New(ctx). 245 WithArtifact(binary, archive.Replacements). 246 Apply(archive.NameTemplate) 247 if err != nil { 248 return err 249 } 250 finalName := name + artifact.ExtraOr(*binary, artifact.ExtraExt, "") 251 log.WithField("binary", binary.Name). 252 WithField("name", finalName). 253 Info("naming binary") 254 255 finalPath := filepath.Join(ctx.Config.Dist, finalName) 256 257 if err = os.Link(binary.Path, finalPath); err != nil { 258 return err 259 } 260 261 ctx.Artifacts.Add(&artifact.Artifact{ 262 Type: artifact.UploadableBinary, 263 Name: finalName, 264 Path: binary.Path, 265 Goos: binary.Goos, 266 Goarch: binary.Goarch, 267 Goarm: binary.Goarm, 268 Gomips: binary.Gomips, 269 Goamd64: binary.Goamd64, 270 Extra: map[string]interface{}{ 271 artifact.ExtraBuilds: []*artifact.Artifact{binary}, 272 artifact.ExtraID: archive.ID, 273 artifact.ExtraFormat: archive.Format, 274 artifact.ExtraBinary: binary.Name, 275 artifact.ExtraReplaces: binaries[0].Extra[artifact.ExtraReplaces], 276 }, 277 }) 278 } 279 return nil 280 } 281 282 func packageFormat(archive config.Archive, platform string) string { 283 for _, override := range archive.FormatOverrides { 284 if strings.HasPrefix(platform, override.Goos) { 285 return override.Format 286 } 287 } 288 return archive.Format 289 } 290 291 // NewEnhancedArchive enhances a pre-existing archive.Archive instance 292 // with this pipe specifics. 293 func NewEnhancedArchive(a archive.Archive, wrap string) archive.Archive { 294 return EnhancedArchive{ 295 a: a, 296 wrap: wrap, 297 files: map[string]string{}, 298 } 299 } 300 301 // EnhancedArchive is an archive.Archive implementation which decorates an 302 // archive.Archive adding wrap directory support, logging and windows 303 // backslash fixes. 304 type EnhancedArchive struct { 305 a archive.Archive 306 wrap string 307 files map[string]string 308 } 309 310 // Add adds a file. 311 func (d EnhancedArchive) Add(f config.File) error { 312 name := strings.ReplaceAll(filepath.Join(d.wrap, f.Destination), "\\", "/") 313 log.Debugf("adding file: %s as %s", f.Source, name) 314 if _, ok := d.files[f.Destination]; ok { 315 return fmt.Errorf("file %s already exists in the archive", f.Destination) 316 } 317 d.files[f.Destination] = name 318 ff := config.File{ 319 Source: f.Source, 320 Destination: name, 321 Info: f.Info, 322 } 323 return d.a.Add(ff) 324 } 325 326 // Close closes the underlying archive. 327 func (d EnhancedArchive) Close() error { 328 return d.a.Close() 329 }