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 }