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 }