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