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