github.com/goreleaser/goreleaser@v1.25.1/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/goreleaser/goreleaser/internal/archivefiles" 17 "github.com/goreleaser/goreleaser/internal/artifact" 18 "github.com/goreleaser/goreleaser/internal/deprecate" 19 "github.com/goreleaser/goreleaser/internal/ids" 20 "github.com/goreleaser/goreleaser/internal/semerrgroup" 21 "github.com/goreleaser/goreleaser/internal/tmpl" 22 "github.com/goreleaser/goreleaser/pkg/archive" 23 "github.com/goreleaser/goreleaser/pkg/config" 24 "github.com/goreleaser/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.StripParentBinaryFolder { 63 archive.StripBinaryDirectory = true 64 deprecate.Notice(ctx, "archives.strip_parent_binary_folder") 65 } 66 if archive.RLCP != "" && archive.Format != "binary" && len(archive.Files) > 0 { 67 deprecate.Notice(ctx, "archives.rlcp") 68 } 69 if len(archive.Files) == 0 { 70 archive.Files = []config.File{ 71 {Source: "license*", Default: true}, 72 {Source: "LICENSE*", Default: true}, 73 {Source: "readme*", Default: true}, 74 {Source: "README*", Default: true}, 75 {Source: "changelog*", Default: true}, 76 {Source: "CHANGELOG*", Default: true}, 77 } 78 } 79 if archive.NameTemplate == "" { 80 archive.NameTemplate = defaultNameTemplate 81 if archive.Format == "binary" { 82 archive.NameTemplate = defaultBinaryNameTemplate 83 } 84 } 85 ids.Inc(archive.ID) 86 } 87 return ids.Validate() 88 } 89 90 // Run the pipe. 91 func (Pipe) Run(ctx *context.Context) error { 92 g := semerrgroup.New(ctx.Parallelism) 93 for i, archive := range ctx.Config.Archives { 94 archive := archive 95 if archive.Meta { 96 g.Go(func() error { 97 return createMeta(ctx, archive) 98 }) 99 continue 100 } 101 102 filter := []artifact.Filter{artifact.Or( 103 artifact.ByType(artifact.Binary), 104 artifact.ByType(artifact.UniversalBinary), 105 artifact.ByType(artifact.Header), 106 artifact.ByType(artifact.CArchive), 107 artifact.ByType(artifact.CShared), 108 )} 109 if len(archive.Builds) > 0 { 110 filter = append(filter, artifact.ByIDs(archive.Builds...)) 111 } 112 artifacts := ctx.Artifacts.Filter(artifact.And(filter...)).GroupByPlatform() 113 if err := checkArtifacts(artifacts); err != nil && archive.Format != "binary" && !archive.AllowDifferentBinaryCount { 114 return fmt.Errorf("invalid archive: %d: %w", i, ErrArchiveDifferentBinaryCount) 115 } 116 for group, artifacts := range artifacts { 117 log.Debugf("group %s has %d binaries", group, len(artifacts)) 118 artifacts := artifacts 119 format := packageFormat(archive, artifacts[0].Goos) 120 switch format { 121 case "none": 122 // do nothing 123 log.WithField("goos", artifacts[0].Goos).Info("ignored due to format override to 'none'") 124 case "binary": 125 g.Go(func() error { 126 return skip(ctx, archive, artifacts) 127 }) 128 default: 129 g.Go(func() error { 130 return create(ctx, archive, artifacts, format) 131 }) 132 } 133 } 134 } 135 return g.Wait() 136 } 137 138 func checkArtifacts(artifacts map[string][]*artifact.Artifact) error { 139 lens := map[int]bool{} 140 for _, v := range artifacts { 141 lens[len(v)] = true 142 } 143 if len(lens) <= 1 { 144 return nil 145 } 146 return ErrArchiveDifferentBinaryCount 147 } 148 149 func createMeta(ctx *context.Context, arch config.Archive) error { 150 return create(ctx, arch, nil, arch.Format) 151 } 152 153 func create(ctx *context.Context, arch config.Archive, binaries []*artifact.Artifact, format string) error { 154 template := tmpl.New(ctx) 155 if len(binaries) > 0 { 156 template = template.WithArtifact(binaries[0]) 157 } 158 folder, err := template.Apply(arch.NameTemplate) 159 if err != nil { 160 return err 161 } 162 archivePath := filepath.Join(ctx.Config.Dist, folder+"."+format) 163 lock.Lock() 164 if err := os.MkdirAll(filepath.Dir(archivePath), 0o755|os.ModeDir); err != nil { 165 lock.Unlock() 166 return err 167 } 168 if _, err = os.Stat(archivePath); !errors.Is(err, fs.ErrNotExist) { 169 lock.Unlock() 170 return fmt.Errorf("archive named %s already exists. Check your archive name template", archivePath) 171 } 172 archiveFile, err := os.Create(archivePath) 173 if err != nil { 174 lock.Unlock() 175 return fmt.Errorf("failed to create directory %s: %w", archivePath, err) 176 } 177 lock.Unlock() 178 defer archiveFile.Close() 179 180 log := log.WithField("archive", archivePath) 181 log.Info("creating") 182 183 wrap, err := template.Apply(wrapFolder(arch)) 184 if err != nil { 185 return err 186 } 187 a, err := archive.New(archiveFile, format) 188 if err != nil { 189 return err 190 } 191 a = NewEnhancedArchive(a, wrap) 192 defer a.Close() 193 194 files, err := archivefiles.Eval(template, arch.Files) 195 if err != nil { 196 return fmt.Errorf("failed to find files to archive: %w", err) 197 } 198 if arch.Meta && len(files) == 0 { 199 return fmt.Errorf("no files found") 200 } 201 for _, f := range files { 202 if err = a.Add(f); err != nil { 203 return fmt.Errorf("failed to add: '%s' -> '%s': %w", f.Source, f.Destination, err) 204 } 205 } 206 bins := []string{} 207 for _, binary := range binaries { 208 dst := binary.Name 209 if arch.StripBinaryDirectory { 210 dst = filepath.Base(dst) 211 } 212 if err := a.Add(config.File{ 213 Source: binary.Path, 214 Destination: dst, 215 Info: arch.BuildsInfo, 216 }); err != nil { 217 return fmt.Errorf("failed to add: '%s' -> '%s': %w", binary.Path, dst, err) 218 } 219 bins = append(bins, binary.Name) 220 } 221 art := &artifact.Artifact{ 222 Type: artifact.UploadableArchive, 223 Name: folder + "." + format, 224 Path: archivePath, 225 Extra: map[string]interface{}{ 226 artifact.ExtraID: arch.ID, 227 artifact.ExtraFormat: format, 228 artifact.ExtraWrappedIn: wrap, 229 artifact.ExtraBinaries: bins, 230 }, 231 } 232 if len(binaries) > 0 { 233 art.Goos = binaries[0].Goos 234 art.Goarch = binaries[0].Goarch 235 art.Goarm = binaries[0].Goarm 236 art.Gomips = binaries[0].Gomips 237 art.Goamd64 = binaries[0].Goamd64 238 art.Extra[artifact.ExtraReplaces] = binaries[0].Extra[artifact.ExtraReplaces] 239 } 240 241 ctx.Artifacts.Add(art) 242 return nil 243 } 244 245 func wrapFolder(a config.Archive) string { 246 switch a.WrapInDirectory { 247 case "true": 248 return a.NameTemplate 249 case "false": 250 return "" 251 default: 252 return a.WrapInDirectory 253 } 254 } 255 256 func skip(ctx *context.Context, archive config.Archive, binaries []*artifact.Artifact) error { 257 for _, binary := range binaries { 258 name, err := tmpl.New(ctx).WithArtifact(binary).Apply(archive.NameTemplate) 259 if err != nil { 260 return err 261 } 262 finalName := name + artifact.ExtraOr(*binary, artifact.ExtraExt, "") 263 log.WithField("binary", binary.Name). 264 WithField("name", finalName). 265 Info("skip archiving") 266 ctx.Artifacts.Add(&artifact.Artifact{ 267 Type: artifact.UploadableBinary, 268 Name: finalName, 269 Path: binary.Path, 270 Goos: binary.Goos, 271 Goarch: binary.Goarch, 272 Goarm: binary.Goarm, 273 Gomips: binary.Gomips, 274 Goamd64: binary.Goamd64, 275 Extra: map[string]interface{}{ 276 artifact.ExtraID: archive.ID, 277 artifact.ExtraFormat: archive.Format, 278 artifact.ExtraBinary: binary.Name, 279 artifact.ExtraReplaces: binaries[0].Extra[artifact.ExtraReplaces], 280 }, 281 }) 282 } 283 return nil 284 } 285 286 func packageFormat(archive config.Archive, platform string) string { 287 for _, override := range archive.FormatOverrides { 288 if strings.HasPrefix(platform, override.Goos) { 289 return override.Format 290 } 291 } 292 return archive.Format 293 } 294 295 // NewEnhancedArchive enhances a pre-existing archive.Archive instance 296 // with this pipe specifics. 297 func NewEnhancedArchive(a archive.Archive, wrap string) archive.Archive { 298 return EnhancedArchive{ 299 a: a, 300 wrap: wrap, 301 files: map[string]string{}, 302 } 303 } 304 305 // EnhancedArchive is an archive.Archive implementation which decorates an 306 // archive.Archive adding wrap directory support, logging and windows 307 // backslash fixes. 308 type EnhancedArchive struct { 309 a archive.Archive 310 wrap string 311 files map[string]string 312 } 313 314 // Add adds a file. 315 func (d EnhancedArchive) Add(f config.File) error { 316 name := strings.ReplaceAll(filepath.Join(d.wrap, f.Destination), "\\", "/") 317 log.Debugf("adding file: %s as %s", f.Source, 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 }