github.com/amane3/goreleaser@v0.182.0/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/amane3/goreleaser/internal/ids" 15 "github.com/amane3/goreleaser/internal/logext" 16 "github.com/amane3/goreleaser/internal/semerrgroup" 17 "github.com/amane3/goreleaser/internal/tmpl" 18 builders "github.com/amane3/goreleaser/pkg/build" 19 "github.com/amane3/goreleaser/pkg/config" 20 "github.com/amane3/goreleaser/pkg/context" 21 "github.com/apex/log" 22 "github.com/mattn/go-shellwords" 23 24 // langs to init. 25 _ "github.com/amane3/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 runPipeOnBuild(ctx *context.Context, build config.Build) error { 88 var g = semerrgroup.New(ctx.Parallelism) 89 for _, target := range build.Targets { 90 target := target 91 build := build 92 g.Go(func() error { 93 opts, err := buildOptionsForTarget(ctx, build, target) 94 if err != nil { 95 return err 96 } 97 98 if err := runHook(ctx, *opts, build.Env, build.Hooks.Pre); err != nil { 99 return fmt.Errorf("pre hook failed: %w", err) 100 } 101 if err := doBuild(ctx, build, *opts); err != nil { 102 return err 103 } 104 if !ctx.SkipPostBuildHooks { 105 if err := runHook(ctx, *opts, build.Env, build.Hooks.Post); err != nil { 106 return fmt.Errorf("post hook failed: %w", err) 107 } 108 } 109 return nil 110 }) 111 } 112 113 return g.Wait() 114 } 115 116 func runHook(ctx *context.Context, opts builders.Options, buildEnv []string, hooks config.BuildHooks) error { 117 if len(hooks) == 0 { 118 return nil 119 } 120 121 for _, hook := range hooks { 122 var env []string 123 124 env = append(env, ctx.Env.Strings()...) 125 env = append(env, buildEnv...) 126 127 for _, rawEnv := range hook.Env { 128 e, err := tmpl.New(ctx).WithBuildOptions(opts).Apply(rawEnv) 129 if err != nil { 130 return err 131 } 132 env = append(env, e) 133 } 134 135 dir, err := tmpl.New(ctx).WithBuildOptions(opts).Apply(hook.Dir) 136 if err != nil { 137 return err 138 } 139 140 sh, err := tmpl.New(ctx).WithBuildOptions(opts). 141 WithEnvS(env). 142 Apply(hook.Cmd) 143 if err != nil { 144 return err 145 } 146 147 log.WithField("hook", sh).Info("running hook") 148 cmd, err := shellwords.Parse(sh) 149 if err != nil { 150 return err 151 } 152 153 if err := run(ctx, dir, cmd, env); err != nil { 154 return err 155 } 156 } 157 158 return nil 159 } 160 161 func doBuild(ctx *context.Context, build config.Build, opts builders.Options) error { 162 return builders.For(build.Lang).Build(ctx, build, opts) 163 } 164 165 func buildOptionsForTarget(ctx *context.Context, build config.Build, target string) (*builders.Options, error) { 166 var ext = extFor(target, build.Flags) 167 168 binary, err := tmpl.New(ctx).Apply(build.Binary) 169 if err != nil { 170 return nil, err 171 } 172 173 build.Binary = binary 174 var name = build.Binary + ext 175 path, err := filepath.Abs( 176 filepath.Join( 177 ctx.Config.Dist, 178 fmt.Sprintf("%s_%s", build.ID, target), 179 name, 180 ), 181 ) 182 if err != nil { 183 return nil, err 184 } 185 186 var goos string 187 var goarch string 188 189 if strings.Contains(target, "_") { 190 goos = strings.Split(target, "_")[0] 191 goarch = strings.Split(target, "_")[1] 192 } 193 194 log.WithField("binary", path).Info("building") 195 return &builders.Options{ 196 Target: target, 197 Name: name, 198 Path: path, 199 Ext: ext, 200 Os: goos, 201 Arch: goarch, 202 }, nil 203 } 204 205 func extFor(target string, flags config.FlagArray) string { 206 if strings.Contains(target, "windows") { 207 for _, s := range flags { 208 if s == "-buildmode=c-shared" { 209 return ".dll" 210 } 211 if s == "-buildmode=c-archive" { 212 return ".lib" 213 } 214 } 215 return ".exe" 216 } 217 if target == "js_wasm" { 218 return ".wasm" 219 } 220 return "" 221 } 222 223 func run(ctx *context.Context, dir string, command, env []string) error { 224 /* #nosec */ 225 var cmd = exec.CommandContext(ctx, command[0], command[1:]...) 226 var entry = log.WithField("cmd", command) 227 cmd.Env = env 228 var b bytes.Buffer 229 cmd.Stderr = io.MultiWriter(logext.NewErrWriter(entry), &b) 230 cmd.Stdout = io.MultiWriter(logext.NewWriter(entry), &b) 231 if dir != "" { 232 cmd.Dir = dir 233 } 234 entry.WithField("env", env).Debug("running") 235 if err := cmd.Run(); err != nil { 236 entry.WithError(err).Debug("failed") 237 return fmt.Errorf("%q: %w", b.String(), err) 238 } 239 return nil 240 }