github.com/goreleaser/goreleaser@v1.25.1/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/goreleaser/goreleaser/internal/artifact"
    16  	"github.com/goreleaser/goreleaser/internal/extrafiles"
    17  	"github.com/goreleaser/goreleaser/internal/semerrgroup"
    18  	"github.com/goreleaser/goreleaser/internal/tmpl"
    19  	"github.com/goreleaser/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  	cs := &ctx.Config.Checksum
    40  	if cs.Algorithm == "" {
    41  		cs.Algorithm = "sha256"
    42  	}
    43  	if cs.NameTemplate == "" {
    44  		if cs.Split {
    45  			cs.NameTemplate = "{{ .ArtifactName }}.{{ .Algorithm }}"
    46  		} else {
    47  			cs.NameTemplate = "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
    48  		}
    49  	}
    50  	return nil
    51  }
    52  
    53  // Run the pipe.
    54  func (Pipe) Run(ctx *context.Context) error {
    55  	if ctx.Config.Checksum.Split {
    56  		return splitChecksum(ctx)
    57  	}
    58  
    59  	return singleChecksum(ctx)
    60  }
    61  
    62  func splitChecksum(ctx *context.Context) error {
    63  	artifactList, err := buildArtifactList(ctx)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	for _, art := range artifactList {
    69  		filename, err := tmpl.New(ctx).
    70  			WithArtifact(art).
    71  			WithExtraFields(tmpl.Fields{
    72  				"Algorithm": ctx.Config.Checksum.Algorithm,
    73  			}).
    74  			Apply(ctx.Config.Checksum.NameTemplate)
    75  		if err != nil {
    76  			return fmt.Errorf("checksum: name template: %w", err)
    77  		}
    78  		filepath := filepath.Join(ctx.Config.Dist, filename)
    79  		if err := refreshOne(ctx, *art, filepath); err != nil {
    80  			return fmt.Errorf("checksum: %s: %w", art.Path, err)
    81  		}
    82  		ctx.Artifacts.Add(&artifact.Artifact{
    83  			Type: artifact.Checksum,
    84  			Path: filepath,
    85  			Name: filename,
    86  			Extra: map[string]interface{}{
    87  				artifact.ExtraChecksumOf: art.Path,
    88  				artifact.ExtraRefresh: func() error {
    89  					log.WithField("file", filename).Info("refreshing checksums")
    90  					return refreshOne(ctx, *art, filepath)
    91  				},
    92  			},
    93  		})
    94  	}
    95  	return nil
    96  }
    97  
    98  func singleChecksum(ctx *context.Context) error {
    99  	filename, err := tmpl.New(ctx).Apply(ctx.Config.Checksum.NameTemplate)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	filepath := filepath.Join(ctx.Config.Dist, filename)
   104  	if err := refreshAll(ctx, filepath); err != nil {
   105  		if errors.Is(err, errNoArtifacts) {
   106  			return nil
   107  		}
   108  		return err
   109  	}
   110  	ctx.Artifacts.Add(&artifact.Artifact{
   111  		Type: artifact.Checksum,
   112  		Path: filepath,
   113  		Name: filename,
   114  		Extra: map[string]interface{}{
   115  			artifact.ExtraRefresh: func() error {
   116  				log.WithField("file", filename).Info("refreshing checksums")
   117  				return refreshAll(ctx, filepath)
   118  			},
   119  		},
   120  	})
   121  	return nil
   122  }
   123  
   124  func refreshOne(ctx *context.Context, art artifact.Artifact, path string) error {
   125  	check, err := art.Checksum(ctx.Config.Checksum.Algorithm)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	return os.WriteFile(path, []byte(check), 0o644)
   130  }
   131  
   132  func refreshAll(ctx *context.Context, filepath string) error {
   133  	lock.Lock()
   134  	defer lock.Unlock()
   135  
   136  	artifactList, err := buildArtifactList(ctx)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	g := semerrgroup.New(ctx.Parallelism)
   142  	sumLines := make([]string, len(artifactList))
   143  	for i, artifact := range artifactList {
   144  		i := i
   145  		artifact := artifact
   146  		g.Go(func() error {
   147  			sumLine, err := checksums(ctx.Config.Checksum.Algorithm, artifact)
   148  			if err != nil {
   149  				return err
   150  			}
   151  			sumLines[i] = sumLine
   152  			return nil
   153  		})
   154  	}
   155  
   156  	err = g.Wait()
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	file, err := os.OpenFile(
   162  		filepath,
   163  		os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
   164  		0o644,
   165  	)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	defer file.Close()
   170  
   171  	// sort to ensure the signature is deterministic downstream
   172  	sort.Sort(ByFilename(sumLines))
   173  	_, err = file.WriteString(strings.Join(sumLines, ""))
   174  	return err
   175  }
   176  
   177  func buildArtifactList(ctx *context.Context) ([]*artifact.Artifact, error) {
   178  	filter := artifact.Or(
   179  		artifact.ByType(artifact.UploadableArchive),
   180  		artifact.ByType(artifact.UploadableBinary),
   181  		artifact.ByType(artifact.UploadableSourceArchive),
   182  		artifact.ByType(artifact.LinuxPackage),
   183  		artifact.ByType(artifact.SBOM),
   184  	)
   185  	if len(ctx.Config.Checksum.IDs) > 0 {
   186  		filter = artifact.And(filter, artifact.ByIDs(ctx.Config.Checksum.IDs...))
   187  	}
   188  
   189  	artifactList := ctx.Artifacts.Filter(filter).List()
   190  
   191  	extraFiles, err := extrafiles.Find(ctx, ctx.Config.Checksum.ExtraFiles)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	for name, path := range extraFiles {
   197  		artifactList = append(artifactList, &artifact.Artifact{
   198  			Name: name,
   199  			Path: path,
   200  			Type: artifact.UploadableFile,
   201  		})
   202  	}
   203  
   204  	if len(artifactList) == 0 {
   205  		return nil, errNoArtifacts
   206  	}
   207  	return artifactList, nil
   208  }
   209  
   210  func checksums(algorithm string, a *artifact.Artifact) (string, error) {
   211  	log.WithField("file", a.Name).Debug("checksumming")
   212  	sha, err := a.Checksum(algorithm)
   213  	if err != nil {
   214  		return "", err
   215  	}
   216  
   217  	if a.Extra == nil {
   218  		a.Extra = make(artifact.Extras)
   219  	}
   220  	a.Extra[artifactChecksumExtra] = fmt.Sprintf("%s:%s", algorithm, sha)
   221  
   222  	return fmt.Sprintf("%v  %v\n", sha, a.Name), nil
   223  }
   224  
   225  // ByFilename implements sort.Interface for []string based on
   226  // the filename of a checksum line ("{checksum}  {filename}\n")
   227  type ByFilename []string
   228  
   229  func (s ByFilename) Len() int      { return len(s) }
   230  func (s ByFilename) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   231  func (s ByFilename) Less(i, j int) bool {
   232  	return strings.Split(s[i], "  ")[1] < strings.Split(s[j], "  ")[1]
   233  }