github.com/szyn/goreleaser@v0.76.1-0.20180517112710-333da09a1297/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 `