github.com/windmeup/goreleaser@v1.21.95/internal/pipe/checksums/checksums.go (about)

     1  // Package checksums provides a Pipe that creates .checksums files for
     2  // each artifact.
     3  package checksums
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/caarlos0/log"
    15  	"github.com/windmeup/goreleaser/internal/artifact"
    16  	"github.com/windmeup/goreleaser/internal/extrafiles"
    17  	"github.com/windmeup/goreleaser/internal/semerrgroup"
    18  	"github.com/windmeup/goreleaser/internal/tmpl"
    19  	"github.com/windmeup/goreleaser/pkg/context"
    20  )
    21  
    22  const (
    23  	artifactChecksumExtra = "Checksum"
    24  )
    25  
    26  var (
    27  	errNoArtifacts = errors.New("there are no artifacts to sign")
    28  	lock           sync.Mutex
    29  )
    30  
    31  // Pipe for checksums.
    32  type Pipe struct{}
    33  
    34  func (Pipe) String() string                 { return "calculating checksums" }
    35  func (Pipe) Skip(ctx *context.Context) bool { return ctx.Config.Checksum.Disable }
    36  
    37  // Default sets the pipe defaults.
    38  func (Pipe) Default(ctx *context.Context) error {
    39  	if ctx.Config.Checksum.NameTemplate == "" {
    40  		ctx.Config.Checksum.NameTemplate = "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
    41  	}
    42  	if ctx.Config.Checksum.Algorithm == "" {
    43  		ctx.Config.Checksum.Algorithm = "sha256"
    44  	}
    45  	return nil
    46  }
    47  
    48  // Run the pipe.
    49  func (Pipe) Run(ctx *context.Context) error {
    50  	filename, err := tmpl.New(ctx).Apply(ctx.Config.Checksum.NameTemplate)
    51  	if err != nil {
    52  		return err
    53  	}
    54  	filepath := filepath.Join(ctx.Config.Dist, filename)
    55  	if err := refresh(ctx, filepath); err != nil {
    56  		if errors.Is(err, errNoArtifacts) {
    57  			return nil
    58  		}
    59  		return err
    60  	}
    61  	ctx.Artifacts.Add(&artifact.Artifact{
    62  		Type: artifact.Checksum,
    63  		Path: filepath,
    64  		Name: filename,
    65  		Extra: map[string]interface{}{
    66  			artifact.ExtraRefresh: func() error {
    67  				log.WithField("file", filename).Info("refreshing checksums")
    68  				return refresh(ctx, filepath)
    69  			},
    70  		},
    71  	})
    72  	return nil
    73  }
    74  
    75  func refresh(ctx *context.Context, filepath string) error {
    76  	lock.Lock()
    77  	defer lock.Unlock()
    78  	filter := artifact.Or(
    79  		artifact.ByType(artifact.UploadableArchive),
    80  		artifact.ByType(artifact.UploadableBinary),
    81  		artifact.ByType(artifact.UploadableSourceArchive),
    82  		artifact.ByType(artifact.LinuxPackage),
    83  		artifact.ByType(artifact.SBOM),
    84  	)
    85  	if len(ctx.Config.Checksum.IDs) > 0 {
    86  		filter = artifact.And(filter, artifact.ByIDs(ctx.Config.Checksum.IDs...))
    87  	}
    88  
    89  	artifactList := ctx.Artifacts.Filter(filter).List()
    90  
    91  	extraFiles, err := extrafiles.Find(ctx, ctx.Config.Checksum.ExtraFiles)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	for name, path := range extraFiles {
    97  		artifactList = append(artifactList, &artifact.Artifact{
    98  			Name: name,
    99  			Path: path,
   100  			Type: artifact.UploadableFile,
   101  		})
   102  	}
   103  
   104  	if len(artifactList) == 0 {
   105  		return errNoArtifacts
   106  	}
   107  
   108  	g := semerrgroup.New(ctx.Parallelism)
   109  	sumLines := make([]string, len(artifactList))
   110  	for i, artifact := range artifactList {
   111  		i := i
   112  		artifact := artifact
   113  		g.Go(func() error {
   114  			sumLine, err := checksums(ctx.Config.Checksum.Algorithm, artifact)
   115  			if err != nil {
   116  				return err
   117  			}
   118  			sumLines[i] = sumLine
   119  			return nil
   120  		})
   121  	}
   122  
   123  	err = g.Wait()
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	file, err := os.OpenFile(
   129  		filepath,
   130  		os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
   131  		0o644,
   132  	)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	defer file.Close()
   137  
   138  	// sort to ensure the signature is deterministic downstream
   139  	sort.Sort(ByFilename(sumLines))
   140  	_, err = file.WriteString(strings.Join(sumLines, ""))
   141  	return err
   142  }
   143  
   144  func checksums(algorithm string, a *artifact.Artifact) (string, error) {
   145  	log.WithField("file", a.Name).Debug("checksumming")
   146  	sha, err := a.Checksum(algorithm)
   147  	if err != nil {
   148  		return "", err
   149  	}
   150  
   151  	if a.Extra == nil {
   152  		a.Extra = make(artifact.Extras)
   153  	}
   154  	a.Extra[artifactChecksumExtra] = fmt.Sprintf("%s:%s", algorithm, sha)
   155  
   156  	return fmt.Sprintf("%v  %v\n", sha, a.Name), nil
   157  }
   158  
   159  // ByFilename implements sort.Interface for []string based on
   160  // the filename of a checksum line ("{checksum}  {filename}\n")
   161  type ByFilename []string
   162  
   163  func (s ByFilename) Len() int      { return len(s) }
   164  func (s ByFilename) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   165  func (s ByFilename) Less(i, j int) bool {
   166  	return strings.Split(s[i], "  ")[1] < strings.Split(s[j], "  ")[1]
   167  }