github.com/goreleaser/goreleaser@v1.25.1/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 "fmt" 7 "os" 8 "path/filepath" 9 "reflect" 10 "strings" 11 12 "github.com/caarlos0/go-shellwords" 13 "github.com/caarlos0/log" 14 "github.com/goreleaser/goreleaser/internal/deprecate" 15 "github.com/goreleaser/goreleaser/internal/ids" 16 "github.com/goreleaser/goreleaser/internal/semerrgroup" 17 "github.com/goreleaser/goreleaser/internal/shell" 18 "github.com/goreleaser/goreleaser/internal/skips" 19 "github.com/goreleaser/goreleaser/internal/tmpl" 20 builders "github.com/goreleaser/goreleaser/pkg/build" 21 "github.com/goreleaser/goreleaser/pkg/config" 22 "github.com/goreleaser/goreleaser/pkg/context" 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 g := semerrgroup.New(ctx.Parallelism) 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 runPipeOnBuild(ctx, g, build) 45 } 46 return g.Wait() 47 } 48 49 // Default sets the pipe defaults. 50 func (Pipe) Default(ctx *context.Context) error { 51 if !reflect.DeepEqual(ctx.Config.SingleBuild, config.Build{}) { 52 deprecate.Notice(ctx, "build") 53 } 54 55 ids := ids.New("builds") 56 for i, build := range ctx.Config.Builds { 57 build, err := buildWithDefaults(ctx, build) 58 if err != nil { 59 return err 60 } 61 ctx.Config.Builds[i] = build 62 ids.Inc(ctx.Config.Builds[i].ID) 63 } 64 if len(ctx.Config.Builds) == 0 { 65 build, err := buildWithDefaults(ctx, ctx.Config.SingleBuild) 66 if err != nil { 67 return err 68 } 69 ctx.Config.Builds = []config.Build{build} 70 } 71 return ids.Validate() 72 } 73 74 func buildWithDefaults(ctx *context.Context, build config.Build) (config.Build, error) { 75 if build.Builder == "" { 76 build.Builder = "go" 77 } 78 if build.Binary == "" { 79 build.Binary = ctx.Config.ProjectName 80 } 81 if build.ID == "" { 82 build.ID = ctx.Config.ProjectName 83 } 84 for k, v := range build.Env { 85 build.Env[k] = os.ExpandEnv(v) 86 } 87 return builders.For(build.Builder).WithDefaults(build) 88 } 89 90 func runPipeOnBuild(ctx *context.Context, g semerrgroup.Group, build config.Build) { 91 for _, target := range filter(ctx, build.Targets) { 92 target := target 93 build := build 94 g.Go(func() error { 95 opts, err := buildOptionsForTarget(ctx, build, target) 96 if err != nil { 97 return err 98 } 99 100 if !skips.Any(ctx, skips.PreBuildHooks) { 101 if err := runHook(ctx, *opts, build.Env, build.Hooks.Pre); err != nil { 102 return fmt.Errorf("pre hook failed: %w", err) 103 } 104 } 105 if err := doBuild(ctx, build, *opts); err != nil { 106 return err 107 } 108 if !skips.Any(ctx, skips.PostBuildHooks) { 109 if err := runHook(ctx, *opts, build.Env, build.Hooks.Post); err != nil { 110 return fmt.Errorf("post hook failed: %w", err) 111 } 112 } 113 return nil 114 }) 115 } 116 } 117 118 func runHook(ctx *context.Context, opts builders.Options, buildEnv []string, hooks config.Hooks) error { 119 if len(hooks) == 0 { 120 return nil 121 } 122 123 for _, hook := range hooks { 124 var env []string 125 126 env = append(env, ctx.Env.Strings()...) 127 for _, rawEnv := range append(buildEnv, 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 := shell.Run(ctx, dir, cmd, env, hook.Output); 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.Builder).Build(ctx, build, opts) 163 } 164 165 func buildOptionsForTarget(ctx *context.Context, build config.Build, target string) (*builders.Options, error) { 166 ext := extFor(target, build.BuildDetails) 167 parts := strings.Split(target, "_") 168 if len(parts) < 2 { 169 return nil, fmt.Errorf("%s is not a valid build target", target) 170 } 171 172 goos := parts[0] 173 goarch := parts[1] 174 175 var gomips string 176 var goarm string 177 var goamd64 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 if strings.HasPrefix(goarch, "amd64") && len(parts) > 2 { 185 goamd64 = parts[2] 186 } 187 188 buildOpts := builders.Options{ 189 Target: target, 190 Ext: ext, 191 Goos: goos, 192 Goarch: goarch, 193 Goarm: goarm, 194 Gomips: gomips, 195 Goamd64: goamd64, 196 } 197 198 bin, err := tmpl.New(ctx).WithBuildOptions(buildOpts).Apply(build.Binary) 199 if err != nil { 200 return nil, err 201 } 202 203 name := bin + ext 204 dir := fmt.Sprintf("%s_%s", build.ID, target) 205 if build.NoUniqueDistDir { 206 dir = "" 207 } 208 relpath := filepath.Join(ctx.Config.Dist, dir, name) 209 path, err := filepath.Abs(relpath) 210 if err != nil { 211 return nil, err 212 } 213 buildOpts.Path = path 214 buildOpts.Name = name 215 216 log.WithField("binary", relpath).Info("building") 217 return &buildOpts, nil 218 } 219 220 func extFor(target string, build config.BuildDetails) string { 221 // Configure the extensions for shared and static libraries - by default .so and .a respectively - 222 // with overrides for Windows (.dll for shared and .lib for static) and .dylib for macOS. 223 switch build.Buildmode { 224 case "c-shared": 225 if strings.Contains(target, "darwin") { 226 return ".dylib" 227 } 228 if strings.Contains(target, "windows") { 229 return ".dll" 230 } 231 return ".so" 232 case "c-archive": 233 if strings.Contains(target, "windows") { 234 return ".lib" 235 } 236 return ".a" 237 } 238 239 if target == "js_wasm" { 240 return ".wasm" 241 } 242 243 if strings.Contains(target, "windows") { 244 return ".exe" 245 } 246 247 return "" 248 }