github.com/joselitofilho/goreleaser@v0.155.1-0.20210123221854-e4891856c593/internal/pipe/build/build.go (about) 1 // Package build provides a pipe that can build binaries for several 2 // languages. 3 package build 4 5 import ( 6 "bytes" 7 "fmt" 8 "io" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "strings" 13 14 "github.com/apex/log" 15 "github.com/goreleaser/goreleaser/internal/ids" 16 "github.com/goreleaser/goreleaser/internal/logext" 17 "github.com/goreleaser/goreleaser/internal/semerrgroup" 18 "github.com/goreleaser/goreleaser/internal/tmpl" 19 builders "github.com/goreleaser/goreleaser/pkg/build" 20 "github.com/goreleaser/goreleaser/pkg/config" 21 "github.com/goreleaser/goreleaser/pkg/context" 22 "github.com/mattn/go-shellwords" 23 24 // langs to init. 25 _ "github.com/goreleaser/goreleaser/internal/builders/golang" 26 ) 27 28 // Pipe for build. 29 type Pipe struct{} 30 31 func (Pipe) String() string { 32 return "building binaries" 33 } 34 35 // Run the pipe. 36 func (Pipe) Run(ctx *context.Context) error { 37 for _, build := range ctx.Config.Builds { 38 if build.Skip { 39 log.WithField("id", build.ID).Info("skip is set") 40 continue 41 } 42 log.WithField("build", build).Debug("building") 43 if err := runPipeOnBuild(ctx, build); err != nil { 44 return err 45 } 46 } 47 return nil 48 } 49 50 // Default sets the pipe defaults. 51 func (Pipe) Default(ctx *context.Context) error { 52 var ids = ids.New("builds") 53 for i, build := range ctx.Config.Builds { 54 build, err := buildWithDefaults(ctx, build) 55 if err != nil { 56 return err 57 } 58 ctx.Config.Builds[i] = build 59 ids.Inc(ctx.Config.Builds[i].ID) 60 } 61 if len(ctx.Config.Builds) == 0 { 62 build, err := buildWithDefaults(ctx, ctx.Config.SingleBuild) 63 if err != nil { 64 return err 65 } 66 ctx.Config.Builds = []config.Build{build} 67 } 68 return ids.Validate() 69 } 70 71 func buildWithDefaults(ctx *context.Context, build config.Build) (config.Build, error) { 72 if build.Lang == "" { 73 build.Lang = "go" 74 } 75 if build.Binary == "" { 76 build.Binary = ctx.Config.ProjectName 77 } 78 if build.ID == "" { 79 build.ID = ctx.Config.ProjectName 80 } 81 for k, v := range build.Env { 82 build.Env[k] = os.ExpandEnv(v) 83 } 84 return builders.For(build.Lang).WithDefaults(build) 85 } 86 87 func shouldBuildID(ctx *context.Context, buildID string) bool { 88 buildIDs := ctx.BuildIDs 89 if len(buildIDs) > 0 { 90 for _, ID := range buildIDs { 91 if buildID == strings.TrimSpace(ID) { 92 return true 93 } 94 } 95 return false 96 } 97 return true 98 } 99 100 func runPipeOnBuild(ctx *context.Context, build config.Build) error { 101 var g = semerrgroup.New(ctx.Parallelism) 102 for _, target := range build.Targets { 103 target := target 104 build := build 105 g.Go(func() error { 106 opts, err := buildOptionsForTarget(ctx, build, target) 107 if err != nil { 108 return err 109 } 110 111 if !shouldBuildID(ctx, build.ID) { 112 return nil 113 } 114 115 if err := runHook(ctx, *opts, build.Env, build.Hooks.Pre); err != nil { 116 return fmt.Errorf("pre hook failed: %w", err) 117 } 118 if err := doBuild(ctx, build, *opts); err != nil { 119 return err 120 } 121 if !ctx.SkipPostBuildHooks { 122 if err := runHook(ctx, *opts, build.Env, build.Hooks.Post); err != nil { 123 return fmt.Errorf("post hook failed: %w", err) 124 } 125 } 126 return nil 127 }) 128 } 129 130 return g.Wait() 131 } 132 133 func runHook(ctx *context.Context, opts builders.Options, buildEnv []string, hooks config.BuildHooks) error { 134 if len(hooks) == 0 { 135 return nil 136 } 137 138 for _, hook := range hooks { 139 var env []string 140 141 env = append(env, ctx.Env.Strings()...) 142 env = append(env, buildEnv...) 143 144 for _, rawEnv := range hook.Env { 145 e, err := tmpl.New(ctx).WithBuildOptions(opts).Apply(rawEnv) 146 if err != nil { 147 return err 148 } 149 env = append(env, e) 150 } 151 152 dir, err := tmpl.New(ctx).WithBuildOptions(opts).Apply(hook.Dir) 153 if err != nil { 154 return err 155 } 156 157 sh, err := tmpl.New(ctx).WithBuildOptions(opts). 158 WithEnvS(env). 159 Apply(hook.Cmd) 160 if err != nil { 161 return err 162 } 163 164 log.WithField("hook", sh).Info("running hook") 165 cmd, err := shellwords.Parse(sh) 166 if err != nil { 167 return err 168 } 169 170 if err := run(ctx, dir, cmd, env); err != nil { 171 return err 172 } 173 } 174 175 return nil 176 } 177 178 func doBuild(ctx *context.Context, build config.Build, opts builders.Options) error { 179 return builders.For(build.Lang).Build(ctx, build, opts) 180 } 181 182 func buildOptionsForTarget(ctx *context.Context, build config.Build, target string) (*builders.Options, error) { 183 var ext = extFor(target, build.Flags) 184 var goos string 185 var goarch string 186 187 if strings.Contains(target, "_") { 188 goos = strings.Split(target, "_")[0] 189 goarch = strings.Split(target, "_")[1] 190 } 191 192 buildOpts := builders.Options{ 193 Target: target, 194 Ext: ext, 195 Os: goos, 196 Arch: goarch, 197 } 198 199 binary, err := tmpl.New(ctx).WithBuildOptions(buildOpts).Apply(build.Binary) 200 if err != nil { 201 return nil, err 202 } 203 204 build.Binary = binary 205 var name = build.Binary + ext 206 path, err := filepath.Abs( 207 filepath.Join( 208 ctx.Config.Dist, 209 fmt.Sprintf("%s_%s", build.ID, target), 210 name, 211 ), 212 ) 213 if err != nil { 214 return nil, err 215 } 216 217 log.WithField("binary", path).Info("building") 218 buildOpts.Name = name 219 buildOpts.Path = path 220 return &buildOpts, nil 221 } 222 223 func extFor(target string, flags config.FlagArray) string { 224 if strings.Contains(target, "windows") { 225 for _, s := range flags { 226 if s == "-buildmode=c-shared" { 227 return ".dll" 228 } 229 if s == "-buildmode=c-archive" { 230 return ".lib" 231 } 232 } 233 return ".exe" 234 } 235 if target == "js_wasm" { 236 return ".wasm" 237 } 238 return "" 239 } 240 241 func run(ctx *context.Context, dir string, command, env []string) error { 242 /* #nosec */ 243 var cmd = exec.CommandContext(ctx, command[0], command[1:]...) 244 var entry = log.WithField("cmd", command) 245 cmd.Env = env 246 var b bytes.Buffer 247 cmd.Stderr = io.MultiWriter(logext.NewErrWriter(entry), &b) 248 cmd.Stdout = io.MultiWriter(logext.NewWriter(entry), &b) 249 if dir != "" { 250 cmd.Dir = dir 251 } 252 entry.WithField("env", env).Debug("running") 253 if err := cmd.Run(); err != nil { 254 entry.WithError(err).Debug("failed") 255 return fmt.Errorf("%q: %w", b.String(), err) 256 } 257 return nil 258 }