Skip to content

Commit

Permalink
Added support for pinning build flags and environment variables. (#82)
Browse files Browse the repository at this point in the history
Fixes: #46

Signed-off-by: Bartlomiej Plotka <[email protected]>
  • Loading branch information
bwplotka authored May 13, 2021
1 parent ca2c6fc commit 1ee08ef
Show file tree
Hide file tree
Showing 30 changed files with 401 additions and 57 deletions.
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,23 @@ We use *breaking* word for marking changes that are not backward compatible (rel

## (Under development)

## [v0.4.1](https://github.com/bwplotka/bingo/releases/tag/v0.4.1) - 2021.05.12

### Added

* Added support for build flags and envs via go.mod file.

### Fixed

* Generated files have limited permission.
* Support for Go pre-released versions.


## [v0.4.0](https://github.com/bwplotka/bingo/releases/tag/v0.4.0) - 2021.03.24

* Added support for Go 1.16, following the changes it introduces in the module system: https://blog.golang.org/go116-module-changes
### Added

* Added support for Go 1.16, following the changes it introduces in the module system: https://blog.golang.org/go116-module-changes.

## [v0.3.1](https://github.com/bwplotka/bingo/releases/tag/v0.3.1) - 2021.02.02

Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,25 @@ Let's show a few, real, sometimes novel examples showcasing `bingo` capabilities
${GOBIN}/thanos-v0.17.2 --help
```
## Advanced Techniques
* Using advanced go build flags and environment variables.
To tell bingo to use certain env vars and tags during build time, just add them as a comment to the go.mod file manually and do
`bingo get`. Done!
NOTE: Order of comment matters. First bingo expects relative package name (optional), then environment variables, then flags. All space delimited.
Real example from production project that relies on extended Hugo.
```
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
go 1.16
require github.com/gohugoio/hugo v0.83.1 // CGO_ENABLED=1 -tags=extended
```
## Production Usage
To see production example see:
Expand Down
19 changes: 12 additions & 7 deletions get.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ func get(ctx context.Context, logger *log.Logger, c getConfig, rawTarget string)
target.Module.Version = mf.DirectPackage().Module.Version
}
target.RelPath = mf.DirectPackage().RelPath
target.BuildFlags = mf.DirectPackage().BuildFlags
target.BuildEnvs = mf.DirectPackage().BuildEnvs

// Save for future versions without potentially existing files.
pkgPath = target.Path()
Expand Down Expand Up @@ -530,6 +532,7 @@ func getPackage(ctx context.Context, logger *log.Logger, c installPackageConfig,
if c.verbose {
logger.Println("getting target", target.String(), "(module", target.Module.Path, ")")
}

// The out module file we generate/maintain keep in modDir.
outModFile := filepath.Join(c.modDir, name+".mod")
tmpEmptyModFilePath := filepath.Join(c.modDir, name+"-e.tmp.mod")
Expand All @@ -551,7 +554,7 @@ func getPackage(ctx context.Context, logger *log.Logger, c installPackageConfig,
}
defer errcapture.Do(&err, tmpEmptyModFile.Close, "close")

runnable := c.runner.With(ctx, tmpEmptyModFile.FileName(), c.modDir)
runnable := c.runner.With(ctx, tmpEmptyModFile.FileName(), c.modDir, nil)
if err := resolvePackage(logger, c.verbose, tmpEmptyModFile.FileName(), runnable, c.update, &target); err != nil {
return err
}
Expand Down Expand Up @@ -588,8 +591,7 @@ func getPackage(ctx context.Context, logger *log.Logger, c installPackageConfig,
return err
}

runnable := c.runner.With(ctx, tmpModFile.FileName(), c.modDir)
if err := install(runnable, name, c.link, tmpModFile.DirectPackage()); err != nil {
if err := install(ctx, c.runner, c.modDir, name, c.link, tmpModFile); err != nil {
return errors.Wrap(err, "install")
}

Expand Down Expand Up @@ -642,7 +644,6 @@ func autoFetchReplaceStatements(runnable runner.Runnable, target bingo.Package)
return nil, errors.Wrapf(err, "parse target mod file %v", targetModFile)
}
return targetModParsed.Replace, nil

}

// gobin mimics the way go install finds where to install go tool.
Expand All @@ -654,15 +655,19 @@ func gobin() string {
return binPath
}

func install(runnable runner.Runnable, name string, link bool, pkg *bingo.Package) (err error) {
func install(ctx context.Context, r *runner.Runner, modDir string, name string, link bool, modFile *bingo.ModFile) (err error) {
pkg := modFile.DirectPackage()
if err := validateTargetName(name); err != nil {
return errors.Wrap(err, pkg.String())
}

// Two purposes of doing list with mod=mod:
// * Check if path is pointing to non-buildable package.
// * Rebuild go.sum and go.mod (tidy) which is required to build with -mod=readonly (default) to work.
if listOutput, err := runnable.List(runner.NoUpdatePolicy, "-mod=mod", "-f={{.Name}}", pkg.Path()); err != nil {
var listArgs []string
listArgs = append(listArgs, modFile.DirectPackage().BuildFlags...)
listArgs = append(listArgs, "-mod=mod", "-f={{.Name}}", pkg.Path())
if listOutput, err := r.With(ctx, modFile.FileName(), modDir, nil).List(runner.NoUpdatePolicy, listArgs...); err != nil {
return errors.Wrap(err, "list")
} else if !strings.HasSuffix(listOutput, "main") {
return errors.Errorf("package %s is non-main (go list output %q), nothing to get and build", pkg.Path(), listOutput)
Expand All @@ -672,7 +677,7 @@ func install(runnable runner.Runnable, name string, link bool, pkg *bingo.Packag

// go install does not define -modfile flag so so we mimic go install with go build -o instead.
binPath := filepath.Join(gobin, fmt.Sprintf("%s-%s", name, pkg.Module.Version))
if err := runnable.Build(pkg.Path(), binPath); err != nil {
if err := r.With(ctx, modFile.FileName(), modDir, pkg.BuildEnvs).Build(pkg.Path(), binPath, pkg.BuildFlags...); err != nil {
return errors.Wrap(err, "build versioned")
}

Expand Down
6 changes: 3 additions & 3 deletions get_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ func TestGet(t *testing.T) {
},
expectRows: []row{
// TODO(bwplotka) This will be painful to maintain, but well... improve it
{name: "thanos", binName: "thanos-v0.19.0", pkgVersion: "github.com/thanos-io/thanos/cmd/thanos@v0.19.0"},
{name: "thanos", binName: "thanos-v0.20.1", pkgVersion: "github.com/thanos-io/thanos/cmd/thanos@v0.20.1"},
},
expectBinaries: []string{
"buildable",
Expand All @@ -712,7 +712,7 @@ func TestGet(t *testing.T) {
"f2-v1.0.0", "f2-v1.1.0", "f2-v1.2.0", "f2-v1.3.0", "f2-v1.4.0", "f2-v1.5.0", "f3-v1.1.0", "f3-v1.3.0", "f3-v1.4.0",
"faillint-v1.0.0", "faillint-v1.1.0", "faillint-v1.3.0", "faillint-v1.4.0", "faillint-v1.5.0",
"go-bindata-v3.1.1+incompatible",
"thanos-v0.13.1-0.20210108102609-f85e4003ba51", "thanos-v0.19.0",
"thanos-v0.13.1-0.20210108102609-f85e4003ba51", "thanos-v0.20.1",
"wr_buildable-v0.0.0-20210109165512-ccbd4039b94a", "wr_buildable-v0.0.0-20210110214650-ab990d1be30b",
},
},
Expand All @@ -727,7 +727,7 @@ func TestGet(t *testing.T) {
},
expectRows: []row{
// TODO(bwplotka) This will be painful to maintain, but well... improve it
{name: "thanos", binName: "thanos-v0.19.0", pkgVersion: "github.com/thanos-io/thanos/cmd/thanos@v0.19.0"},
{name: "thanos", binName: "thanos-v0.20.1", pkgVersion: "github.com/thanos-io/thanos/cmd/thanos@v0.20.1"},
},
expectSameBinariesAsBefore: true,
},
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func main() {
goCmd := getFlags.String("go", "go", "Path to the go command.")
getUpdate := getFlags.Bool("u", false, "The -u flag instructs get to update modules providing dependencies of packages named on the command line to use newer minor or patch releases when available.")
getUpdatePatch := getFlags.Bool("upatch", false, "The -upatch flag (not -u patch) also instructs get to update dependencies, but changes the default to select patch releases.")

getInsecure := getFlags.Bool("insecure", false, "Use -insecure flag when using 'go get'")
getLink := getFlags.Bool("l", false, "If enabled, bingo will also create soft link called <tool> that links to the current"+
"<tool>-<version> binary. Use Variables.mk and variables.env if you want to be sure that what you are invoking is what is pinned.")
Expand Down
47 changes: 44 additions & 3 deletions pkg/bingo/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"sort"
"strings"

"github.com/bwplotka/bingo/pkg/envars"
"github.com/bwplotka/bingo/pkg/runner"
"github.com/efficientgo/tools/core/pkg/errcapture"
"github.com/efficientgo/tools/core/pkg/merrors"
Expand Down Expand Up @@ -46,6 +47,11 @@ type Package struct {
// Empty if the module is a full package path.
// If Module.Path is empty and RelPath specified, it means that we don't know what is a module what is the package path.
RelPath string

// BuildEnvs are environment variables to be used during go build process.
BuildEnvs envars.EnvSlice
// BuildFlags are flags to be used during go build process.
BuildFlags []string
}

// String returns a representation of the Package suitable for `go` tools and logging.
Expand Down Expand Up @@ -223,7 +229,7 @@ func (mf *ModFile) Reload() (err error) {

mf.directPackage = &Package{Module: r.Mod}
if len(r.Syntax.Suffix) > 0 {
mf.directPackage.RelPath = strings.Trim(r.Syntax.Suffix[0].Token[3:], "\n")
mf.directPackage.RelPath, mf.directPackage.BuildEnvs, mf.directPackage.BuildFlags = parseDirectPackageMeta(strings.Trim(r.Syntax.Suffix[0].Token[3:], "\n"))
}
break
}
Expand All @@ -235,6 +241,27 @@ func (mf *ModFile) Reload() (err error) {
return nil
}

func parseDirectPackageMeta(line string) (relPath string, buildEnv []string, buildFlags []string) {
elem := strings.Split(line, " ")
for i, l := range elem {
if l == "" {
continue
}

if l[0] == '-' {
buildFlags = elem[i:]
break
}

if !strings.Contains(l, "=") {
relPath = l
continue
}
buildEnv = append(buildEnv, l)
}
return relPath, buildEnv, buildFlags
}

func (mf *ModFile) DirectPackage() *Package {
return mf.directPackage
}
Expand All @@ -260,11 +287,19 @@ func (mf *ModFile) SetDirectRequire(target Package) (err error) {
mf.dropAllRequire()
mf.m.AddNewRequire(target.Module.Path, target.Module.Version, false)

var meta []string
// Add sub package info if needed.
if target.RelPath != "" && target.RelPath != "." {
meta = append(meta, target.RelPath)
}
meta = append(meta, target.BuildEnvs...)
meta = append(meta, target.BuildFlags...)

if len(meta) > 0 {
r := mf.m.Require[0]
r.Syntax.Suffix = append(r.Syntax.Suffix[:0], modfile.Comment{Suffix: true, Token: "// " + target.RelPath})
r.Syntax.Suffix = append(r.Syntax.Suffix[:0], modfile.Comment{Suffix: true, Token: "// " + strings.Join(meta, " ")})
}

mf.m.Cleanup()
mf.directPackage = &target
return nil
Expand Down Expand Up @@ -333,7 +368,7 @@ func ModDirectPackage(modFile string) (pkg Package, err error) {
return *mf.directPackage, nil
}

// ModIndirectPackage return the all indirect mod from any module file.
// ModIndirectModules return the all indirect mod from any module file.
func ModIndirectModules(modFile string) (mods []module.Version, err error) {
m, err := ParseModFileOrReader(modFile, nil)
if err != nil {
Expand Down Expand Up @@ -390,6 +425,9 @@ type PackageRenderable struct {
PackagePath string
EnvVarName string
Versions []PackageVersionRenderable

BuildFlags []string
BuildEnvVars []string
}

func (p PackageRenderable) ToPackages() []Package {
Expand Down Expand Up @@ -457,6 +495,9 @@ ModLoop:
Versions: []PackageVersionRenderable{
{Version: pkg.Module.Version, ModFile: filepath.Base(f)},
},
BuildFlags: pkg.BuildFlags,
BuildEnvVars: pkg.BuildEnvs,

EnvVarName: varName,
PackagePath: pkg.Path(),
ModPath: pkg.Module.Path,
Expand Down
Loading

0 comments on commit 1ee08ef

Please sign in to comment.