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