github.com/amane3/goreleaser@v0.182.0/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 "os" 10 "path/filepath" 11 "strings" 12 "sync" 13 14 "github.com/apex/log" 15 "github.com/campoy/unique" 16 "github.com/goreleaser/fileglob" 17 18 "github.com/amane3/goreleaser/internal/artifact" 19 "github.com/amane3/goreleaser/internal/ids" 20 "github.com/amane3/goreleaser/internal/semerrgroup" 21 "github.com/amane3/goreleaser/internal/tmpl" 22 "github.com/amane3/goreleaser/pkg/archive" 23 "github.com/amane3/goreleaser/pkg/config" 24 "github.com/amane3/goreleaser/pkg/context" 25 ) 26 27 const ( 28 defaultNameTemplate = "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" 29 defaultBinaryNameTemplate = "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" 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 built binaries for each platform, which may cause your users confusion. Please make sure all builds used have the same set of goos/goarch/etc or split it into multiple archives") 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 var 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 var 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 = []string{ 63 "licence*", 64 "LICENCE*", 65 "license*", 66 "LICENSE*", 67 "readme*", 68 "README*", 69 "changelog*", 70 "CHANGELOG*", 71 } 72 } 73 if archive.NameTemplate == "" { 74 archive.NameTemplate = defaultNameTemplate 75 if archive.Format == "binary" { 76 archive.NameTemplate = defaultBinaryNameTemplate 77 } 78 } 79 if len(archive.Builds) == 0 { 80 for _, build := range ctx.Config.Builds { 81 archive.Builds = append(archive.Builds, build.ID) 82 } 83 } 84 ids.Inc(archive.ID) 85 } 86 return ids.Validate() 87 } 88 89 // Run the pipe. 90 func (Pipe) Run(ctx *context.Context) error { 91 var g = semerrgroup.New(ctx.Parallelism) 92 for i, archive := range ctx.Config.Archives { 93 archive := archive 94 var artifacts = ctx.Artifacts.Filter( 95 artifact.And( 96 artifact.ByType(artifact.Binary), 97 artifact.ByIDs(archive.Builds...), 98 ), 99 ).GroupByPlatform() 100 if err := checkArtifacts(artifacts); err != nil && !archive.AllowDifferentBinaryCount { 101 return fmt.Errorf("invalid archive: %d: %w", i, ErrArchiveDifferentBinaryCount) 102 } 103 for group, artifacts := range artifacts { 104 log.Debugf("group %s has %d binaries", group, len(artifacts)) 105 artifacts := artifacts 106 g.Go(func() error { 107 if packageFormat(archive, artifacts[0].Goos) == "binary" { 108 return skip(ctx, archive, artifacts) 109 } 110 return create(ctx, archive, artifacts) 111 }) 112 } 113 } 114 return g.Wait() 115 } 116 117 func checkArtifacts(artifacts map[string][]*artifact.Artifact) error { 118 var lens = map[int]bool{} 119 for _, v := range artifacts { 120 lens[len(v)] = true 121 } 122 if len(lens) <= 1 { 123 return nil 124 } 125 return ErrArchiveDifferentBinaryCount 126 } 127 128 func create(ctx *context.Context, arch config.Archive, binaries []*artifact.Artifact) error { 129 var format = packageFormat(arch, binaries[0].Goos) 130 folder, err := tmpl.New(ctx). 131 WithArtifact(binaries[0], arch.Replacements). 132 Apply(arch.NameTemplate) 133 if err != nil { 134 return err 135 } 136 archivePath := filepath.Join(ctx.Config.Dist, folder+"."+format) 137 lock.Lock() 138 if err := os.MkdirAll(filepath.Dir(archivePath), 0755|os.ModeDir); err != nil { 139 lock.Unlock() 140 return err 141 } 142 if _, err = os.Stat(archivePath); !os.IsNotExist(err) { 143 lock.Unlock() 144 return fmt.Errorf("archive named %s already exists. Check your archive name template", archivePath) 145 } 146 archiveFile, err := os.Create(archivePath) 147 if err != nil { 148 lock.Unlock() 149 return fmt.Errorf("failed to create directory %s: %w", archivePath, err) 150 } 151 lock.Unlock() 152 defer archiveFile.Close() 153 154 var log = log.WithField("archive", archivePath) 155 log.Info("creating") 156 157 template := tmpl.New(ctx). 158 WithArtifact(binaries[0], arch.Replacements) 159 wrap, err := template.Apply(wrapFolder(arch)) 160 if err != nil { 161 return err 162 } 163 164 var a = NewEnhancedArchive(archive.New(archiveFile), wrap) 165 defer a.Close() 166 167 files, err := findFiles(template, arch) 168 if err != nil { 169 return fmt.Errorf("failed to find files to archive: %w", err) 170 } 171 for _, f := range files { 172 if err = a.Add(f, f); err != nil { 173 return fmt.Errorf("failed to add %s to the archive: %w", f, err) 174 } 175 } 176 for _, binary := range binaries { 177 if err := a.Add(binary.Name, binary.Path); err != nil { 178 return fmt.Errorf("failed to add %s -> %s to the archive: %w", binary.Path, binary.Name, err) 179 } 180 } 181 ctx.Artifacts.Add(&artifact.Artifact{ 182 Type: artifact.UploadableArchive, 183 Name: folder + "." + format, 184 Path: archivePath, 185 Goos: binaries[0].Goos, 186 Goarch: binaries[0].Goarch, 187 Goarm: binaries[0].Goarm, 188 Gomips: binaries[0].Gomips, 189 Extra: map[string]interface{}{ 190 "Builds": binaries, 191 "ID": arch.ID, 192 "Format": arch.Format, 193 "WrappedIn": wrap, 194 }, 195 }) 196 return nil 197 } 198 199 func wrapFolder(a config.Archive) string { 200 switch a.WrapInDirectory { 201 case "true": 202 return a.NameTemplate 203 case "false": 204 return "" 205 default: 206 return a.WrapInDirectory 207 } 208 } 209 210 func skip(ctx *context.Context, archive config.Archive, binaries []*artifact.Artifact) error { 211 for _, binary := range binaries { 212 log.WithField("binary", binary.Name).Info("skip archiving") 213 name, err := tmpl.New(ctx). 214 WithArtifact(binary, archive.Replacements). 215 Apply(archive.NameTemplate) 216 if err != nil { 217 return err 218 } 219 ctx.Artifacts.Add(&artifact.Artifact{ 220 Type: artifact.UploadableBinary, 221 Name: name + binary.ExtraOr("Ext", "").(string), 222 Path: binary.Path, 223 Goos: binary.Goos, 224 Goarch: binary.Goarch, 225 Goarm: binary.Goarm, 226 Gomips: binary.Gomips, 227 Extra: map[string]interface{}{ 228 "Builds": []*artifact.Artifact{binary}, 229 "ID": archive.ID, 230 "Format": archive.Format, 231 }, 232 }) 233 } 234 return nil 235 } 236 237 func findFiles(template *tmpl.Template, archive config.Archive) (result []string, err error) { 238 for _, glob := range archive.Files { 239 replaced, err := template.Apply(glob) 240 if err != nil { 241 return result, fmt.Errorf("failed to apply template %s: %w", glob, err) 242 } 243 files, err := fileglob.Glob(replaced) 244 if err != nil { 245 return result, fmt.Errorf("globbing failed for pattern %s: %w", glob, err) 246 } 247 result = append(result, files...) 248 } 249 // remove duplicates 250 unique.Slice(&result, func(i, j int) bool { 251 return strings.Compare(result[i], result[j]) < 0 252 }) 253 return 254 } 255 256 func packageFormat(archive config.Archive, platform string) string { 257 for _, override := range archive.FormatOverrides { 258 if strings.HasPrefix(platform, override.Goos) { 259 return override.Format 260 } 261 } 262 return archive.Format 263 } 264 265 // NewEnhancedArchive enhances a pre-existing archive.Archive instance 266 // with this pipe specifics. 267 func NewEnhancedArchive(a archive.Archive, wrap string) archive.Archive { 268 return EnhancedArchive{ 269 a: a, 270 wrap: wrap, 271 files: map[string]string{}, 272 } 273 } 274 275 // EnhancedArchive is an archive.Archive implementation which decorates an 276 // archive.Archive adding wrap directory support, logging and windows 277 // backslash fixes. 278 type EnhancedArchive struct { 279 a archive.Archive 280 wrap string 281 files map[string]string 282 } 283 284 // Add adds a file. 285 func (d EnhancedArchive) Add(name, path string) error { 286 name = strings.ReplaceAll(filepath.Join(d.wrap, name), "\\", "/") 287 log.Debugf("adding file: %s as %s", path, name) 288 if _, ok := d.files[name]; ok { 289 return fmt.Errorf("file %s already exists in the archive", name) 290 } 291 d.files[name] = path 292 return d.a.Add(name, path) 293 } 294 295 // Close closes the underlying archive. 296 func (d EnhancedArchive) Close() error { 297 return d.a.Close() 298 }