magefile.go (176 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//go:build mage
// +build mage
package main
import (
"context"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
"github.com/urso/magetools/clitool"
"github.com/urso/magetools/ctrl"
"github.com/urso/magetools/fs"
"github.com/urso/magetools/gotool"
"github.com/urso/magetools/mgenv"
"github.com/elastic/go-txfile/dev-tools/lib/mage/xbuild"
)
// Info namespace is used to print additional docs, help messages, and other info.
type Info mg.Namespace
// Prepare namespace is used to prepare/download/build common depenendencies for other tasks to run.
type Prepare mg.Namespace
// Check runs pre-build checks on the environment and source code. (e.g. linters)
type Check mg.Namespace
// Build namespace defines the set of build targets
type Build mg.Namespace
const buildHome = "build"
// environment variables
var (
envBuildOS = mgenv.String("BUILD_OS", runtime.GOOS, "(string) set compiler target GOOS")
envBuildArch = mgenv.String("BUILD_ARCH", runtime.GOARCH, "(string) set compiler target GOARCH")
envTestUseBin = mgenv.Bool("TEST_USE_BIN", false, "(bool) reuse prebuild test binary when running tests")
envTestShort = mgenv.Bool("TEST_SHORT", false, "(bool) run tests with -short flag")
envFailFast = mgenv.Bool("FAIL_FAST", false, "(bool) do not run other tasks on failure")
)
var xProviders = xbuild.NewRegistry(map[xbuild.OSArch]xbuild.Provider{
xbuild.OSArch{"linux", "arm"}: &xbuild.DockerImage{
Image: "balenalib/revpi-core-3-alpine-golang:latest-edge-build",
Workdir: "/go/src/github.com/elastic/go-txfile",
Volumes: map[string]string{
filepath.Join(os.Getenv("GOPATH"), "src"): "/go/src",
},
},
xbuild.OSArch{"linux", runtime.GOARCH}: &xbuild.DockerImage{
Image: golangVersionImage(),
Workdir: "/go/src/github.com/elastic/go-txfile",
Volumes: map[string]string{
filepath.Join(os.Getenv("GOPATH"), "src"): "/go/src",
},
},
})
// targets
// Env prints environment info
func (Info) Env() {
printTitle("Mage environment variables")
for _, k := range mgenv.Keys() {
v, _ := mgenv.Find(k)
fmt.Printf("%v=%v\n", k, v.Get())
}
fmt.Println()
printTitle("Go info")
sh.RunV(mg.GoCmd(), "env")
fmt.Println()
printTitle("docker info")
sh.RunV("docker", "version")
}
// Vars prints the list of registered environment variables
func (Info) Vars() {
for _, k := range mgenv.Keys() {
v, _ := mgenv.Find(k)
fmt.Printf("%v=%v : %v\n", k, v.Default(), v.Doc())
}
}
// All runs all Prepare tasks
func (Prepare) All() { mg.Deps(Prepare.Dirs) }
// Dirs creates requires build directories for storing artifacts
func (Prepare) Dirs() error { return fs.MakeDirs("build") }
// Lint runs golint
func (Check) Lint() error {
return errors.New("TODO: implement me")
}
// Clean removes build artifacts
func Clean() error {
return sh.Rm(buildHome)
}
// Test builds the per package unit test executables.
func (Build) Test() error {
mg.Deps(Prepare.Dirs)
goRun := gotool.New(clitool.NewCLIExecutor(true), mg.GoCmd())
return ctrl.ForEachFrom(goRun.List.ProjectPackages, failFastEach, func(pkg string) error {
fmt.Println("Compile test binary for package", pkg)
tst := goRun.Test
return tst(
context.Background(),
tst.OS(envBuildOS),
tst.ARCH(envBuildArch),
tst.Create(true),
tst.Out(path.Join(buildHome, pkg, path.Base(pkg))),
tst.WithCoverage(""),
tst.Package(pkg),
clitool.Flag("-timeout", "30m"),
)
})
}
// Shell tries to start an interactive shell if a crossbuild environment is configured.
func (Build) Shell() error {
if !crossBuild() {
return errors.New("No cross build environment configured")
}
return withXProvider((xbuild.Provider).Shell)
}
// Test runs the unit tests.
func Test() error {
mg.Deps(Prepare.Dirs)
return withExecEnv(func(local, runner clitool.Executor) error {
testUseBin := envTestUseBin
if crossBuild() {
mg.Deps(Build.Test)
testUseBin = true
}
goLocal := gotool.New(local, mg.GoCmd())
goRun := gotool.New(runner, mg.GoCmd())
return ctrl.ForEachFrom(goLocal.List.ProjectPackages, failFastEach, func(pkg string) error {
fmt.Println("Test:", pkg)
if b, err := goLocal.List.HasTests(pkg); !b {
fmt.Printf("Skipping %v: No tests found\n", pkg)
return err
}
home := path.Join(buildHome, pkg)
if err := fs.MakeDirs(home); err != nil {
return err
}
tst := goRun.Test
bin := path.Join(home, path.Base(pkg))
useBinary := fs.ExistsFile(bin) && testUseBin
fmt.Printf("Run test for package '%v' (binary: %v)\n", pkg, useBinary)
return tst(
context.Background(),
tst.UseBinaryIf(bin, useBinary),
tst.WithCoverage(path.Join(home, "cover.out")),
tst.Short(envTestShort),
// tst.Out(bin), - due to bin file tests are failing on Windows, since it seems not to be used - it's disabled
tst.Package(pkg),
tst.Verbose(true),
clitool.Flag("-timeout", "30m"),
)
})
})
}
// helpers
func failFastEach(ops ...ctrl.Operation) error {
mode := ctrl.Each
if envFailFast {
mode = ctrl.Sequential
}
return mode(ops...)
}
func printTitle(s string) {
fmt.Println(s)
for range s {
fmt.Print("=")
}
fmt.Println()
}
func crossBuild() bool {
return envBuildArch != runtime.GOARCH || envBuildOS != runtime.GOOS
}
func withXProvider(fn func(xbuild.Provider) error) error {
return xProviders.With(envBuildOS, envBuildArch, fn)
}
func withExecEnv(fn func(local, remote clitool.Executor) error) error {
local := clitool.NewCLIExecutor(mg.Verbose())
if crossBuild() {
return withXProvider(func(p xbuild.Provider) error {
e, err := p.Executor(mg.Verbose())
if err != nil {
return err
}
return fn(local, e)
})
}
return fn(local, local)
}
func golangVersionImage() string {
version := runtime.Version()
if strings.HasPrefix(version, "go") {
version = version[2:]
} else {
version = "latest"
}
return fmt.Sprintf("golang:%v", version)
}