1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-10 03:47:03 +02:00
goreleaser/internal/archivefiles/archivefiles.go
Carlos Alexandro Becker a209757ad2
feat: better archives relative paths (#3656)
with this patch, a config like:

```yaml
archives:
  - format: tar.gz
    # this name template makes the OS and Arch compatible with the results of uname.
    name_template: >-
      {{ .ProjectName }}_
      {{- title .Os }}_
      {{- if eq .Arch "amd64" }}x86_64
      {{- else if eq .Arch "386" }}i386
      {{- else }}{{ .Arch }}{{ end }}
      {{- if .Arm }}v{{ .Arm }}{{ end }}
    rlcp: true
    files:
      - src: "build/**/*"
        dst: .

nfpms:
  - package_name: foo
    contents:
      - src: "build/**/*"
        dst: usr/share/foo
    formats:
      - apk

```

will eval this:

<img width="1384" alt="CleanShot 2022-12-21 at 22 21 00@2x"
src="https://user-images.githubusercontent.com/245435/209034244-7c31b5f7-cfcd-4825-bb2f-7dd463c5286a.png">

as much as I would like to make this the default, it would be a breaking
change, so we really can't do it.

If `dst` is empty, it'll have the same behavior as before (no rlcp), and
if `strip_parent` is set, it will also still have the same behavior.
Finally, if the format is binary, `rlcp` is ignored too (as it doesn't
make sense).

So, this only changes if:
- your format is not binary; and
- you have files with `src` and `dst` set

Then, goreleaser will warn you to set `rlcp: true`.

## todo

- [x] docs
- [x] more tests probably
- [x] any ideas for a better name for the new config option?

fixes #3655

Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2022-12-27 17:42:55 -03:00

145 lines
3.6 KiB
Go

// Package archivefiles can evaluate a list of config.Files into their final form.
package archivefiles
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"time"
"github.com/caarlos0/log"
"github.com/goreleaser/fileglob"
"github.com/goreleaser/goreleaser/internal/tmpl"
"github.com/goreleaser/goreleaser/pkg/config"
)
// Eval evaluates the given list of files to their final form.
func Eval(template *tmpl.Template, rlcp bool, files []config.File) ([]config.File, error) {
var result []config.File
for _, f := range files {
replaced, err := template.Apply(f.Source)
if err != nil {
return result, fmt.Errorf("failed to apply template %s: %w", f.Source, err)
}
files, err := fileglob.Glob(replaced)
if err != nil {
return result, fmt.Errorf("globbing failed for pattern %s: %w", replaced, err)
}
f.Info.Owner, err = template.Apply(f.Info.Owner)
if err != nil {
return result, fmt.Errorf("failed to apply template %s: %w", f.Info.Owner, err)
}
f.Info.Group, err = template.Apply(f.Info.Group)
if err != nil {
return result, fmt.Errorf("failed to apply template %s: %w", f.Info.Group, err)
}
f.Info.MTime, err = template.Apply(f.Info.MTime)
if err != nil {
return result, fmt.Errorf("failed to apply template %s: %w", f.Info.MTime, err)
}
if f.Info.MTime != "" {
f.Info.ParsedMTime, err = time.Parse(time.RFC3339Nano, f.Info.MTime)
if err != nil {
return result, fmt.Errorf("failed to parse %s: %w", f.Info.MTime, err)
}
}
// the prefix may not be a complete path or may use glob patterns, in that case use the parent directory
prefix := replaced
if _, err := os.Stat(prefix); errors.Is(err, fs.ErrNotExist) || fileglob.ContainsMatchers(prefix) {
prefix = filepath.Dir(longestCommonPrefix(files))
}
for _, file := range files {
dst, err := destinationFor(f, prefix, file, rlcp)
if err != nil {
return nil, err
}
result = append(result, config.File{
Source: file,
Destination: dst,
Info: f.Info,
})
}
}
sort.Slice(result, func(i, j int) bool {
return result[i].Destination < result[j].Destination
})
return unique(result), nil
}
// remove duplicates
func unique(in []config.File) []config.File {
var result []config.File
exist := map[string]string{}
for _, f := range in {
if current := exist[f.Destination]; current != "" {
log.Warnf(
"file '%s' already exists in archive as '%s' - '%s' will be ignored",
f.Destination,
current,
f.Source,
)
continue
}
exist[f.Destination] = f.Source
result = append(result, f)
}
return result
}
func destinationFor(f config.File, prefix, path string, rlcp bool) (string, error) {
if f.StripParent {
return filepath.Join(f.Destination, filepath.Base(path)), nil
}
if rlcp && f.Destination != "" {
relpath, err := filepath.Rel(prefix, path)
if err != nil {
// since prefix is a prefix of src a relative path should always be found
return "", err
}
return filepath.ToSlash(filepath.Join(f.Destination, relpath)), nil
}
return filepath.Join(f.Destination, path), nil
}
// longestCommonPrefix returns the longest prefix of all strings the argument
// slice. If the slice is empty the empty string is returned.
// copied from nfpm
func longestCommonPrefix(strs []string) string {
if len(strs) == 0 {
return ""
}
lcp := strs[0]
for _, str := range strs {
lcp = strlcp(lcp, str)
}
return lcp
}
// copied from nfpm
func strlcp(a, b string) string {
var min int
if len(a) > len(b) {
min = len(b)
} else {
min = len(a)
}
for i := 0; i < min; i++ {
if a[i] != b[i] {
return a[0:i]
}
}
return a[0:min]
}