github.com/szyn/goreleaser@v0.76.1-0.20180517112710-333da09a1297/pipeline/brew/brew.go (about) 1 package brew 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "path/filepath" 9 "strings" 10 "text/template" 11 12 "github.com/apex/log" 13 14 "github.com/goreleaser/goreleaser/checksum" 15 "github.com/goreleaser/goreleaser/config" 16 "github.com/goreleaser/goreleaser/context" 17 "github.com/goreleaser/goreleaser/internal/artifact" 18 "github.com/goreleaser/goreleaser/internal/client" 19 "github.com/goreleaser/goreleaser/pipeline" 20 ) 21 22 // ErrNoDarwin64Build when there is no build for darwin_amd64 23 var ErrNoDarwin64Build = errors.New("brew tap requires one darwin amd64 build") 24 25 // ErrTooManyDarwin64Builds when there are too many builds for darwin_amd64 26 var ErrTooManyDarwin64Builds = errors.New("brew tap requires at most one darwin amd64 build") 27 28 // Pipe for brew deployment 29 type Pipe struct{} 30 31 func (Pipe) String() string { 32 return "creating homebrew formula" 33 } 34 35 // Run the pipe 36 func (Pipe) Run(ctx *context.Context) error { 37 client, err := client.NewGitHub(ctx) 38 if err != nil { 39 return err 40 } 41 return doRun(ctx, client) 42 } 43 44 // Default sets the pipe defaults 45 func (Pipe) Default(ctx *context.Context) error { 46 if ctx.Config.Brew.Install == "" { 47 var installs []string 48 for _, build := range ctx.Config.Builds { 49 if !isBrewBuild(build) { 50 continue 51 } 52 installs = append( 53 installs, 54 fmt.Sprintf(`bin.install "%s"`, build.Binary), 55 ) 56 } 57 ctx.Config.Brew.Install = strings.Join(installs, "\n") 58 } 59 60 if ctx.Config.Brew.CommitAuthor.Name == "" { 61 ctx.Config.Brew.CommitAuthor.Name = "goreleaserbot" 62 } 63 if ctx.Config.Brew.CommitAuthor.Email == "" { 64 ctx.Config.Brew.CommitAuthor.Email = "goreleaser@carlosbecker.com" 65 } 66 if ctx.Config.Brew.Name == "" { 67 ctx.Config.Brew.Name = ctx.Config.ProjectName 68 } 69 return nil 70 } 71 72 func isBrewBuild(build config.Build) bool { 73 for _, ignore := range build.Ignore { 74 if ignore.Goos == "darwin" && ignore.Goarch == "amd64" { 75 return false 76 } 77 } 78 return contains(build.Goos, "darwin") && contains(build.Goarch, "amd64") 79 } 80 81 func contains(ss []string, s string) bool { 82 for _, zs := range ss { 83 if zs == s { 84 return true 85 } 86 } 87 return false 88 } 89 90 func doRun(ctx *context.Context, client client.Client) error { 91 if ctx.Config.Brew.GitHub.Name == "" { 92 return pipeline.Skip("brew section is not configured") 93 } 94 if ctx.Config.Archive.Format == "binary" { 95 return pipeline.Skip("archive format is binary") 96 } 97 98 var archives = ctx.Artifacts.Filter( 99 artifact.And( 100 artifact.ByGoos("darwin"), 101 artifact.ByGoarch("amd64"), 102 artifact.ByGoarm(""), 103 artifact.ByType(artifact.UploadableArchive), 104 ), 105 ).List() 106 if len(archives) == 0 { 107 return ErrNoDarwin64Build 108 } 109 if len(archives) > 1 { 110 return ErrTooManyDarwin64Builds 111 } 112 113 content, err := buildFormula(ctx, client, archives[0]) 114 if err != nil { 115 return err 116 } 117 118 var filename = ctx.Config.Brew.Name + ".rb" 119 var path = filepath.Join(ctx.Config.Dist, filename) 120 log.WithField("formula", path).Info("writing") 121 if err := ioutil.WriteFile(path, content.Bytes(), 0644); err != nil { 122 return err 123 } 124 125 if ctx.Config.Brew.SkipUpload { 126 return pipeline.Skip("brew.skip_upload is set") 127 } 128 if ctx.SkipPublish { 129 return pipeline.ErrSkipPublishEnabled 130 } 131 if ctx.Config.Release.Draft { 132 return pipeline.Skip("release is marked as draft") 133 } 134 135 path = filepath.Join(ctx.Config.Brew.Folder, filename) 136 log.WithField("formula", path). 137 WithField("repo", ctx.Config.Brew.GitHub.String()). 138 Info("pushing") 139 140 var msg = fmt.Sprintf("Brew formula update for %s version %s", ctx.Config.ProjectName, ctx.Git.CurrentTag) 141 return client.CreateFile(ctx, ctx.Config.Brew.CommitAuthor, ctx.Config.Brew.GitHub, content, path, msg) 142 } 143 144 func buildFormula(ctx *context.Context, client client.Client, artifact artifact.Artifact) (bytes.Buffer, error) { 145 data, err := dataFor(ctx, client, artifact) 146 if err != nil { 147 return bytes.Buffer{}, err 148 } 149 return doBuildFormula(data) 150 } 151 152 func doBuildFormula(data templateData) (out bytes.Buffer, err error) { 153 tmpl, err := template.New(data.Name).Parse(formulaTemplate) 154 if err != nil { 155 return out, err 156 } 157 err = tmpl.Execute(&out, data) 158 return 159 } 160 161 func dataFor(ctx *context.Context, client client.Client, artifact artifact.Artifact) (result templateData, err error) { 162 sum, err := checksum.SHA256(artifact.Path) 163 if err != nil { 164 return 165 } 166 var cfg = ctx.Config.Brew 167 return templateData{ 168 Name: formulaNameFor(ctx.Config.Brew.Name), 169 DownloadURL: ctx.Config.GitHubURLs.Download, 170 Desc: cfg.Description, 171 Homepage: cfg.Homepage, 172 Repo: ctx.Config.Release.GitHub, 173 Tag: ctx.Git.CurrentTag, 174 Version: ctx.Version, 175 Caveats: split(cfg.Caveats), 176 File: artifact.Name, 177 SHA256: sum, 178 Dependencies: cfg.Dependencies, 179 BuildDependencies: cfg.BuildDependencies, 180 Conflicts: cfg.Conflicts, 181 Plist: cfg.Plist, 182 Install: split(cfg.Install), 183 Tests: split(cfg.Test), 184 DownloadStrategy: cfg.DownloadStrategy, 185 }, nil 186 } 187 188 func split(s string) []string { 189 strings := strings.Split(strings.TrimSpace(s), "\n") 190 if len(strings) == 1 && strings[0] == "" { 191 return []string{} 192 } 193 return strings 194 } 195 196 func formulaNameFor(name string) string { 197 name = strings.Replace(name, "-", " ", -1) 198 name = strings.Replace(name, "_", " ", -1) 199 return strings.Replace(strings.Title(name), " ", "", -1) 200 }