youtubebeat/vendor/github.com/elastic/beats/dev-tools/mage/crossbuild.go

278 lines
8.3 KiB
Go

// 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.
package mage
import (
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
"github.com/pkg/errors"
)
const defaultCrossBuildTarget = "golangCrossBuild"
// Platforms contains the set of target platforms for cross-builds. It can be
// modified at runtime by setting the PLATFORMS environment variable.
// See NewPlatformList for details about platform filtering expressions.
var Platforms = BuildPlatforms.Defaults()
func init() {
// Allow overriding via PLATFORMS.
if expression := os.Getenv("PLATFORMS"); len(expression) > 0 {
Platforms = NewPlatformList(expression)
}
}
// CrossBuildOption defines a option to the CrossBuild target.
type CrossBuildOption func(params *crossBuildParams)
// ImageSelectorFunc returns the name of the builder image.
type ImageSelectorFunc func(platform string) (string, error)
// ForPlatforms filters the platforms based on the given expression.
func ForPlatforms(expr string) func(params *crossBuildParams) {
return func(params *crossBuildParams) {
params.Platforms = params.Platforms.Filter(expr)
}
}
// WithTarget specifies the mage target to execute inside the golang-crossbuild
// container.
func WithTarget(target string) func(params *crossBuildParams) {
return func(params *crossBuildParams) {
params.Target = target
}
}
// InDir specifies the base directory to use when cross-building.
func InDir(path ...string) func(params *crossBuildParams) {
return func(params *crossBuildParams) {
params.InDir = filepath.Join(path...)
}
}
// Serially causes each cross-build target to be executed serially instead of
// in parallel.
func Serially() func(params *crossBuildParams) {
return func(params *crossBuildParams) {
params.Serial = true
}
}
// ImageSelector returns the name of the selected builder image.
func ImageSelector(f ImageSelectorFunc) func(params *crossBuildParams) {
return func(params *crossBuildParams) {
params.ImageSelector = f
}
}
type crossBuildParams struct {
Platforms BuildPlatformList
Target string
Serial bool
InDir string
ImageSelector ImageSelectorFunc
}
// CrossBuild executes a given build target once for each target platform.
func CrossBuild(options ...CrossBuildOption) error {
params := crossBuildParams{Platforms: Platforms, Target: defaultCrossBuildTarget, ImageSelector: crossBuildImage}
for _, opt := range options {
opt(&params)
}
// Docker is required for this target.
if err := HaveDocker(); err != nil {
return err
}
if len(params.Platforms) == 0 {
log.Printf("Skipping cross-build of target=%v because platforms list is empty.", params.Target)
return nil
}
// Build the magefile for Linux so we can run it inside the container.
mg.Deps(buildMage)
log.Println("crossBuild: Platform list =", params.Platforms)
var deps []interface{}
for _, buildPlatform := range params.Platforms {
if !buildPlatform.Flags.CanCrossBuild() {
return fmt.Errorf("unsupported cross build platform %v", buildPlatform.Name)
}
builder := GolangCrossBuilder{buildPlatform.Name, params.Target, params.InDir, params.ImageSelector}
if params.Serial {
if err := builder.Build(); err != nil {
return errors.Wrapf(err, "failed cross-building target=%v for platform=%v %v", params.ImageSelector,
params.Target, buildPlatform.Name)
}
} else {
deps = append(deps, builder.Build)
}
}
// Each build runs in parallel.
Parallel(deps...)
return nil
}
// CrossBuildXPack executes the 'golangCrossBuild' target in the Beat's
// associated x-pack directory to produce a version of the Beat that contains
// Elastic licensed content.
func CrossBuildXPack(options ...CrossBuildOption) error {
o := []CrossBuildOption{InDir("x-pack", BeatName)}
o = append(o, options...)
return CrossBuild(o...)
}
// buildMage pre-compiles the magefile to a binary using the native GOOS/GOARCH
// values for Docker. This is required to so that we can later pass GOOS and
// GOARCH to mage for the cross-build. It has the benefit of speeding up the
// build because the mage -compile is done only once rather than in each Docker
// container.
func buildMage() error {
env := map[string]string{
"GOOS": "linux",
"GOARCH": "amd64",
}
return sh.RunWith(env, "mage", "-f", "-compile", filepath.Join("build", "mage-linux-amd64"))
}
func crossBuildImage(platform string) (string, error) {
tagSuffix := "main"
switch {
case strings.HasPrefix(platform, "darwin"):
tagSuffix = "darwin"
case strings.HasPrefix(platform, "linux/arm"):
tagSuffix = "arm"
case strings.HasPrefix(platform, "linux/mips"):
tagSuffix = "mips"
case strings.HasPrefix(platform, "linux/ppc"):
tagSuffix = "ppc"
case platform == "linux/s390x":
tagSuffix = "s390x"
case strings.HasPrefix(platform, "linux"):
// Use an older version of libc to gain greater OS compatibility.
// Debian 7 uses glibc 2.13.
tagSuffix = "main-debian7"
}
goVersion, err := GoVersion()
if err != nil {
return "", err
}
return BeatsCrossBuildImage + ":" + goVersion + "-" + tagSuffix, nil
}
// GolangCrossBuilder executes the specified mage target inside of the
// associated golang-crossbuild container image for the platform.
type GolangCrossBuilder struct {
Platform string
Target string
InDir string
ImageSelector ImageSelectorFunc
}
// Build executes the build inside of Docker.
func (b GolangCrossBuilder) Build() error {
fmt.Printf(">> %v: Building for %v\n", b.Target, b.Platform)
repoInfo, err := GetProjectRepoInfo()
if err != nil {
return errors.Wrap(err, "failed to determine repo root and package sub dir")
}
mountPoint := filepath.ToSlash(filepath.Join("/go", "src", repoInfo.RootImportPath))
// use custom dir for build if given, subdir if not:
cwd := repoInfo.SubDir
if b.InDir != "" {
cwd = b.InDir
}
workDir := filepath.ToSlash(filepath.Join(mountPoint, cwd))
buildCmd, err := filepath.Rel(workDir, filepath.Join(mountPoint, repoInfo.SubDir, "build/mage-linux-amd64"))
if err != nil {
return errors.Wrap(err, "failed to determine mage-linux-amd64 relative path")
}
dockerRun := sh.RunCmd("docker", "run")
image, err := b.ImageSelector(b.Platform)
if err != nil {
return errors.Wrap(err, "failed to determine golang-crossbuild image tag")
}
verbose := ""
if mg.Verbose() {
verbose = "true"
}
var args []string
if runtime.GOOS != "windows" {
args = append(args,
"--env", "EXEC_UID="+strconv.Itoa(os.Getuid()),
"--env", "EXEC_GID="+strconv.Itoa(os.Getgid()),
)
}
args = append(args,
"--rm",
"--env", "MAGEFILE_VERBOSE="+verbose,
"--env", "MAGEFILE_TIMEOUT="+EnvOr("MAGEFILE_TIMEOUT", ""),
"-v", repoInfo.RootDir+":"+mountPoint,
"-w", workDir,
image,
"--build-cmd", buildCmd+" "+b.Target,
"-p", b.Platform,
)
return dockerRun(args...)
}
// DockerChown chowns files generated during build. EXEC_UID and EXEC_GID must
// be set in the containers environment otherwise this is a noop.
func DockerChown(path string) {
// Chown files generated during build that are root owned.
uid, _ := strconv.Atoi(EnvOr("EXEC_UID", "-1"))
gid, _ := strconv.Atoi(EnvOr("EXEC_GID", "-1"))
if uid > 0 && gid > 0 {
if err := chownPaths(uid, gid, path); err != nil {
log.Println(err)
}
}
}
// chownPaths will chown the file and all of the dirs specified in the path.
func chownPaths(uid, gid int, path string) error {
return filepath.Walk(path, func(name string, _ os.FileInfo, err error) error {
if err != nil {
return err
}
log.Printf("chown line: %s\n", name)
if err := os.Chown(name, uid, gid); err != nil {
return errors.Wrapf(err, "failed to chown path=%v", name)
}
return err
})
}