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