github.com/goreleaser/goreleaser@v1.25.1/internal/archivefiles/archivefiles.go (about) 1 // Package archivefiles can evaluate a list of config.Files into their final form. 2 package archivefiles 3 4 import ( 5 "errors" 6 "fmt" 7 "io/fs" 8 "os" 9 "path/filepath" 10 "sort" 11 "time" 12 13 "github.com/caarlos0/log" 14 "github.com/goreleaser/fileglob" 15 "github.com/goreleaser/goreleaser/internal/tmpl" 16 "github.com/goreleaser/goreleaser/pkg/config" 17 ) 18 19 // Eval evaluates the given list of files to their final form. 20 func Eval(template *tmpl.Template, files []config.File) ([]config.File, error) { 21 var result []config.File 22 for _, f := range files { 23 glob, err := template.Apply(f.Source) 24 if err != nil { 25 return result, fmt.Errorf("failed to apply template %s: %w", f.Source, err) 26 } 27 28 files, err := fileglob.Glob(glob) 29 if err != nil { 30 return result, fmt.Errorf("globbing failed for pattern %s: %w", glob, err) 31 } 32 33 if len(files) == 0 { 34 if !f.Default { 35 // only log if its not a default glob, as those are usually 36 // very generic and are not really warnings for the user. 37 log.WithField("glob", f.Source).Warn("no files matched") 38 } 39 continue 40 } 41 42 if err := tmplInfo(template, &f.Info); err != nil { 43 return result, err 44 } 45 46 // the prefix may not be a complete path or may use glob patterns, in that case use the parent directory 47 prefix := glob 48 if _, err := os.Stat(prefix); errors.Is(err, fs.ErrNotExist) || fileglob.ContainsMatchers(prefix) { 49 prefix = filepath.Dir(longestCommonPrefix(files)) 50 } 51 52 for _, file := range files { 53 dst, err := destinationFor(f, prefix, file) 54 if err != nil { 55 return nil, err 56 } 57 result = append(result, config.File{ 58 Source: filepath.ToSlash(file), 59 Destination: filepath.ToSlash(dst), 60 Info: f.Info, 61 }) 62 } 63 } 64 65 sort.Slice(result, func(i, j int) bool { 66 return result[i].Destination < result[j].Destination 67 }) 68 69 return unique(result), nil 70 } 71 72 func tmplInfo(template *tmpl.Template, info *config.FileInfo) error { 73 if err := template.ApplyAll( 74 &info.Owner, 75 &info.Group, 76 &info.MTime, 77 ); err != nil { 78 return err 79 } 80 if info.MTime != "" { 81 var err error 82 info.ParsedMTime, err = time.Parse(time.RFC3339Nano, info.MTime) 83 if err != nil { 84 return fmt.Errorf("failed to parse %s: %w", info.MTime, err) 85 } 86 } 87 return nil 88 } 89 90 // remove duplicates 91 func unique(in []config.File) []config.File { 92 var result []config.File 93 exist := map[string]string{} 94 for _, f := range in { 95 if current := exist[f.Destination]; current != "" { 96 log.Warnf( 97 "file '%s' already exists in archive as '%s' - '%s' will be ignored", 98 f.Destination, 99 current, 100 f.Source, 101 ) 102 continue 103 } 104 exist[f.Destination] = f.Source 105 result = append(result, f) 106 } 107 108 return result 109 } 110 111 func destinationFor(f config.File, prefix, path string) (string, error) { 112 if f.StripParent { 113 return filepath.Join(f.Destination, filepath.Base(path)), nil 114 } 115 116 if f.Destination != "" { 117 relpath, err := filepath.Rel(prefix, path) 118 if err != nil { 119 // since prefix is a prefix of src a relative path should always be found 120 return "", err 121 } 122 return filepath.ToSlash(filepath.Join(f.Destination, relpath)), nil 123 } 124 125 return filepath.Join(f.Destination, path), nil 126 } 127 128 // longestCommonPrefix returns the longest prefix of all strings the argument 129 // slice. If the slice is empty the empty string is returned. 130 // copied from nfpm 131 func longestCommonPrefix(strs []string) string { 132 if len(strs) == 0 { 133 return "" 134 } 135 lcp := strs[0] 136 for _, str := range strs { 137 lcp = strlcp(lcp, str) 138 } 139 return lcp 140 } 141 142 // copied from nfpm 143 func strlcp(a, b string) string { 144 var min int 145 if len(a) > len(b) { 146 min = len(b) 147 } else { 148 min = len(a) 149 } 150 for i := 0; i < min; i++ { 151 if a[i] != b[i] { 152 return a[0:i] 153 } 154 } 155 return a[0:min] 156 }