github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/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 "sort" 12 "strings" 13 "sync" 14 15 "github.com/apex/log" 16 "github.com/goreleaser/fileglob" 17 18 "github.com/goreleaser/goreleaser/internal/artifact" 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 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 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 if len(archive.Builds) == 0 { 78 for _, build := range ctx.Config.Builds { 79 archive.Builds = append(archive.Builds, build.ID) 80 } 81 } 82 ids.Inc(archive.ID) 83 } 84 return ids.Validate() 85 } 86 87 // Run the pipe. 88 func (Pipe) Run(ctx *context.Context) error { 89 g := semerrgroup.New(ctx.Parallelism) 90 for i, archive := range ctx.Config.Archives { 91 archive := archive 92 artifacts := ctx.Artifacts.Filter( 93 artifact.And( 94 artifact.ByType(artifact.Binary), 95 artifact.ByIDs(archive.Builds...), 96 ), 97 ).GroupByPlatform() 98 if err := checkArtifacts(artifacts); err != nil && !archive.AllowDifferentBinaryCount { 99 return fmt.Errorf("invalid archive: %d: %w", i, ErrArchiveDifferentBinaryCount) 100 } 101 for group, artifacts := range artifacts { 102 log.Debugf("group %s has %d binaries", group, len(artifacts)) 103 artifacts := artifacts 104 g.Go(func() error { 105 if packageFormat(archive, artifacts[0].Goos) == "binary" { 106 return skip(ctx, archive, artifacts) 107 } 108 return create(ctx, archive, artifacts) 109 }) 110 } 111 } 112 return g.Wait() 113 } 114 115 func checkArtifacts(artifacts map[string][]*artifact.Artifact) error { 116 lens := map[int]bool{} 117 for _, v := range artifacts { 118 lens[len(v)] = true 119 } 120 if len(lens) <= 1 { 121 return nil 122 } 123 return ErrArchiveDifferentBinaryCount 124 } 125 126 func create(ctx *context.Context, arch config.Archive, binaries []*artifact.Artifact) error { 127 format := packageFormat(arch, binaries[0].Goos) 128 folder, err := tmpl.New(ctx). 129 WithArtifact(binaries[0], arch.Replacements). 130 Apply(arch.NameTemplate) 131 if err != nil { 132 return err 133 } 134 archivePath := filepath.Join(ctx.Config.Dist, folder+"."+format) 135 lock.Lock() 136 if err := os.MkdirAll(filepath.Dir(archivePath), 0o755|os.ModeDir); err != nil { 137 lock.Unlock() 138 return err 139 } 140 if _, err = os.Stat(archivePath); !os.IsNotExist(err) { 141 lock.Unlock() 142 return fmt.Errorf("archive named %s already exists. Check your archive name template", archivePath) 143 } 144 archiveFile, err := os.Create(archivePath) 145 if err != nil { 146 lock.Unlock() 147 return fmt.Errorf("failed to create directory %s: %w", archivePath, err) 148 } 149 lock.Unlock() 150 defer archiveFile.Close() 151 152 log := log.WithField("archive", archivePath) 153 log.Info("creating") 154 155 template := tmpl.New(ctx). 156 WithArtifact(binaries[0], arch.Replacements) 157 wrap, err := template.Apply(wrapFolder(arch)) 158 if err != nil { 159 return err 160 } 161 162 a := NewEnhancedArchive(archive.New(archiveFile), wrap) 163 defer a.Close() 164 165 files, err := findFiles(template, arch.Files) 166 if err != nil { 167 return fmt.Errorf("failed to find files to archive: %w", err) 168 } 169 for _, f := range files { 170 if err = a.Add(f); err != nil { 171 return fmt.Errorf("failed to add: '%s' -> '%s': %w", f.Source, f.Destination, err) 172 } 173 } 174 for _, binary := range binaries { 175 if err := a.Add(config.File{ 176 Source: binary.Path, 177 Destination: binary.Name, 178 }); err != nil { 179 return fmt.Errorf("failed to add: '%s' -> '%s': %w", binary.Path, binary.Name, err) 180 } 181 } 182 ctx.Artifacts.Add(&artifact.Artifact{ 183 Type: artifact.UploadableArchive, 184 Name: folder + "." + format, 185 Path: archivePath, 186 Goos: binaries[0].Goos, 187 Goarch: binaries[0].Goarch, 188 Goarm: binaries[0].Goarm, 189 Gomips: binaries[0].Gomips, 190 Extra: map[string]interface{}{ 191 "Builds": binaries, 192 "ID": arch.ID, 193 "Format": arch.Format, 194 "WrappedIn": wrap, 195 }, 196 }) 197 return nil 198 } 199 200 func wrapFolder(a config.Archive) string { 201 switch a.WrapInDirectory { 202 case "true": 203 return a.NameTemplate 204 case "false": 205 return "" 206 default: 207 return a.WrapInDirectory 208 } 209 } 210 211 func skip(ctx *context.Context, archive config.Archive, binaries []*artifact.Artifact) error { 212 for _, binary := range binaries { 213 log.WithField("binary", binary.Name).Info("skip archiving") 214 name, err := tmpl.New(ctx). 215 WithArtifact(binary, archive.Replacements). 216 Apply(archive.NameTemplate) 217 if err != nil { 218 return err 219 } 220 ctx.Artifacts.Add(&artifact.Artifact{ 221 Type: artifact.UploadableBinary, 222 Name: name + binary.ExtraOr("Ext", "").(string), 223 Path: binary.Path, 224 Goos: binary.Goos, 225 Goarch: binary.Goarch, 226 Goarm: binary.Goarm, 227 Gomips: binary.Gomips, 228 Extra: map[string]interface{}{ 229 "Builds": []*artifact.Artifact{binary}, 230 "ID": archive.ID, 231 "Format": archive.Format, 232 }, 233 }) 234 } 235 return nil 236 } 237 238 func findFiles(template *tmpl.Template, files []config.File) ([]config.File, error) { 239 var result []config.File 240 for _, f := range files { 241 replaced, err := template.Apply(f.Source) 242 if err != nil { 243 return result, fmt.Errorf("failed to apply template %s: %w", f.Source, err) 244 } 245 246 files, err := fileglob.Glob(replaced) 247 if err != nil { 248 return result, fmt.Errorf("globbing failed for pattern %s: %w", f.Source, err) 249 } 250 251 for _, file := range files { 252 result = append(result, config.File{ 253 Source: file, 254 Destination: destinationFor(f, file), 255 Info: f.Info, 256 }) 257 } 258 } 259 260 sort.Slice(result, func(i, j int) bool { 261 return result[i].Destination < result[j].Destination 262 }) 263 264 return unique(result), nil 265 } 266 267 // remove duplicates 268 func unique(in []config.File) []config.File { 269 var result []config.File 270 exist := map[string]string{} 271 for _, f := range in { 272 if current := exist[f.Destination]; current != "" { 273 log.Warnf( 274 "file '%s' already exists in archive as '%s' - '%s' will be ignored", 275 f.Destination, 276 current, 277 f.Source, 278 ) 279 continue 280 } 281 exist[f.Destination] = f.Source 282 result = append(result, f) 283 } 284 285 return result 286 } 287 288 func destinationFor(f config.File, path string) string { 289 if f.Destination == "" { 290 return path 291 } 292 if f.StripParent { 293 return filepath.Join(f.Destination, filepath.Base(path)) 294 } 295 return filepath.Join(f.Destination, path) 296 } 297 298 func packageFormat(archive config.Archive, platform string) string { 299 for _, override := range archive.FormatOverrides { 300 if strings.HasPrefix(platform, override.Goos) { 301 return override.Format 302 } 303 } 304 return archive.Format 305 } 306 307 // NewEnhancedArchive enhances a pre-existing archive.Archive instance 308 // with this pipe specifics. 309 func NewEnhancedArchive(a archive.Archive, wrap string) archive.Archive { 310 return EnhancedArchive{ 311 a: a, 312 wrap: wrap, 313 files: map[string]string{}, 314 } 315 } 316 317 // EnhancedArchive is an archive.Archive implementation which decorates an 318 // archive.Archive adding wrap directory support, logging and windows 319 // backslash fixes. 320 type EnhancedArchive struct { 321 a archive.Archive 322 wrap string 323 files map[string]string 324 } 325 326 // Add adds a file. 327 func (d EnhancedArchive) Add(f config.File) error { 328 name := strings.ReplaceAll(filepath.Join(d.wrap, f.Destination), "\\", "/") 329 log.Debugf("adding file: %s as %s", f.Source, name) 330 if _, ok := d.files[f.Destination]; ok { 331 return fmt.Errorf("file %s already exists in the archive", f.Destination) 332 } 333 d.files[f.Destination] = name 334 ff := config.File{ 335 Source: f.Source, 336 Destination: name, 337 Info: f.Info, 338 } 339 return d.a.Add(ff) 340 } 341 342 // Close closes the underlying archive. 343 func (d EnhancedArchive) Close() error { 344 return d.a.Close() 345 }