diff --git a/README.md b/README.md index e7a66d6f1..673860ecb 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ goreleaser -GoReleaser builds Go binaries for several platforms, creates a github release and then -push a homebrew formulae to a repository. All that wrapped in your favorite CI. +GoReleaser builds Go binaries for several platforms, creates a Github release and then +pushes a Homebrew formulae to a repository. All that wrapped in your favorite CI. This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to root@carlosbecker.com. @@ -14,45 +14,37 @@ By participating, you are expected to uphold this code. Please report unacceptab The idea started with a [simple shell script](https://github.com/goreleaser/old-go-releaser), but it quickly became more complex and I also wanted to publish binaries via -homebrew. +Homebrew. So, the all-new goreleaser was born. ## Usage -Create a `goreleaser.yml` file in the root of your repository. A minimal config -would look like this: +- You need to export a `GITHUB_TOKEN` environment variable with +the `repo` scope selected. You can create one [here](https://github.com/settings/tokens/new). -```yaml -repo: user/repo -binary_name: my-binary -``` +- GoReleaser uses the latest [Git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) of your repository, +so you need to [create a tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging#Annotated-Tags) first. -You may then run releaser at the root of your repository: +- Now you can run `releaser` at the root of your repository: ```sh curl -s https://raw.githubusercontent.com/goreleaser/get/master/latest | bash ``` -For that to work, you need to export a `GITHUB_TOKEN` environment variable with -the `repo` scope selected. You can create one -[here](https://github.com/settings/tokens/new). - -GoReleaser uses the latest [Git tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) of your repository, -so you need to [create a tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging#Annotated-Tags) first. - -This will build `main.go` as `my-binary`, for `Darwin` and `Linux` +This will build `main.go` as binary, for `Darwin` and `Linux` (`amd64` and `i386`), archive the binary and common files as `.tar.gz`, -and finally, publish a new github release in the `user/repo` repository with +and finally, publish a new Github release in the repository with archives uploaded. + +For further customization create a `goreleaser.yml` file in the root of your repository. + ### Homebrew Add a `brew` section to push a formulae to a Homebrew tab repository: ```yaml -repo: user/repo -binary_name: my-binary brew: repo: user/homebrew-tap folder: optional/subfolder/inside/the/repo @@ -66,8 +58,6 @@ See the [Homebrew docs](https://github.com/Homebrew/brew/blob/master/docs/How-to Just add a `build` section ```yaml -repo: user/repo -binary_name: my-binary build: main: ./cmd/main.go ldflags: -s -w @@ -87,8 +77,6 @@ You can customize the name and format of the archive adding an `archive` section: ```yaml -repo: user/repo -binary_name: my-binary archive: name_template: "{{.BinaryName}}_{{.Version}}_{{.Os}}_{{.Arch}}" format: zip @@ -111,8 +99,6 @@ You might also want to change the files that are packaged by adding a `files` section: ```yaml -repo: user/repo -binary_name: my-binary files: - LICENSE.txt - README.md @@ -124,8 +110,7 @@ files: ### ldflags (main.version) -GoReleaser already sets a `main.version` ldflag, so, in you `main.go` program, -you can: +GoReleaser always sets a `main.version` ldflag. You can use it in your `main.go` file: ```go package main @@ -137,11 +122,29 @@ func main() { } ``` -And this version will always be the tag name. +And this version will always be the name of the current tag. -## Wire it with travis-ci -You may want to wire this to auto-deploy your new tags on travis, for example: +### Other customizations + +- By default it's assumed that the repository to release to is the same as the Git `remote origin`. + If this is not the case for your project, you can specify a `repo`: + +```yaml +repo: owner/custom-repo +``` + +- By default the binary name is the name of the project directory. + You can specify a different `binary_name`: + +```yaml +binary_name: my-binary +``` + + +## Wire it with Travis CI + +You may want to wire this to auto-deploy your new tags on Travis, for example: ```yaml after_success: @@ -150,12 +153,12 @@ after_success: ## What the end result looks like -The release on github looks pretty much like this: +The release on Github looks pretty much like this: [![image](https://cloud.githubusercontent.com/assets/245435/21578845/09404c8a-cf78-11e6-92d7-165ddc03ca6c.png) ](https://github.com/goreleaser/releaser/releases) -And the [homebrew formulae](https://github.com/goreleaser/homebrew-tap/blob/master/release.rb) would look like: +And the [Homebrew formulae](https://github.com/goreleaser/homebrew-tap/blob/master/release.rb) would look like: ```rb class Release < Formula diff --git a/main.go b/main.go index 47a191fc9..c4608de4b 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,6 @@ import ( "github.com/goreleaser/releaser/pipeline/git" "github.com/goreleaser/releaser/pipeline/release" "github.com/goreleaser/releaser/pipeline/repos" - "github.com/goreleaser/releaser/pipeline/valid" "github.com/urfave/cli" ) @@ -28,9 +27,6 @@ var pipes = []pipeline.Pipe{ git.Pipe{}, repos.Pipe{}, - // validate - valid.Pipe{}, - // real work build.Pipe{}, compress.Pipe{}, @@ -52,11 +48,12 @@ func main() { } app.Action = func(c *cli.Context) (err error) { var file = c.String("config") - config, err := config.Load(file) - if err != nil { + cfg, err := config.Load(file) + // Allow failing to load the config file if file is not explicitly specified + if err != nil && c.IsSet("config") { return cli.NewExitError(err.Error(), 1) } - context := context.New(config) + context := context.New(cfg) log.SetFlags(0) for _, pipe := range pipes { log.Println(pipe.Description()) diff --git a/pipeline/brew/brew.go b/pipeline/brew/brew.go index 048fbf69e..d21798c02 100644 --- a/pipeline/brew/brew.go +++ b/pipeline/brew/brew.go @@ -40,7 +40,7 @@ type templateData struct { // Pipe for brew deployment type Pipe struct{} -// Name of the pipe +// Description of the pipe func (Pipe) Description() string { return "Creating homebrew formulae..." } diff --git a/pipeline/build/build.go b/pipeline/build/build.go index 6c82a4f58..436669adc 100644 --- a/pipeline/build/build.go +++ b/pipeline/build/build.go @@ -14,7 +14,7 @@ import ( // Pipe for build type Pipe struct{} -// Name of the pipe +// Description of the pipe func (Pipe) Description() string { return "Building..." } diff --git a/pipeline/compress/compress.go b/pipeline/compress/compress.go index 3f0a2217c..a5bc9297e 100644 --- a/pipeline/compress/compress.go +++ b/pipeline/compress/compress.go @@ -15,7 +15,7 @@ import ( // Pipe for compress type Pipe struct{} -// Name of the pipe +// Description of the pipe func (Pipe) Description() string { return "Creating archives..." } @@ -32,6 +32,7 @@ func (Pipe) Run(ctx *context.Context) error { return g.Wait() } +// Archive represents a compression archive files from disk can be written to. type Archive interface { Close() error Add(name, path string) error diff --git a/pipeline/defaults/defaults.go b/pipeline/defaults/defaults.go index adcb1fe14..37e09f75e 100644 --- a/pipeline/defaults/defaults.go +++ b/pipeline/defaults/defaults.go @@ -1,6 +1,7 @@ package defaults import ( + "errors" "io/ioutil" "strings" @@ -12,13 +13,23 @@ var defaultFiles = []string{"licence", "license", "readme", "changelog"} // Pipe for brew deployment type Pipe struct{} -// Name of the pipe +// Description of the pipe func (Pipe) Description() string { return "Setting defaults..." } // Run the pipe -func (Pipe) Run(ctx *context.Context) (err error) { +func (Pipe) Run(ctx *context.Context) error { + if ctx.Config.Repo == "" { + repo, err := remoteRepo() + ctx.Config.Repo = repo + if err != nil { + return errors.New("failed reading repo from Git: " + err.Error()) + } + } + if ctx.Config.BinaryName == "" { + ctx.Config.BinaryName = strings.Split(ctx.Config.Repo, "/")[1] + } if ctx.Config.Build.Main == "" { ctx.Config.Build.Main = "main.go" } @@ -50,14 +61,14 @@ func (Pipe) Run(ctx *context.Context) (err error) { } } if len(ctx.Config.Files) != 0 { - return + return nil } files, err := findFiles() if err != nil { - return + return err } ctx.Config.Files = files - return + return nil } func findFiles() (files []string, err error) { diff --git a/pipeline/defaults/defaults_test.go b/pipeline/defaults/defaults_test.go index 7df77f4a1..0ecd88b73 100644 --- a/pipeline/defaults/defaults_test.go +++ b/pipeline/defaults/defaults_test.go @@ -18,6 +18,8 @@ func TestFillBasicData(t *testing.T) { assert.NoError(Pipe{}.Run(ctx)) + assert.Equal("goreleaser/releaser", config.Repo) + assert.Equal("releaser", config.BinaryName) assert.Equal("main.go", config.Build.Main) assert.Equal("tar.gz", config.Archive.Format) assert.Contains(config.Build.Oses, "darwin") diff --git a/pipeline/defaults/remote.go b/pipeline/defaults/remote.go new file mode 100644 index 000000000..599462fd5 --- /dev/null +++ b/pipeline/defaults/remote.go @@ -0,0 +1,29 @@ +package defaults + +import ( + "errors" + "os/exec" + "strings" +) + +// remoteRepo gets the repo name from the Git config. +func remoteRepo() (result string, err error) { + cmd := exec.Command("git", "config", "--get", "remote.origin.url") + bts, err := cmd.CombinedOutput() + if err != nil { + return "", errors.New(err.Error() + ": " + string(bts)) + } + return extractRepoFromURL(string(bts)), nil +} + +func extractRepoFromURL(s string) string { + for _, r := range []string{ + "git@github.com:", + ".git", + "https://github.com/", + "\n", + } { + s = strings.Replace(s, r, "", -1) + } + return s +} diff --git a/pipeline/defaults/remote_test.go b/pipeline/defaults/remote_test.go new file mode 100644 index 000000000..66c3cdb83 --- /dev/null +++ b/pipeline/defaults/remote_test.go @@ -0,0 +1,26 @@ +package defaults + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRepoName(t *testing.T) { + assert := assert.New(t) + name, err := remoteRepo() + assert.NoError(err) + assert.Equal("goreleaser/releaser", name) +} + +func TestExtractReporFromGitURL(t *testing.T) { + assert := assert.New(t) + url := extractRepoFromURL("git@github.com:goreleaser/releaser.git") + assert.Equal("goreleaser/releaser", url) +} + +func TestExtractReporFromHttpsURL(t *testing.T) { + assert := assert.New(t) + url := extractRepoFromURL("https://github.com/goreleaser/releaser.git") + assert.Equal("goreleaser/releaser", url) +} diff --git a/pipeline/env/env.go b/pipeline/env/env.go index b35b9ab28..8c72da157 100644 --- a/pipeline/env/env.go +++ b/pipeline/env/env.go @@ -7,12 +7,13 @@ import ( "github.com/goreleaser/releaser/context" ) +// ErrMissingToken indicates an error when GITHUB_TOKEN is missing in the environment var ErrMissingToken = errors.New("Missing GITHUB_TOKEN") // Pipe for env type Pipe struct{} -// Name of the pipe +// Description of the pipe func (Pipe) Description() string { return "Loading data from environment variables..." } diff --git a/pipeline/git/git.go b/pipeline/git/git.go index ed2e5f39c..a137c994a 100644 --- a/pipeline/git/git.go +++ b/pipeline/git/git.go @@ -5,7 +5,7 @@ import "github.com/goreleaser/releaser/context" // Pipe for brew deployment type Pipe struct{} -// Name of the pipe +// Description of the pipe func (Pipe) Description() string { return "Gathering Git data..." } diff --git a/pipeline/release/release.go b/pipeline/release/release.go index 66ddb9152..b17af9fb0 100644 --- a/pipeline/release/release.go +++ b/pipeline/release/release.go @@ -14,7 +14,7 @@ import ( // Pipe for github release type Pipe struct{} -// Name of the pipe +// Description of the pipe func (Pipe) Description() string { return "Releasing to GitHub..." } diff --git a/pipeline/repos/repos.go b/pipeline/repos/repos.go index 17f82c2f7..5a3784420 100644 --- a/pipeline/repos/repos.go +++ b/pipeline/repos/repos.go @@ -9,7 +9,7 @@ import ( // Pipe for brew deployment type Pipe struct{} -// Name of the pipe +// Description of the pipe func (Pipe) Description() string { return "Filling repositories data..." } diff --git a/pipeline/valid/valid.go b/pipeline/valid/valid.go deleted file mode 100644 index 710e51a13..000000000 --- a/pipeline/valid/valid.go +++ /dev/null @@ -1,26 +0,0 @@ -package valid - -import ( - "errors" - - "github.com/goreleaser/releaser/context" -) - -// Pipe for brew deployment -type Pipe struct{} - -// Name of the pipe -func (Pipe) Description() string { - return "Validating configuration..." -} - -// Run the pipe -func (Pipe) Run(ctx *context.Context) (err error) { - if ctx.Config.BinaryName == "" { - return errors.New("missing binary_name") - } - if ctx.Config.Repo == "" { - return errors.New("missing repo") - } - return -} diff --git a/pipeline/valid/valid_test.go b/pipeline/valid/valid_test.go deleted file mode 100644 index 35a6bb001..000000000 --- a/pipeline/valid/valid_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package valid - -import ( - "testing" - - "github.com/goreleaser/releaser/config" - "github.com/goreleaser/releaser/context" - "github.com/stretchr/testify/assert" -) - -func runPipe(repo, bin string) error { - var config = &config.ProjectConfig{ - Repo: repo, - BinaryName: bin, - } - var ctx = &context.Context{ - Config: config, - } - return Pipe{}.Run(ctx) -} - -func TestValidadeMissingBinaryName(t *testing.T) { - assert.Error(t, runPipe("a/b", "")) -} - -func TestValidadeMissingRepo(t *testing.T) { - assert.Error(t, runPipe("", "a")) -} - -func TestValidadeMinimalConfig(t *testing.T) { - assert.NoError(t, runPipe("a/b", "a")) -}