Skip to content

Commit

Permalink
Added a bunch of new features and improvements
Browse files Browse the repository at this point in the history
* Added `sfreleaser build` to build artifacts, `sfreleaser build --help` for all the juicy details of the new command.
* Bumped to `Golang` `1.20.5`, this will pull `goreleaser/goreleaser-cross:v1.20.5` so expect some delays before your build starts.`
  > **Note** `docker pull goreleaser/goreleaser-cross:v1.20.5` to "boostrap" this step.
* The platform `linux/arm64` is now built by default.
* When version is prompted in release, default value is now extracted from release notes' header.
* Speed up build by mounting local `go env GOCACHE` into the Docker container that build artifacts (only if language == `golang`).
  • Loading branch information
maoueh committed Jul 6, 2023
1 parent d296410 commit cf505a6
Show file tree
Hide file tree
Showing 12 changed files with 339 additions and 27 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## v0.6.0

* Added `sfreleaser build` to build artifacts, `sfreleaser build --help` for all the juicy details of the new command.

* Bumped to `Golang` `1.20.5`, this will pull `goreleaser/goreleaser-cross:v1.20.5` so expect some delays before your build starts.`

> **Note** `docker pull goreleaser/goreleaser-cross:v1.20.5` to "boostrap" this step.
* The platform `linux/arm64` is now built by default.

* When version is prompted in release, default value is now extracted from release notes' header.

* Speed up build by mounting local `go env GOCACHE` into the Docker container that build artifacts (only if language == `golang`).

## v0.5.5

* Validate that received `<version>` argument in `sfreleaser release <version>` actually follows our convention.
Expand Down
155 changes: 155 additions & 0 deletions cmd/sfreleaser/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package main

import (
"fmt"
"os"
"os/signal"
"path/filepath"
"syscall"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/streamingfast/cli"
. "github.com/streamingfast/cli"
"github.com/streamingfast/cli/sflags"
"go.uber.org/zap"
)

var BuildCmd = Command(nil,
"build [<version>]",
"Build the actual artifacts this project would produce on release",
Description(`
Based on the type of project your are building, perform the necessary step to perform
a build of the artifacts of your project.
How the build is performed and what build artifacts are produced depends on the choosen
language and variant.
Refer to 'sfrealeaser releaser --help' for more information on the available options.
`),
ExamplePrefixed("sfreleaser build", `
# Build for the current platform when no argument
# Build for all platforms
--all
# Build for specific platform(s)
--platform linux/arm64 --platform linux/amd64
# Build for specific platform(s) (alternative syntax)
-P linux/arm64 -P linux/amd64
`),
Flags(func(flags *pflag.FlagSet) {
// Those are all provided by 'release' now! This means duplication at the config level for
// build vs release, what a mess. How to deal with this? I don't want to break compatibility.
flags.Bool("allow-dirty", false, "Perform release step even if Git is not clean, tries to configured used tool(s) to also allow dirty Git state")
flags.StringArray("pre-build-hooks", nil, "Set of pre build hooks to run before run the actual building steps")
flags.String("goreleaser-docker-image", "goreleaser/goreleaser-cross:v1.20.5", "Full Docker image used to run Goreleaser tool (which perform Go builds and GitHub releases (in all languages))")

// Flag specific to build
flags.Bool("all", false, "Build for all platforms and not your current machine")
flags.StringArrayP("platform", "P", nil, "Run only for those platform (repeat --platform <value> for multiple platforms), platform are defined as 'os/arch' (e.g. 'linux/amd64', dash separator also accepted), use 'darwin' to build for macOS and 'windows' for Windows (if activated)")
}),
Execute(func(cmd *cobra.Command, args []string) error {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

go func() {
<-sigs
cli.Exit(1)
}()

if err := build(cmd, args); err != nil {
// Return error normally, will hit OnCommandError right after
return err
}

// Forces our exit handler (if any) to run
cli.Exit(0)
return nil
}),
OnCommandError(func(err error) {
fmt.Println("The build failed with the following error:")
fmt.Println()
fmt.Println(err.Error())
fmt.Println()

fmt.Println("If the error is not super clear, you can use 'sfreleaser doctor' which")
fmt.Println("list common errors and how to fix them.")

cli.Exit(1)
}),
)

func build(cmd *cobra.Command, args []string) error {
global := mustGetGlobal(cmd)
build := &BuildModel{Version: ""}
if len(args) > 0 {
build.Version = args[0]
cli.NoError(validVersion(build.Version), "invalid version")
}

allowDirty := sflags.MustGetBool(cmd, "allow-dirty")
goreleaserDockerImage := sflags.MustGetString(cmd, "goreleaser-docker-image")
preBuildHooks := sflags.MustGetStringArray(cmd, "pre-build-hooks")

build.populate(cmd)

zlog.Debug("starting 'sfreleaser build'",
zap.Inline(global),
zap.Bool("allow_dirty", allowDirty),
zap.String("goreleaser_docker_image", goreleaserDockerImage),
zap.Strings("pre_build_hooks", preBuildHooks),
zap.Reflect("build_model", build),
)

global.ensureValidForBuild()

cli.NoError(os.Chdir(global.WorkingDirectory), "Unable to change directory to %q", global.WorkingDirectory)

verifyTools()

// For simplicity in the code below
version := build.Version
fmt.Printf("Building %q ...\n", version)

buildDirectory := "build"
envFilePath := filepath.Join(buildDirectory, ".env.release")

cli.NoError(os.MkdirAll(buildDirectory, os.ModePerm), "Unable to create build directory")
configureGitHubTokenEnvFile(envFilePath)

// By doing this after creating the build directory and release notes, we ensure
// that those are ignored, the user will need to ignore them to process (or --allow-dirty).
if !allowDirty {
ensureGitNotDirty()
}

if len(preBuildHooks) > 0 {
fmt.Println()
fmt.Printf("Executing %d pre-build hook(s)\n", len(preBuildHooks))
executeHooks(preBuildHooks, global, &ReleaseModel{Version: version})
}

if version != "" {
fmt.Println()
fmt.Println("Creating temporary tag so that goreleaser can work properly")
run("git tag", version)

cli.ExitHandler(deleteTagExitHandlerID, func(_ int) {
zlog.Debug("Deleting local temporary tag")
runSilent("git tag -d", version)
})
}

gitHubRelease := &GitHubReleaseModel{
AllowDirty: allowDirty,
EnvFilePath: envFilePath,
GoreleaseConfigPath: filepath.Join(buildDirectory, "goreleaser.yaml"),
GoreleaserImageID: goreleaserDockerImage,
}

buildArtifacts(global, build, gitHubRelease)

return nil
}
2 changes: 1 addition & 1 deletion cmd/sfreleaser/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func fetchGitSyncState() gitSyncState {

func ensureGitNotDirty() {
if isGitDirty() {
fmt.Println("Your git repository is dirty, refusing to release (use --allow-dirty to releaser while being Git dirty)")
fmt.Println("Your git repository is dirty, refusing to release (use --allow-dirty to continue even if Git is dirty)")
run("git status")
cli.Exit(1)
}
Expand Down
1 change: 1 addition & 0 deletions cmd/sfreleaser/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func main() {
ConfigureVersion(version),

DoctorCmd,
BuildCmd,
ReleaseCmd,
InstallCmd,

Expand Down
24 changes: 24 additions & 0 deletions cmd/sfreleaser/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ func (g *GlobalModel) ResolveFile(in string) string {
return filepath.Join(g.ConfigRoot, in)
}

func (g *GlobalModel) ensureValidForBuild() {
g.ensureValidForRelease()

if g.Language != LanguageGolang {
cli.Quit(`'sfreleaser build' only works for Go projects at the moment, sorry!`)
}
}

func (g *GlobalModel) ensureValidForRelease() {
var errors []string
if g.Language == LanguageUnset {
Expand All @@ -88,6 +96,22 @@ func (g *GlobalModel) ensureValidForRelease() {
}
}

type BuildModel struct {
Version string

All bool
Platforms []string
}

func (m *BuildModel) populate(cmd *cobra.Command) {
m.All = sflags.MustGetBool(cmd, "all")
m.Platforms = sflags.MustGetStringArray(cmd, "platform")

for i, platform := range m.Platforms {
m.Platforms[i] = strings.Replace(strings.ToLower(platform), "/", "-", 1)
}
}

type ReleaseModel struct {
Version string

Expand Down
15 changes: 12 additions & 3 deletions cmd/sfreleaser/prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/bobg/go-generics/v2/slices"
"github.com/streamingfast/cli"
"go.uber.org/zap"
)

func promptLanguage() Language {
Expand All @@ -16,13 +17,21 @@ func promptVariant() Variant {
return cli.PromptSelect("Project variant", slices.Filter(VariantNames(), isSupportedVariant), ParseVariant)
}

func promptVersion() string {
zlog.Debug("asking for version via terminal")
func promptVersion(defaultVersion string) string {
zlog.Debug("asking for version via terminal", zap.String("default", defaultVersion))

opts := []cli.PromptOption{
validateVersionPrompt(),
}

if defaultVersion != "" {
opts = append(opts, cli.WithPromptDefaultValue(defaultVersion))
}

return cli.Prompt(
fmt.Sprintf("What version do you want to release (current latest tag is %s)", latestTag()),
cli.PromptTypeString,
validateVersionPrompt(),
opts...,
)
}

Expand Down
10 changes: 5 additions & 5 deletions cmd/sfreleaser/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ var ReleaseCmd = Command(release,
flags.StringArray("pre-build-hooks", nil, "Set of pre build hooks to run before run the actual building steps")
flags.String("upload-substreams-spkg", "", "If provided, add this Substreams package file to the release, if manifest is a 'substreams.yaml' file, the package is first built")
flags.Bool("publish-now", false, "By default, publish the release to GitHub in draft mode, if the flag is used, the release is published as latest")
flags.String("goreleaser-docker-image", "goreleaser/goreleaser-cross:v1.20.3", "Full Docker image used to run Goreleaser tool (which perform Go builds and GitHub releases (in all languages))")
flags.String("goreleaser-docker-image", "goreleaser/goreleaser-cross:v1.20.5", "Full Docker image used to run Goreleaser tool (which perform Go builds and GitHub releases (in all languages))")

// Brew Flags
flags.Bool("brew-disabled", false, "[Brew only] Disable Brew tap release completely, only applies for 'Golang'/'Application' types")
Expand Down Expand Up @@ -132,7 +132,7 @@ func release(cmd *cobra.Command, args []string) error {
verifyTools()

if release.Version == "" {
release.Version = promptVersion()
release.Version = promptVersion(readReleaseNotesVersion(changelogPath))
}

// For simplicity in the code below
Expand Down Expand Up @@ -166,7 +166,7 @@ func release(cmd *cobra.Command, args []string) error {
executeHooks(preBuildHooks, global, release)
}

uploadSpkgPath := prepareSubstreamsSpkg(uploadSubstreamsSPKG, global, release)
uploadSpkgPath := prepareSubstreamsSpkg(uploadSubstreamsSPKG, global, release.Version)

fmt.Println()
fmt.Println("Creating temporary tag so that goreleaser can work properly")
Expand Down Expand Up @@ -260,13 +260,13 @@ func executeHook(command string, model map[string]any) {
run(out.String())
}

func prepareSubstreamsSpkg(spkgPath string, global *GlobalModel, release *ReleaseModel) string {
func prepareSubstreamsSpkg(spkgPath string, global *GlobalModel, version string) string {
if spkgPath == "" {
return ""
}

if !strings.HasSuffix(spkgPath, ".spkg") {
spkgPath = filepath.Join(global.WorkingDirectory, "build", global.Project+"-"+release.Version+".spkg")
spkgPath = filepath.Join(global.WorkingDirectory, "build", global.Project+"-"+version+".spkg")

fmt.Printf("Packing your Substreams file at %q\n", spkgPath)
run("substreams pack -o", "'"+spkgPath+"'")
Expand Down
57 changes: 52 additions & 5 deletions cmd/sfreleaser/release_github.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,51 @@ package main

import (
"fmt"
"runtime"
"strings"

"github.com/streamingfast/cli"
)

func buildArtifacts(global *GlobalModel, build *BuildModel, githubRelease *GitHubReleaseModel) {
renderGoreleaserFile(global, &ReleaseModel{
Version: build.Version,
Brew: &BrewReleaseModel{Disabled: true},
Rust: &RustReleaseModel{},
}, githubRelease)

var goreleaserArguments []string
if build.All {
// Nothing, default build all
} else if len(build.Platforms) > 0 {
for _, platform := range build.Platforms {
goreleaserArguments = append(goreleaserArguments, "--id", platform)
}
} else {
goreleaserArguments = []string{"--id", runtime.GOOS + "-" + runtime.GOARCH}
}

if build.Version == "" {
goreleaserArguments = append(goreleaserArguments, "--snapshot")
}

run(goreleaseDockerCommand(global, githubRelease, "build", nil, goreleaserArguments)...)
}

func releaseGithub(global *GlobalModel, release *ReleaseModel, githubRelease *GitHubReleaseModel) {
if devSkipGoreleaser {
return
}

renderGoreleaserFile(global, release, githubRelease)

fmt.Println()
run(goreleaseDockerCommand(global, githubRelease, "release", nil, []string{
"--release-notes=" + githubRelease.ReleaseNotesPath,
})...)
}

func goreleaseDockerCommand(global *GlobalModel, githubRelease *GitHubReleaseModel, command string, dockerExtraArguments []string, goReleaserExtraArguments []string) []string {
arguments := []string{
"docker",

Expand All @@ -23,19 +57,32 @@ func releaseGithub(global *GlobalModel, release *ReleaseModel, githubRelease *Gi
"-v /var/run/docker.sock:/var/run/docker.sock",
"-v", cli.WorkingDirectory() + ":/go/src/work",
"-w /go/src/work",
}

if global.Language == LanguageGolang {
if output, _, err := maybeResultOf("go env GOCACHE"); err == nil && output != "" {
arguments = append(arguments, "-e GOCACHE=/go/cache")
arguments = append(arguments, "-v", strings.TrimSpace(output)+":/go/cache")
}
}

arguments = append(arguments, dockerExtraArguments...)

arguments = append(arguments, []string{
githubRelease.GoreleaserImageID,

// goreleaser arguments
command,
"-f", githubRelease.GoreleaseConfigPath,
"--timeout=60m",
"--rm-dist",
"--release-notes=" + githubRelease.ReleaseNotesPath,
}
"--clean",
}...)

if githubRelease.AllowDirty {
arguments = append(arguments, "--skip-validate")
}

fmt.Println()
run(arguments...)
arguments = append(arguments, goReleaserExtraArguments...)

return arguments
}
Loading

0 comments on commit cf505a6

Please sign in to comment.