1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-23 21:19:17 +02:00

285 lines
6.9 KiB
Go
Raw Normal View History

2018-01-21 14:31:08 -02:00
package golang
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
2018-01-26 19:26:28 -02:00
"os/exec"
"path/filepath"
"strconv"
2018-01-21 14:31:08 -02:00
"strings"
"time"
2018-01-21 14:31:08 -02:00
2018-01-26 19:26:28 -02:00
"github.com/apex/log"
2018-01-21 14:31:08 -02:00
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/builders/buildtarget"
"github.com/goreleaser/goreleaser/internal/tmpl"
api "github.com/goreleaser/goreleaser/pkg/build"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
2018-01-21 14:31:08 -02:00
)
// Default builder instance.
2018-11-07 22:04:49 -02:00
// nolint: gochecknoglobals
2018-01-21 14:31:08 -02:00
var Default = &Builder{}
2018-11-07 22:04:49 -02:00
// nolint: gochecknoinits
2018-01-21 14:31:08 -02:00
func init() {
2018-01-22 01:10:17 -02:00
api.Register("go", Default)
2018-01-21 14:31:08 -02:00
}
// Builder is golang builder.
2018-01-22 01:10:17 -02:00
type Builder struct{}
2018-01-21 14:31:08 -02:00
// WithDefaults sets the defaults for a golang build and returns it.
func (*Builder) WithDefaults(build config.Build) (config.Build, error) {
if build.GoBinary == "" {
build.GoBinary = "go"
}
if build.Dir == "" {
build.Dir = "."
}
if build.Main == "" {
build.Main = "."
}
if len(build.Ldflags) == 0 {
build.Ldflags = []string{"-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser"}
}
2018-01-22 01:10:17 -02:00
if len(build.Targets) == 0 {
if len(build.Goos) == 0 {
build.Goos = []string{"linux", "darwin"}
}
if len(build.Goarch) == 0 {
build.Goarch = []string{"amd64", "arm64", "386"}
}
if len(build.Goarm) == 0 {
build.Goarm = []string{"6"}
}
if len(build.Gomips) == 0 {
build.Gomips = []string{"hardfloat"}
}
targets, err := buildtarget.List(build)
build.Targets = targets
if err != nil {
return build, err
}
}
return build, nil
}
// Build builds a golang build.
2018-01-22 01:10:17 -02:00
func (*Builder) Build(ctx *context.Context, build config.Build, options api.Options) error {
tpl := tmpl.New(ctx)
if build.Main != "" {
m, err := tpl.Apply(build.Main)
if err != nil {
return err
}
build.Main = m
}
if build.UnproxiedMain != "" {
m, err := tpl.Apply(build.UnproxiedMain)
if err != nil {
return err
}
build.UnproxiedMain = m
}
if err := checkMain(build); err != nil {
return err
2018-01-21 14:31:08 -02:00
}
artifact := &artifact.Artifact{
Type: artifact.Binary,
Path: options.Path,
Name: options.Name,
Goos: options.Goos,
Goarch: options.Goarch,
Goarm: options.Goarm,
Gomips: options.Gomips,
Extra: map[string]interface{}{
artifact.ExtraBinary: strings.TrimSuffix(filepath.Base(options.Path), options.Ext),
artifact.ExtraExt: options.Ext,
artifact.ExtraID: build.ID,
},
}
env := append(ctx.Env.Strings(), build.Env...)
env = append(
env,
"GOOS="+options.Goos,
"GOARCH="+options.Goarch,
"GOARM="+options.Goarm,
"GOMIPS="+options.Gomips,
"GOMIPS64="+options.Gomips,
)
cmd, err := buildGoBuildLine(ctx, build, options, artifact, env)
2018-01-21 14:31:08 -02:00
if err != nil {
return err
}
if err := run(ctx, cmd, env, build.Dir); err != nil {
return fmt.Errorf("failed to build for %s: %w", options.Target, err)
2018-01-21 14:31:08 -02:00
}
if build.ModTimestamp != "" {
modTimestamp, err := tmpl.New(ctx).WithEnvS(env).WithArtifact(artifact, map[string]string{}).Apply(build.ModTimestamp)
if err != nil {
return err
}
modUnix, err := strconv.ParseInt(modTimestamp, 10, 64)
if err != nil {
return err
}
modTime := time.Unix(modUnix, 0)
err = os.Chtimes(options.Path, modTime, modTime)
if err != nil {
return fmt.Errorf("failed to change times for %s: %w", options.Target, err)
}
}
ctx.Artifacts.Add(artifact)
2018-01-21 14:31:08 -02:00
return nil
}
func buildGoBuildLine(ctx *context.Context, build config.Build, options api.Options, artifact *artifact.Artifact, env []string) ([]string, error) {
cmd := []string{build.GoBinary, "build"}
flags, err := processFlags(ctx, artifact, env, build.Flags, "")
if err != nil {
return cmd, err
}
cmd = append(cmd, flags...)
asmflags, err := processFlags(ctx, artifact, env, build.Asmflags, "-asmflags=")
if err != nil {
return cmd, err
}
cmd = append(cmd, asmflags...)
gcflags, err := processFlags(ctx, artifact, env, build.Gcflags, "-gcflags=")
if err != nil {
return cmd, err
}
cmd = append(cmd, gcflags...)
// tags is not a repeatable flag
if len(build.Tags) > 0 {
tags, err := processFlags(ctx, artifact, env, build.Tags, "")
if err != nil {
return cmd, err
}
cmd = append(cmd, "-tags="+strings.Join(tags, ","))
}
// ldflags is not a repeatable flag
if len(build.Ldflags) > 0 {
// flag prefix is skipped because ldflags need to output a single string
ldflags, err := processFlags(ctx, artifact, env, build.Ldflags, "")
if err != nil {
return cmd, err
}
// ldflags need to be single string in order to apply correctly
cmd = append(cmd, "-ldflags="+strings.Join(ldflags, " "))
}
cmd = append(cmd, "-o", options.Path, build.Main)
return cmd, nil
}
func processFlags(ctx *context.Context, a *artifact.Artifact, env, flags []string, flagPrefix string) ([]string, error) {
processed := make([]string, 0, len(flags))
for _, rawFlag := range flags {
flag, err := processFlag(ctx, a, env, rawFlag)
if err != nil {
return nil, err
}
processed = append(processed, flagPrefix+flag)
}
return processed, nil
}
func processFlag(ctx *context.Context, a *artifact.Artifact, env []string, rawFlag string) (string, error) {
return tmpl.New(ctx).WithEnvS(env).WithArtifact(a, map[string]string{}).Apply(rawFlag)
}
func run(ctx *context.Context, command, env []string, dir string) error {
2018-01-26 19:26:28 -02:00
/* #nosec */
cmd := exec.CommandContext(ctx, command[0], command[1:]...)
log := log.WithField("env", env).WithField("cmd", command)
cmd.Env = env
cmd.Dir = dir
log.Debug("running")
2018-01-26 19:26:28 -02:00
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("%w: %s", err, string(out))
2018-01-26 19:26:28 -02:00
}
return nil
}
2018-07-04 00:42:24 -07:00
func checkMain(build config.Build) error {
main := build.Main
if build.UnproxiedMain != "" {
main = build.UnproxiedMain
}
dir := build.Dir
if build.UnproxiedDir != "" {
dir = build.UnproxiedDir
}
2018-01-21 14:31:08 -02:00
if main == "" {
main = "."
}
if dir != "" {
main = filepath.Join(dir, main)
}
2018-01-21 14:31:08 -02:00
stat, ferr := os.Stat(main)
2018-02-01 13:11:46 +01:00
if ferr != nil {
return fmt.Errorf("couldn't find main file: %w", ferr)
2018-01-21 14:31:08 -02:00
}
if stat.IsDir() {
packs, err := parser.ParseDir(token.NewFileSet(), main, nil, 0)
2018-01-21 14:31:08 -02:00
if err != nil {
return fmt.Errorf("failed to parse dir: %s: %w", main, err)
2018-01-21 14:31:08 -02:00
}
for _, pack := range packs {
for _, file := range pack.Files {
if hasMain(file) {
return nil
}
}
}
return errNoMain{build.Binary}
2018-01-21 14:31:08 -02:00
}
2018-01-26 21:54:08 -02:00
file, err := parser.ParseFile(token.NewFileSet(), main, nil, 0)
2018-01-21 14:31:08 -02:00
if err != nil {
return fmt.Errorf("failed to parse file: %s: %w", main, err)
2018-01-21 14:31:08 -02:00
}
if hasMain(file) {
return nil
}
return errNoMain{build.Binary}
}
type errNoMain struct {
bin string
}
func (e errNoMain) Error() string {
return fmt.Sprintf("build for %s does not contain a main function\nLearn more at https://goreleaser.com/errors/no-main\n", e.bin)
2018-01-21 14:31:08 -02:00
}
func hasMain(file *ast.File) bool {
for _, decl := range file.Decls {
fn, isFn := decl.(*ast.FuncDecl)
if !isFn {
continue
}
if fn.Name.Name == "main" && fn.Recv == nil {
return true
}
}
return false
}