github.com/droot/goreleaser@v0.66.2-0.20180420030140-c2db5fb17157/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  
    16  	"github.com/goreleaser/goreleaser/config"
    17  	"github.com/goreleaser/goreleaser/context"
    18  	"github.com/goreleaser/goreleaser/pipeline"
    19  	"github.com/goreleaser/goreleaser/pipeline/archive"
    20  	"github.com/goreleaser/goreleaser/pipeline/artifactory"
    21  	"github.com/goreleaser/goreleaser/pipeline/before"
    22  	"github.com/goreleaser/goreleaser/pipeline/brew"
    23  	"github.com/goreleaser/goreleaser/pipeline/build"
    24  	"github.com/goreleaser/goreleaser/pipeline/changelog"
    25  	"github.com/goreleaser/goreleaser/pipeline/checksums"
    26  	"github.com/goreleaser/goreleaser/pipeline/defaults"
    27  	"github.com/goreleaser/goreleaser/pipeline/dist"
    28  	"github.com/goreleaser/goreleaser/pipeline/docker"
    29  	"github.com/goreleaser/goreleaser/pipeline/effectiveconfig"
    30  	"github.com/goreleaser/goreleaser/pipeline/env"
    31  	"github.com/goreleaser/goreleaser/pipeline/git"
    32  	"github.com/goreleaser/goreleaser/pipeline/nfpm"
    33  	"github.com/goreleaser/goreleaser/pipeline/release"
    34  	"github.com/goreleaser/goreleaser/pipeline/scoop"
    35  	"github.com/goreleaser/goreleaser/pipeline/sign"
    36  	"github.com/goreleaser/goreleaser/pipeline/snapcraft"
    37  )
    38  
    39  var (
    40  	version = "dev"
    41  	commit  = "none"
    42  	date    = "unknown"
    43  )
    44  
    45  var pipes = []Piper{
    46  	defaults.Pipe{},        // load default configs
    47  	before.Pipe{},          // run global hooks before build
    48  	dist.Pipe{},            // ensure ./dist is clean
    49  	git.Pipe{},             // get and validate git repo state
    50  	effectiveconfig.Pipe{}, // writes the actual config (with defaults et al set) to dist
    51  	changelog.Pipe{},       // builds the release changelog
    52  	env.Pipe{},             // load and validate environment variables
    53  	build.Pipe{},           // build
    54  	archive.Pipe{},         // archive in tar.gz, zip or binary (which does no archiving at all)
    55  	nfpm.Pipe{},            // archive via fpm (deb, rpm) using "native" go impl
    56  	snapcraft.Pipe{},       // archive via snapcraft (snap)
    57  	checksums.Pipe{},       // checksums of the files
    58  	sign.Pipe{},            // sign artifacts
    59  	docker.Pipe{},          // create and push docker images
    60  	artifactory.Pipe{},     // push to artifactory
    61  	release.Pipe{},         // release to github
    62  	brew.Pipe{},            // push to brew tap
    63  	scoop.Pipe{},           // push to scoop bucket
    64  }
    65  
    66  // Piper defines a pipe, which can be part of a pipeline (a serie of pipes).
    67  type Piper interface {
    68  	fmt.Stringer
    69  
    70  	// Run the pipe
    71  	Run(ctx *context.Context) error
    72  }
    73  
    74  type releaseOptions struct {
    75  	Config       string
    76  	ReleaseNotes string
    77  	Snapshot     bool
    78  	SkipPublish  bool
    79  	SkipValidate bool
    80  	RmDist       bool
    81  	Debug        bool
    82  	Parallelism  int
    83  	Timeout      time.Duration
    84  }
    85  
    86  func init() {
    87  	log.SetHandler(cli.Default)
    88  }
    89  
    90  func main() {
    91  	fmt.Println()
    92  	defer fmt.Println()
    93  
    94  	var app = kingpin.New("goreleaser", "Deliver Go binaries as fast and easily as possible")
    95  	var initCmd = app.Command("init", "Generates a .goreleaser.yml file").Alias("i")
    96  	var releaseCmd = app.Command("release", "Releases the current project").Alias("r").Default()
    97  	var config = releaseCmd.Flag("config", "Load configuration from file").Short('c').Short('f').PlaceHolder(".goreleaser.yml").String()
    98  	var releaseNotes = releaseCmd.Flag("release-notes", "Load custom release notes from a markdown file").PlaceHolder("notes.md").String()
    99  	var snapshot = releaseCmd.Flag("snapshot", "Generate an unversioned snapshot release, skipping all validations and without publishing any artifacts").Bool()
   100  	var skipPublish = releaseCmd.Flag("skip-publish", "Generates all artifacts but does not publish them anywhere").Bool()
   101  	var skipValidate = releaseCmd.Flag("skip-validate", "Skips all git sanity checks").Bool()
   102  	var rmDist = releaseCmd.Flag("rm-dist", "Remove the dist folder before building").Bool()
   103  	var parallelism = releaseCmd.Flag("parallelism", "Amount of slow tasks to do in concurrently").Short('p').Default("4").Int() // TODO: use runtime.NumCPU here?
   104  	var debug = releaseCmd.Flag("debug", "Enable debug mode").Bool()
   105  	var timeout = releaseCmd.Flag("timeout", "Timeout to the entire release process").Default("30m").Duration()
   106  
   107  	app.Version(fmt.Sprintf("%v, commit %v, built at %v", version, commit, date))
   108  	app.VersionFlag.Short('v')
   109  	app.HelpFlag.Short('h')
   110  
   111  	switch kingpin.MustParse(app.Parse(os.Args[1:])) {
   112  	case initCmd.FullCommand():
   113  		var filename = ".goreleaser.yml"
   114  		if err := initProject(filename); err != nil {
   115  			log.WithError(err).Error("failed to init project")
   116  			terminate(1)
   117  			return
   118  		}
   119  		log.WithField("file", filename).Info("config created; please edit accordingly to your needs")
   120  	case releaseCmd.FullCommand():
   121  		start := time.Now()
   122  		log.Infof(color.New(color.Bold).Sprintf("releasing using goreleaser %s...", version))
   123  		var options = releaseOptions{
   124  			Config:       *config,
   125  			ReleaseNotes: *releaseNotes,
   126  			Snapshot:     *snapshot,
   127  			SkipPublish:  *skipPublish,
   128  			SkipValidate: *skipValidate,
   129  			RmDist:       *rmDist,
   130  			Parallelism:  *parallelism,
   131  			Debug:        *debug,
   132  			Timeout:      *timeout,
   133  		}
   134  		if err := releaseProject(options); err != nil {
   135  			log.WithError(err).Errorf(color.New(color.Bold).Sprintf("release failed after %0.2fs", time.Since(start).Seconds()))
   136  			terminate(1)
   137  			return
   138  		}
   139  		log.Infof(color.New(color.Bold).Sprintf("release succeeded after %0.2fs", time.Since(start).Seconds()))
   140  	}
   141  }
   142  
   143  func terminate(status int) {
   144  	os.Exit(status)
   145  }
   146  
   147  func releaseProject(options releaseOptions) error {
   148  	if options.Debug {
   149  		log.SetLevel(log.DebugLevel)
   150  	}
   151  	cfg, err := loadConfig(options.Config)
   152  	if err != nil {
   153  		return err
   154  	}
   155  	ctx, cancel := context.NewWithTimeout(cfg, options.Timeout)
   156  	defer cancel()
   157  	ctx.Parallelism = options.Parallelism
   158  	ctx.Debug = options.Debug
   159  	log.Debugf("parallelism: %v", ctx.Parallelism)
   160  	if options.ReleaseNotes != "" {
   161  		bts, err := ioutil.ReadFile(options.ReleaseNotes)
   162  		if err != nil {
   163  			return err
   164  		}
   165  		log.WithField("file", options.ReleaseNotes).Info("loaded custom release notes")
   166  		log.WithField("file", options.ReleaseNotes).Debugf("custom release notes: \n%s", string(bts))
   167  		ctx.ReleaseNotes = string(bts)
   168  	}
   169  	ctx.Snapshot = options.Snapshot
   170  	ctx.SkipPublish = ctx.Snapshot || options.SkipPublish
   171  	ctx.SkipValidate = ctx.Snapshot || options.SkipValidate
   172  	ctx.RmDist = options.RmDist
   173  	return doRelease(ctx)
   174  }
   175  
   176  func doRelease(ctx *context.Context) error {
   177  	defer func() { cli.Default.Padding = 3 }()
   178  	var release = func() error {
   179  		for _, pipe := range pipes {
   180  			cli.Default.Padding = 3
   181  			log.Infof(color.New(color.Bold).Sprint(strings.ToUpper(pipe.String())))
   182  			cli.Default.Padding = 6
   183  			if err := handle(pipe.Run(ctx)); err != nil {
   184  				return err
   185  			}
   186  		}
   187  		return nil
   188  	}
   189  	return ctrlc.Default.Run(ctx, release)
   190  }
   191  
   192  func handle(err error) error {
   193  	if err == nil {
   194  		return nil
   195  	}
   196  	if pipeline.IsSkip(err) {
   197  		log.WithField("reason", err.Error()).Warn("skipped")
   198  		return nil
   199  	}
   200  	return err
   201  }
   202  
   203  // InitProject creates an example goreleaser.yml in the current directory
   204  func initProject(filename string) error {
   205  	if _, err := os.Stat(filename); !os.IsNotExist(err) {
   206  		if err != nil {
   207  			return err
   208  		}
   209  		return fmt.Errorf("%s already exists", filename)
   210  	}
   211  	log.Infof(color.New(color.Bold).Sprintf("Generating %s file", filename))
   212  	return ioutil.WriteFile(filename, []byte(exampleConfig), 0644)
   213  }
   214  
   215  func loadConfig(path string) (config.Project, error) {
   216  	if path != "" {
   217  		return config.Load(path)
   218  	}
   219  	for _, f := range [4]string{
   220  		".goreleaser.yml",
   221  		".goreleaser.yaml",
   222  		"goreleaser.yml",
   223  		"goreleaser.yaml",
   224  	} {
   225  		proj, err := config.Load(f)
   226  		if err != nil && os.IsNotExist(err) {
   227  			continue
   228  		}
   229  		return proj, err
   230  	}
   231  	// the user didn't specified a config file and the known files
   232  	// doest not exist, so, return an empty config and a nil err.
   233  	log.Warn("could not load config, using defaults")
   234  	return config.Project{}, nil
   235  }
   236  
   237  var exampleConfig = `# This is an example goreleaser.yaml file with some sane defaults.
   238  # Make sure to check the documentation at http://goreleaser.com
   239  builds:
   240  - env:
   241    - CGO_ENABLED=0
   242  archive:
   243    replacements:
   244      darwin: Darwin
   245      linux: Linux
   246      windows: Windows
   247      386: i386
   248      amd64: x86_64
   249  checksum:
   250    name_template: 'checksums.txt'
   251  snapshot:
   252    name_template: "{{ .Tag }}-next"
   253  changelog:
   254    sort: asc
   255    filters:
   256      exclude:
   257      - '^docs:'
   258      - '^test:'
   259  `