github.com/jonathanlloyd/goreleaser@v0.91.1/main.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/alecthomas/kingpin"
    11  	"github.com/apex/log"
    12  	"github.com/apex/log/handlers/cli"
    13  	"github.com/caarlos0/ctrlc"
    14  	"github.com/fatih/color"
    15  	"github.com/goreleaser/goreleaser/internal/pipe"
    16  	"github.com/goreleaser/goreleaser/internal/pipeline"
    17  	"github.com/goreleaser/goreleaser/pkg/config"
    18  	"github.com/goreleaser/goreleaser/pkg/context"
    19  )
    20  
    21  var (
    22  	version = "dev"
    23  	commit  = "none"
    24  	date    = "unknown"
    25  )
    26  
    27  type releaseOptions struct {
    28  	Config       string
    29  	ReleaseNotes string
    30  	Snapshot     bool
    31  	SkipPublish  bool
    32  	SkipSign     bool
    33  	SkipValidate bool
    34  	RmDist       bool
    35  	Debug        bool
    36  	Parallelism  int
    37  	Timeout      time.Duration
    38  }
    39  
    40  func init() {
    41  	log.SetHandler(cli.Default)
    42  }
    43  
    44  func main() {
    45  	fmt.Println()
    46  	defer fmt.Println()
    47  
    48  	var app = kingpin.New("goreleaser", "Deliver Go binaries as fast and easily as possible")
    49  	var initCmd = app.Command("init", "Generates a .goreleaser.yml file").Alias("i")
    50  	var releaseCmd = app.Command("release", "Releases the current project").Alias("r").Default()
    51  	var config = releaseCmd.Flag("config", "Load configuration from file").Short('c').Short('f').PlaceHolder(".goreleaser.yml").String()
    52  	var releaseNotes = releaseCmd.Flag("release-notes", "Load custom release notes from a markdown file").PlaceHolder("notes.md").String()
    53  	var snapshot = releaseCmd.Flag("snapshot", "Generate an unversioned snapshot release, skipping all validations and without publishing any artifacts").Bool()
    54  	var skipPublish = releaseCmd.Flag("skip-publish", "Generates all artifacts but does not publish them anywhere").Bool()
    55  	var skipSign = releaseCmd.Flag("skip-sign", "Skips signing the artifacts").Bool()
    56  	var skipValidate = releaseCmd.Flag("skip-validate", "Skips all git sanity checks").Bool()
    57  	var rmDist = releaseCmd.Flag("rm-dist", "Remove the dist folder before building").Bool()
    58  	var parallelism = releaseCmd.Flag("parallelism", "Amount of slow tasks to do in concurrently").Short('p').Default("4").Int() // TODO: use runtime.NumCPU here?
    59  	var debug = releaseCmd.Flag("debug", "Enable debug mode").Bool()
    60  	var timeout = releaseCmd.Flag("timeout", "Timeout to the entire release process").Default("30m").Duration()
    61  
    62  	app.Version(fmt.Sprintf("%v, commit %v, built at %v", version, commit, date))
    63  	app.VersionFlag.Short('v')
    64  	app.HelpFlag.Short('h')
    65  
    66  	switch kingpin.MustParse(app.Parse(os.Args[1:])) {
    67  	case initCmd.FullCommand():
    68  		var filename = ".goreleaser.yml"
    69  		if err := initProject(filename); err != nil {
    70  			log.WithError(err).Error("failed to init project")
    71  			terminate(1)
    72  			return
    73  		}
    74  		log.WithField("file", filename).Info("config created; please edit accordingly to your needs")
    75  	case releaseCmd.FullCommand():
    76  		start := time.Now()
    77  		log.Infof(color.New(color.Bold).Sprintf("releasing using goreleaser %s...", version))
    78  		var options = releaseOptions{
    79  			Config:       *config,
    80  			ReleaseNotes: *releaseNotes,
    81  			Snapshot:     *snapshot,
    82  			SkipPublish:  *skipPublish,
    83  			SkipValidate: *skipValidate,
    84  			SkipSign:     *skipSign,
    85  			RmDist:       *rmDist,
    86  			Parallelism:  *parallelism,
    87  			Debug:        *debug,
    88  			Timeout:      *timeout,
    89  		}
    90  		if err := releaseProject(options); err != nil {
    91  			log.WithError(err).Errorf(color.New(color.Bold).Sprintf("release failed after %0.2fs", time.Since(start).Seconds()))
    92  			terminate(1)
    93  			return
    94  		}
    95  		log.Infof(color.New(color.Bold).Sprintf("release succeeded after %0.2fs", time.Since(start).Seconds()))
    96  	}
    97  }
    98  
    99  func terminate(status int) {
   100  	os.Exit(status)
   101  }
   102  
   103  func releaseProject(options releaseOptions) error {
   104  	if options.Debug {
   105  		log.SetLevel(log.DebugLevel)
   106  	}
   107  	cfg, err := loadConfig(options.Config)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	ctx, cancel := context.NewWithTimeout(cfg, options.Timeout)
   112  	defer cancel()
   113  	ctx.Parallelism = options.Parallelism
   114  	ctx.Debug = options.Debug
   115  	log.Debugf("parallelism: %v", ctx.Parallelism)
   116  	if options.ReleaseNotes != "" {
   117  		bts, err := ioutil.ReadFile(options.ReleaseNotes)
   118  		if err != nil {
   119  			return err
   120  		}
   121  		log.WithField("file", options.ReleaseNotes).Info("loaded custom release notes")
   122  		log.WithField("file", options.ReleaseNotes).Debugf("custom release notes: \n%s", string(bts))
   123  		ctx.ReleaseNotes = string(bts)
   124  	}
   125  	ctx.Snapshot = options.Snapshot
   126  	ctx.SkipPublish = ctx.Snapshot || options.SkipPublish
   127  	ctx.SkipValidate = ctx.Snapshot || options.SkipValidate
   128  	ctx.SkipSign = options.SkipSign
   129  	ctx.RmDist = options.RmDist
   130  	return doRelease(ctx)
   131  }
   132  
   133  var bold = color.New(color.Bold)
   134  
   135  func doRelease(ctx *context.Context) error {
   136  	defer func() { cli.Default.Padding = 3 }()
   137  	var release = func() error {
   138  		for _, pipe := range pipeline.Pipeline {
   139  			cli.Default.Padding = 3
   140  			log.Infof(bold.Sprint(strings.ToUpper(pipe.String())))
   141  			cli.Default.Padding = 6
   142  			if err := handle(pipe.Run(ctx)); err != nil {
   143  				return err
   144  			}
   145  		}
   146  		return nil
   147  	}
   148  	return ctrlc.Default.Run(ctx, release)
   149  }
   150  
   151  func handle(err error) error {
   152  	if err == nil {
   153  		return nil
   154  	}
   155  	if pipe.IsSkip(err) {
   156  		log.WithField("reason", err.Error()).Warn("skipped")
   157  		return nil
   158  	}
   159  	return err
   160  }
   161  
   162  // InitProject creates an example goreleaser.yml in the current directory
   163  func initProject(filename string) error {
   164  	if _, err := os.Stat(filename); !os.IsNotExist(err) {
   165  		if err != nil {
   166  			return err
   167  		}
   168  		return fmt.Errorf("%s already exists", filename)
   169  	}
   170  	log.Infof(color.New(color.Bold).Sprintf("Generating %s file", filename))
   171  	return ioutil.WriteFile(filename, []byte(exampleConfig), 0644)
   172  }
   173  
   174  func loadConfig(path string) (config.Project, error) {
   175  	if path != "" {
   176  		return config.Load(path)
   177  	}
   178  	for _, f := range [4]string{
   179  		".goreleaser.yml",
   180  		".goreleaser.yaml",
   181  		"goreleaser.yml",
   182  		"goreleaser.yaml",
   183  	} {
   184  		proj, err := config.Load(f)
   185  		if err != nil && os.IsNotExist(err) {
   186  			continue
   187  		}
   188  		return proj, err
   189  	}
   190  	// the user didn't specified a config file and the known files
   191  	// doest not exist, so, return an empty config and a nil err.
   192  	log.Warn("could not load config, using defaults")
   193  	return config.Project{}, nil
   194  }
   195  
   196  var exampleConfig = `# This is an example goreleaser.yaml file with some sane defaults.
   197  # Make sure to check the documentation at http://goreleaser.com
   198  before:
   199    hooks:
   200      # you may remove this if you don't use vgo
   201      - go mod download
   202      # you may remove this if you don't need go generate
   203      - go generate ./...
   204  builds:
   205  - env:
   206    - CGO_ENABLED=0
   207  archive:
   208    replacements:
   209      darwin: Darwin
   210      linux: Linux
   211      windows: Windows
   212      386: i386
   213      amd64: x86_64
   214  checksum:
   215    name_template: 'checksums.txt'
   216  snapshot:
   217    name_template: "{{ .Tag }}-next"
   218  changelog:
   219    sort: asc
   220    filters:
   221      exclude:
   222      - '^docs:'
   223      - '^test:'
   224  `