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  }