Skip to content

Commit

Permalink
WIP Add golang sub package
Browse files Browse the repository at this point in the history
  • Loading branch information
heppu committed Nov 14, 2023
1 parent edc2352 commit 53f854d
Show file tree
Hide file tree
Showing 5 changed files with 417 additions and 33 deletions.
42 changes: 9 additions & 33 deletions go.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import (
"context"
"fmt"
"log"
"os"
"path"
"strings"

"github.com/elisasre/mageutil/golang"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
Expand All @@ -34,29 +33,17 @@ type BuildInfo struct {

// Go is shorthand for go executable provided by system.
func Go(ctx context.Context, args ...string) error {
return GoWith(ctx, nil, args...)
return golang.Go(ctx, args...)
}

// GoWith is shorthand for go executable provided by system.
func GoWith(ctx context.Context, env map[string]string, args ...string) error {
fmt.Printf("env: %s\n", env)
return sh.RunWithV(env, "go", args...)
return golang.GoWith(ctx, env, args...)
}

// Targets returns list of main pkgs under CmdDir.
func Targets(ctx context.Context) ([]string, error) {
entries, err := os.ReadDir(CmdDir)
if err != nil {
return nil, err
}

targets := make([]string, 0, len(entries))
for _, e := range entries {
if e.IsDir() {
targets = append(targets, e.Name())
}
}
return targets, nil
return golang.BuildTargets(ctx)
}

// BuildAll binaries for targets returned by utils.Targets using utils.Build.
Expand Down Expand Up @@ -185,12 +172,7 @@ func Run(ctx context.Context, name string, args ...string) error {

// GoList lists all packages in given target.
func GoList(ctx context.Context, target string) ([]string, error) {
pkgsRaw, err := sh.Output("go", "list", target)
if err != nil {
return nil, err
}
pkgs := strings.Split(strings.ReplaceAll(pkgsRaw, "\r\n", ","), "\n")
return pkgs, nil
return golang.ListPackages(ctx, target)
}

// BinDir returns path in format of target/bin/{GOOS}/{GOARCH}
Expand All @@ -212,30 +194,24 @@ func BinDir() (string, error) {
// Deprecated: use Tidy instead
func Ensure(ctx context.Context) error {
log.Println("WARNING: Ensure is deprecated, use Tidy instead")
return Tidy(ctx)
return golang.Tidy(ctx)
}

// EnsureInSync checks that all dependencies are up to date
// useful in CI/CD pipelines to validate that dependencies match go.mod
// Deprecated: use TidyAndVerifyNoChanges instead
func EnsureInSync(ctx context.Context) error {
log.Println("WARNING: EnsureInSync is deprecated, use TidyAndVerifyNoChanges instead")
return TidyAndVerifyNoChanges(ctx)
return golang.TidyAndVerify(ctx)
}

// Tidy runs go mod tidy
func Tidy(ctx context.Context) error {
return Go(ctx, "mod", "tidy")
return golang.Tidy(ctx)
}

// TidyAndVerifyNoChanges runs go mod tidy and verifies that there are no changes to go.mod or go.sum
// useful in CI/CD pipelines to validate that dependencies match go.mod
func TidyAndVerifyNoChanges(ctx context.Context) error {
if err := Tidy(ctx); err != nil {
return err
}
if err := Git(ctx, "diff", "--exit-code", "--", "go.mod", "go.sum"); err != nil {
return fmt.Errorf("go.mod and go.sum are not in sync. run `go mod tidy` and commit changes")
}
return nil
return golang.TidyAndVerify(ctx)
}
199 changes: 199 additions & 0 deletions golang/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// package golang provides util functions for [Magefile].
// For usage please refer to [documentation] provided by Magefile.
// For autocompletions see [completions].
//
// [Magefile]: https://magefile.org/
// [documentation]: https://magefile.org/importing/
// [completions]: https://github.com/elisasre/mageutil/tree/main/completions
package golang

import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"path"
"strings"

"github.com/magefile/mage/sh"
)

// BinDir is base directory for build outputs.
const BinDir = "./target/bin/"

// BuildInfo contains relevant information about produced binary.
type BuildInfo struct {
BinPath string
GOOS string
GOARCH string
}

type (
BuildPlatform struct{ OS, Arch string }
BuildMatrix []BuildPlatform
)

// DefaultBuildMatrix defines subset of cross-compile targets supported by Go.
var DefaultBuildMatrix = BuildMatrix{
{OS: "linux", Arch: "amd64"},
{OS: "linux", Arch: "arm64"},
{OS: "darwin", Arch: "amd64"},
{OS: "darwin", Arch: "arm64"},
{OS: "windows", Arch: "amd64"},
}

// Build builds binary which is created under ./target/bin/{GOOS}/{GOARCH}/.
func Build(ctx context.Context, name string, buildArgs ...string) (BuildInfo, error) {
return BuildWith(ctx, nil, name, buildArgs...)
}

// BuildWith injects given env and builds binary which is created under ./target/bin/{GOOS}/{GOARCH}/.
func BuildWith(ctx context.Context, env map[string]string, name string, buildArgs ...string) (BuildInfo, error) {
return build(ctx, env, BinDir, name, buildArgs...)
}

// BuildForPlatform builds binary for wanted architecture and os.
func BuildForPlatform(ctx context.Context, goos, goarch, name string, buildArgs ...string) (BuildInfo, error) {
return BuildForPlatformWith(ctx, nil, goos, goarch, name, buildArgs...)
}

// BuildForPlatform injects env, builds binary for wanted architecture and os.
func BuildForPlatformWith(ctx context.Context, env map[string]string, goos, goarch, name string, buildArgs ...string) (BuildInfo, error) {
if env == nil {
env = map[string]string{}
}
env["GOOS"] = goos
env["GOARCH"] = goarch
return BuildWith(ctx, env, name, buildArgs...)
}

// WithSHA is a wrapper for build functions that adds SHA256Sum calculation.
func WithSHA(info BuildInfo, err error) (BuildInfo, error) {
if err != nil {
return BuildInfo{}, err
}

return info, SHA256Sum(info.BinPath, info.BinPath+".sha256")
}

func BuildForTesting(ctx context.Context, name string) (BuildInfo, error) {
env := map[string]string{
"CGO_ENABLED": "1",
}

pkgs, err := ListPackages(ctx, "./...")
if err != nil {
return BuildInfo{}, err
}

args := []string{"-race", "-cover", "-covermode", "atomic", "-coverpkg=" + strings.Join(pkgs, ",")}
return build(ctx, env, TestBinDir, name, args...)
}

// BuildFromMatrixWithSHA is a higher level build utility function doing cross compilation with sha calculation.
func BuildFromMatrixWithSHA(ctx context.Context, env map[string]string, matrix BuildMatrix, name string, buildArgs ...string) ([]BuildInfo, error) {
info := make([]BuildInfo, 0, len(matrix))
for _, m := range matrix {
i, err := BuildForPlatformWith(ctx, env, m.OS, m.Arch, name, buildArgs...)
if err != nil {
return nil, err
}
info = append(info, i)
}

return info, nil
}

// BuildTargets returns list packages under ./cmd/.
func BuildTargets(ctx context.Context) ([]string, error) {
entries, err := os.ReadDir("./cmd/")
if err != nil {
return nil, err
}

targets := make([]string, 0, len(entries))
for _, e := range entries {
if e.IsDir() {
targets = append(targets, e.Name())
}
}
return targets, nil
}

// ListPackages lists all packages in given target.
func ListPackages(ctx context.Context, target string) ([]string, error) {
pkgsRaw, err := sh.Output("go", "list", target)
if err != nil {
return nil, err
}
pkgs := strings.Split(strings.ReplaceAll(pkgsRaw, "\r\n", ","), "\n")
return pkgs, nil
}

// SHA256Sum calculates sum for input file and stores it in output file.
// Output should be compatible with sha256sum program.
func SHA256Sum(input, output string) error {
f, err := os.Open(input)
if err != nil {
return err
}
defer f.Close()

data, err := io.ReadAll(f)
if err != nil {
return err
}
sum := sha256.Sum256(data)
hexSum := hex.EncodeToString(sum[:])

sumFile, err := os.Create(output)
if err != nil {
return err
}
defer f.Close()

_, err = fmt.Fprintf(sumFile, "%s *%s\n", hexSum, input)
if err != nil {
return err
}

return nil
}

func build(ctx context.Context, env map[string]string, base, name string, buildArgs ...string) (BuildInfo, error) {
info, err := prepareBuildInfo(env, base, name)
if err != nil {
return BuildInfo{}, err
}

args := append([]string{"build", "-o", info.BinPath}, append(buildArgs, "./cmd/"+name)...)
if err = GoWith(ctx, env, args...); err != nil {
return BuildInfo{}, err
}

return info, nil
}
func prepareBuildInfo(env map[string]string, base, name string) (BuildInfo, error) {
goos, err := sh.Output("go", "env", "GOOS")
if err != nil {
return BuildInfo{}, err
}

goarch, err := sh.Output("go", "env", "GOARCH")
if err != nil {
return BuildInfo{}, err
}

if envOS, ok := env["GOOS"]; ok {
goos = envOS
}

if envArch, ok := env["GOARCH"]; ok {
goarch = envArch
}

binaryPath := path.Join(base, goos, goarch, name)
return BuildInfo{BinPath: binaryPath, GOOS: goos, GOARCH: goarch}, nil
}
37 changes: 37 additions & 0 deletions golang/go.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package golang

import (
"context"
"fmt"

"github.com/elisasre/mageutil/git"
"github.com/magefile/mage/sh"
)

// Go is shorthand for go executable provided by system.
func Go(ctx context.Context, args ...string) error {
return GoWith(ctx, nil, args...)
}

// GoWith is shorthand for go executable provided by system.
func GoWith(ctx context.Context, env map[string]string, args ...string) error {
fmt.Printf("env: %s\n", env)
return sh.RunWithV(env, "go", args...)
}

// Tidy runs go mod tidy.
func Tidy(ctx context.Context) error {
return Go(ctx, "mod", "tidy")
}

// TidyAndVerify runs go mod tidy and verifies that there are no changes to go.mod or go.sum.
// This is useful in CI/CD pipelines to validate that dependencies match go.mod.
func TidyAndVerify(ctx context.Context) error {
if err := Tidy(ctx); err != nil {
return err
}
if err := git.Git(ctx, "diff", "--exit-code", "--", "go.mod", "go.sum"); err != nil {
return fmt.Errorf("go.mod and go.sum are not in sync. run `go mod tidy` and commit changes")
}
return nil
}
Loading

0 comments on commit 53f854d

Please sign in to comment.