build/mage/golang/build.go (132 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. package golang import ( "errors" "fmt" "os" "runtime" "strings" "time" "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "github.com/elastic/harp/build/mage/git" ) type buildOpts struct { binaryName string packageName string cgoEnabled bool pieEnabled bool goOS string goArch string goArm string } // BuildOption is used to define function option pattern. type BuildOption func(*buildOpts) // ----------------------------------------------------------------------------- // WithCGO enables CGO compilation func WithCGO() BuildOption { return func(opts *buildOpts) { opts.cgoEnabled = true } } // WithPIE enables Position Independent Executable compilation func WithPIE() BuildOption { return func(opts *buildOpts) { opts.pieEnabled = true } } // GOOS sets the GOOS value during build func GOOS(value string) BuildOption { return func(opts *buildOpts) { opts.goOS = value } } // GOARCH sets the GOARCH value during build func GOARCH(value string) BuildOption { return func(opts *buildOpts) { opts.goArch = value } } // GOARM sets the GOARM value during build func GOARM(value string) BuildOption { return func(opts *buildOpts) { opts.goArm = value } } // ----------------------------------------------------------------------------- // Build the given binary using the given package. // //nolint:funlen // to refactor func Build(name, packageName, version string, opts ...BuildOption) func() error { const ( defaultCgoEnabled = false defaultGoOs = runtime.GOOS defaultGoArch = runtime.GOARCH defaultGoArm = "" ) // Default build options defaultOpts := &buildOpts{ binaryName: name, packageName: packageName, cgoEnabled: defaultCgoEnabled, goOS: defaultGoOs, goArch: defaultGoArch, goArm: defaultGoArm, } // Apply options for _, o := range opts { o(defaultOpts) } return func() error { // Retrieve git info first mg.SerialDeps(git.CollectInfo) // Generate artifact name artifactName := fmt.Sprintf("%s-%s-%s%s", name, defaultOpts.goOS, defaultOpts.goArch, defaultOpts.goArm) // Compilation flags compilationFlags := []string{} // Check if fips is enabled buildTags := "-tags=!fips" if os.Getenv("HARP_BUILD_FIPS_MODE") == "1" { artifactName = fmt.Sprintf("%s-fips", artifactName) compilationFlags = append(compilationFlags, "fips") buildTags = "-tags=fips" } // Check if CGO is enabled if defaultOpts.cgoEnabled { artifactName = fmt.Sprintf("%s-cgo", artifactName) compilationFlags = append(compilationFlags, "cgo") } // Enable PIE if requested buildMode := "-buildmode=exe" if defaultOpts.pieEnabled { buildMode = "-buildmode=pie" artifactName = fmt.Sprintf("%s-pie", artifactName) compilationFlags = append(compilationFlags, "pie") } // Check compilation flags strCompilationFlags := "defaults" if len(compilationFlags) > 0 { strCompilationFlags = strings.Join(compilationFlags, ",") } // Inject version information varsSetByLinker := map[string]string{ "github.com/elastic/harp/build/version.Name": name, "github.com/elastic/harp/build/version.AppName": packageName, "github.com/elastic/harp/build/version.Version": version, "github.com/elastic/harp/build/version.Commit": git.Revision, "github.com/elastic/harp/build/version.Branch": git.Branch, "github.com/elastic/harp/build/version.BuildDate": time.Now().Format(time.RFC3339), "github.com/elastic/harp/build/version.BuildTags": strCompilationFlags, } var linkerArgs []string for name, value := range varsSetByLinker { linkerArgs = append(linkerArgs, "-X", fmt.Sprintf("'%s=%s'", name, value)) } // Strip and remove DWARF linkerArgs = append(linkerArgs, "-s", "-w") // Assemble ldflags ldflagsValue := strings.Join(linkerArgs, " ") // Build environment env := map[string]string{ "GOOS": defaultOpts.goOS, "GOARCH": defaultOpts.goArch, "CGO_ENABLED": "0", } if defaultOpts.cgoEnabled { env["CGO_ENABLED"] = "1" } if defaultOpts.goArm != "" { env["GOARM"] = defaultOpts.goArm } // Create output directory if errMkDir := os.Mkdir("bin", 0o744); errMkDir != nil { if !errors.Is(errMkDir, os.ErrExist) { return fmt.Errorf("unable to create output directory: %w", errMkDir) } } // Generate output filename filename := fmt.Sprintf("bin/%s", artifactName) if defaultOpts.goOS == "windows" { filename = fmt.Sprintf("%s.exe", filename) } fmt.Fprintf(os.Stdout, " > Generating SBOM %s [%s] [os:%s arch:%s%s flags:%v tag:%v]\n", defaultOpts.binaryName, defaultOpts.packageName, defaultOpts.goOS, defaultOpts.goArch, defaultOpts.goArm, strCompilationFlags, version) // Generate SBOM if err := sh.RunWith(env, "cyclonedx-gomod", "app", "-json", "-output", fmt.Sprintf("%s.sbom.json", filename), "-files", "-licenses", "-main", fmt.Sprintf("cmd/%s", defaultOpts.binaryName), "-packages"); err != nil { return fmt.Errorf("unable to generate SBOM for artifact: %w", err) } fmt.Fprintf(os.Stdout, " > Building %s [%s] [os:%s arch:%s%s flags:%v tag:%v]\n", defaultOpts.binaryName, defaultOpts.packageName, defaultOpts.goOS, defaultOpts.goArch, defaultOpts.goArm, strCompilationFlags, version) // Compile return sh.RunWith(env, "go", "build", buildMode, buildTags, "-trimpath", "-mod=readonly", "-ldflags", ldflagsValue, "-o", filename, packageName) } }