Chore: move build command to importable package (#38726)
* move build command to importable package & clean up
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
GoOSWindows = "windows"
|
||||
GoOSLinux = "linux"
|
||||
|
||||
ServerBinary = "grafana-server"
|
||||
CLIBinary = "grafana-cli"
|
||||
)
|
||||
|
||||
var binaries = []string{ServerBinary, CLIBinary}
|
||||
|
||||
func logError(message string, err error) int {
|
||||
log.Println(message, err)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// RunCmd runs the build command and returns the exit code
|
||||
func RunCmd() int {
|
||||
opts := BuildOptsFromFlags()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return logError("Error getting working directory", err)
|
||||
}
|
||||
|
||||
packageJSON, err := OpenPackageJSON(wd)
|
||||
if err != nil {
|
||||
return logError("Error opening package json", err)
|
||||
}
|
||||
|
||||
version, iteration := LinuxPackageVersion(packageJSON.Version, opts.buildID)
|
||||
|
||||
if opts.printGenVersion {
|
||||
fmt.Print(genPackageVersion(version, iteration))
|
||||
return 0
|
||||
}
|
||||
|
||||
log.Printf("Version: %s, Linux Version: %s, Package Iteration: %s\n", version, version, iteration)
|
||||
|
||||
if flag.NArg() == 0 {
|
||||
log.Println("Usage: go run build.go build")
|
||||
return 1
|
||||
}
|
||||
|
||||
for _, cmd := range flag.Args() {
|
||||
switch cmd {
|
||||
case "setup":
|
||||
setup(opts.goos)
|
||||
|
||||
case "build-srv", "build-server":
|
||||
if !opts.isDev {
|
||||
clean(opts)
|
||||
}
|
||||
|
||||
if err := doBuild("grafana-server", "./pkg/cmd/grafana-server", opts); err != nil {
|
||||
log.Println(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
case "build-cli":
|
||||
clean(opts)
|
||||
if err := doBuild("grafana-cli", "./pkg/cmd/grafana-cli", opts); err != nil {
|
||||
log.Println(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
case "build":
|
||||
//clean()
|
||||
for _, binary := range binaries {
|
||||
log.Println("building binaries", cmd)
|
||||
// Can't use filepath.Join here because filepath.Join calls filepath.Clean, which removes the `./` from this path, which upsets `go build`
|
||||
if err := doBuild(binary, fmt.Sprintf("./pkg/cmd/%s", binary), opts); err != nil {
|
||||
log.Println(err)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
case "build-frontend":
|
||||
yarn("build")
|
||||
|
||||
case "sha-dist":
|
||||
if err := shaDir("dist"); err != nil {
|
||||
return logError("error packaging dist directory", err)
|
||||
}
|
||||
|
||||
case "latest":
|
||||
makeLatestDistCopies()
|
||||
|
||||
case "clean":
|
||||
clean(opts)
|
||||
|
||||
default:
|
||||
log.Println("Unknown command", cmd)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func makeLatestDistCopies() {
|
||||
files, err := ioutil.ReadDir("dist")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create latest copies. Cannot read from /dist")
|
||||
}
|
||||
|
||||
latestMapping := map[string]string{
|
||||
"_amd64.deb": "dist/grafana_latest_amd64.deb",
|
||||
".x86_64.rpm": "dist/grafana-latest-1.x86_64.rpm",
|
||||
".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
|
||||
".linux-amd64-musl.tar.gz": "dist/grafana-latest.linux-x64-musl.tar.gz",
|
||||
".linux-armv7.tar.gz": "dist/grafana-latest.linux-armv7.tar.gz",
|
||||
".linux-armv7-musl.tar.gz": "dist/grafana-latest.linux-armv7-musl.tar.gz",
|
||||
".linux-armv6.tar.gz": "dist/grafana-latest.linux-armv6.tar.gz",
|
||||
".linux-arm64.tar.gz": "dist/grafana-latest.linux-arm64.tar.gz",
|
||||
".linux-arm64-musl.tar.gz": "dist/grafana-latest.linux-arm64-musl.tar.gz",
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
for extension, fullName := range latestMapping {
|
||||
if strings.HasSuffix(file.Name(), extension) {
|
||||
if _, err := runError("cp", path.Join("dist", file.Name()), fullName); err != nil {
|
||||
log.Println("error running cp command:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func yarn(params ...string) {
|
||||
runPrint(`yarn run`, params...)
|
||||
}
|
||||
|
||||
func genPackageVersion(version string, iteration string) string {
|
||||
if iteration != "" {
|
||||
return fmt.Sprintf("%v-%v", version, iteration)
|
||||
} else {
|
||||
return version
|
||||
}
|
||||
}
|
||||
|
||||
func setup(goos string) {
|
||||
args := []string{"install", "-v"}
|
||||
if goos == GoOSWindows {
|
||||
args = append(args, "-buildmode=exe")
|
||||
}
|
||||
args = append(args, "./pkg/cmd/grafana-server")
|
||||
runPrint("go", args...)
|
||||
}
|
||||
|
||||
func doBuild(binaryName, pkg string, opts BuildOpts) error {
|
||||
log.Println("building", binaryName, pkg)
|
||||
libcPart := ""
|
||||
if opts.libc != "" {
|
||||
libcPart = fmt.Sprintf("-%s", opts.libc)
|
||||
}
|
||||
binary := fmt.Sprintf("./bin/%s", binaryName)
|
||||
|
||||
//don't include os/arch/libc in output path in dev environment
|
||||
if !opts.isDev {
|
||||
binary = fmt.Sprintf("./bin/%s-%s%s/%s", opts.goos, opts.goarch, libcPart, binaryName)
|
||||
}
|
||||
|
||||
if opts.goos == GoOSWindows {
|
||||
binary += ".exe"
|
||||
}
|
||||
|
||||
if !opts.isDev {
|
||||
rmr(binary, binary+".md5")
|
||||
}
|
||||
|
||||
lf, err := ldflags(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := []string{"build", "-ldflags", lf}
|
||||
|
||||
if opts.goos == GoOSWindows {
|
||||
// Work around a linking error on Windows: "export ordinal too large"
|
||||
args = append(args, "-buildmode=exe")
|
||||
}
|
||||
|
||||
if len(opts.buildTags) > 0 {
|
||||
args = append(args, "-tags", strings.Join(opts.buildTags, ","))
|
||||
}
|
||||
|
||||
if opts.race {
|
||||
args = append(args, "-race")
|
||||
}
|
||||
|
||||
args = append(args, "-o", binary)
|
||||
args = append(args, pkg)
|
||||
|
||||
runPrint("go", args...)
|
||||
|
||||
if opts.isDev {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := setBuildEnv(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
runPrint("go", "version")
|
||||
libcPart = ""
|
||||
if opts.libc != "" {
|
||||
libcPart = fmt.Sprintf("/%s", opts.libc)
|
||||
}
|
||||
fmt.Printf("Targeting %s/%s%s\n", opts.goos, opts.goarch, libcPart)
|
||||
|
||||
// Create an md5 checksum of the binary, to be included in the archive for
|
||||
// automatic upgrades.
|
||||
return md5File(binary)
|
||||
}
|
||||
|
||||
func ldflags(opts BuildOpts) (string, error) {
|
||||
buildStamp, err := buildStamp()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
b.WriteString("-w")
|
||||
b.WriteString(fmt.Sprintf(" -X main.version=%s", opts.version))
|
||||
b.WriteString(fmt.Sprintf(" -X main.commit=%s", getGitSha()))
|
||||
b.WriteString(fmt.Sprintf(" -X main.buildstamp=%d", buildStamp))
|
||||
b.WriteString(fmt.Sprintf(" -X main.buildBranch=%s", getGitBranch()))
|
||||
if v := os.Getenv("LDFLAGS"); v != "" {
|
||||
b.WriteString(fmt.Sprintf(" -extldflags \"%s\"", v))
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func setBuildEnv(opts BuildOpts) error {
|
||||
if err := os.Setenv("GOOS", opts.goos); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.goos == GoOSWindows {
|
||||
// require windows >=7
|
||||
if err := os.Setenv("CGO_CFLAGS", "-D_WIN32_WINNT=0x0601"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.goarch != "amd64" || opts.goos != GoOSLinux {
|
||||
// needed for all other archs
|
||||
opts.cgo = true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(opts.goarch, "armv") {
|
||||
if err := os.Setenv("GOARCH", "arm"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Setenv("GOARM", opts.goarch[4:]); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := os.Setenv("GOARCH", opts.goarch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.cgo {
|
||||
if err := os.Setenv("CGO_ENABLED", "1"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.gocc == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return os.Setenv("CC", opts.gocc)
|
||||
}
|
||||
|
||||
func buildStamp() (int64, error) {
|
||||
// use SOURCE_DATE_EPOCH if set.
|
||||
if v, ok := os.LookupEnv("SOURCE_DATE_EPOCH"); ok {
|
||||
return strconv.ParseInt(v, 10, 64)
|
||||
}
|
||||
|
||||
bs, err := runError("git", "show", "-s", "--format=%ct")
|
||||
if err != nil {
|
||||
return time.Now().Unix(), nil
|
||||
}
|
||||
|
||||
return strconv.ParseInt(string(bs), 10, 64)
|
||||
}
|
||||
|
||||
func clean(opts BuildOpts) {
|
||||
rmr("dist")
|
||||
rmr("tmp")
|
||||
rmr(filepath.Join(build.Default.GOPATH, fmt.Sprintf("pkg/%s_%s/github.com/grafana", opts.goos, opts.goarch)))
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Package build contains the command / functions for the Grafana build process used when running the "build" target in the makefile
|
||||
package build
|
||||
@@ -0,0 +1,34 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func runError(cmd string, args ...string) ([]byte, error) {
|
||||
// Can ignore gosec G204 because this function is not used in Grafana, only in the build process.
|
||||
//nolint:gosec
|
||||
ecmd := exec.Command(cmd, args...)
|
||||
bs, err := ecmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes.TrimSpace(bs), nil
|
||||
}
|
||||
|
||||
func runPrint(cmd string, args ...string) {
|
||||
log.Println(cmd, strings.Join(args, " "))
|
||||
// Can ignore gosec G204 because this function is not used in Grafana, only in the build process.
|
||||
//nolint:gosec
|
||||
ecmd := exec.Command(cmd, args...)
|
||||
ecmd.Stdout = os.Stdout
|
||||
ecmd.Stderr = os.Stderr
|
||||
err := ecmd.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func logAndClose(c io.Closer) {
|
||||
if err := c.Close(); err != nil {
|
||||
log.Println("error closing:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func shaDir(dir string) error {
|
||||
return filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.Contains(path, ".sha256") {
|
||||
return nil
|
||||
}
|
||||
if err := shaFile(path); err != nil {
|
||||
log.Printf("Failed to create sha file. error: %v\n", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func shaFile(file string) error {
|
||||
// Can ignore gosec G304 because this function is not used in Grafana, only in the build process.
|
||||
//nolint:gosec
|
||||
r, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logAndClose(r)
|
||||
|
||||
h := sha256.New()
|
||||
_, err = io.Copy(h, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := os.Create(file + ".sha256")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(out, "%x\n", h.Sum(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func md5File(file string) error {
|
||||
// Can ignore gosec G304 because this function is not used in Grafana, only in the build process.
|
||||
//nolint:gosec
|
||||
fd, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer logAndClose(fd)
|
||||
|
||||
h := md5.New()
|
||||
_, err = io.Copy(h, fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := os.Create(file + ".md5")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(out, "%x\n", h.Sum(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
// basically `rm -r`s the list of files provided
|
||||
func rmr(paths ...string) {
|
||||
for _, path := range paths {
|
||||
log.Println("rm -r", path)
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
log.Println("error deleting folder", path, "error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package build
|
||||
|
||||
func getGitBranch() string {
|
||||
v, err := runError("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
if err != nil {
|
||||
return "main"
|
||||
}
|
||||
return string(v)
|
||||
}
|
||||
|
||||
func getGitSha() string {
|
||||
v, err := runError("git", "rev-parse", "--short", "HEAD")
|
||||
if err != nil {
|
||||
return "unknown-dev"
|
||||
}
|
||||
return string(v)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BuildOpts are options provided to the build step
|
||||
type BuildOpts struct {
|
||||
goarch string
|
||||
goos string
|
||||
gocc string
|
||||
cgo bool
|
||||
libc string
|
||||
|
||||
pkgArch string
|
||||
version string
|
||||
buildTags []string
|
||||
// deb & rpm does not support semver so have to handle their version a little differently
|
||||
race bool
|
||||
includeBuildID bool
|
||||
buildID string
|
||||
isDev bool
|
||||
enterprise bool
|
||||
skipRpmGen bool
|
||||
skipDebGen bool
|
||||
printGenVersion bool
|
||||
}
|
||||
|
||||
// BuildOptsFromFlags reads the cmd args to assemble a BuildOpts object. This function calls flag.Parse()
|
||||
func BuildOptsFromFlags() BuildOpts {
|
||||
opts := BuildOpts{}
|
||||
|
||||
var buildIDRaw string
|
||||
var buildTagsRaw string
|
||||
|
||||
flag.StringVar(&opts.goarch, "goarch", runtime.GOARCH, "GOARCH")
|
||||
flag.StringVar(&opts.goos, "goos", runtime.GOOS, "GOOS")
|
||||
flag.StringVar(&opts.gocc, "cc", "", "CC")
|
||||
flag.StringVar(&opts.libc, "libc", "", "LIBC")
|
||||
flag.StringVar(&buildTagsRaw, "build-tags", "", "Sets custom build tags")
|
||||
flag.BoolVar(&opts.cgo, "cgo-enabled", false, "Enable cgo")
|
||||
flag.StringVar(&opts.pkgArch, "pkg-arch", "", "PKG ARCH")
|
||||
flag.BoolVar(&opts.race, "race", false, "Use race detector")
|
||||
flag.BoolVar(&opts.includeBuildID, "includeBuildID", true, "IncludeBuildID in package name")
|
||||
flag.BoolVar(&opts.enterprise, "enterprise", false, "Build enterprise version of Grafana")
|
||||
flag.StringVar(&buildIDRaw, "buildID", "0", "Build ID from CI system")
|
||||
flag.BoolVar(&opts.isDev, "dev", false, "optimal for development, skips certain steps")
|
||||
flag.BoolVar(&opts.skipRpmGen, "skipRpm", false, "skip rpm package generation (default: false)")
|
||||
flag.BoolVar(&opts.skipDebGen, "skipDeb", false, "skip deb package generation (default: false)")
|
||||
flag.BoolVar(&opts.printGenVersion, "gen-version", false, "generate Grafana version and output (default: false)")
|
||||
flag.Parse()
|
||||
|
||||
opts.buildID = shortenBuildID(buildIDRaw)
|
||||
|
||||
if len(buildTagsRaw) > 0 {
|
||||
opts.buildTags = strings.Split(buildTagsRaw, ",")
|
||||
}
|
||||
|
||||
if opts.pkgArch == "" {
|
||||
opts.pkgArch = opts.goarch
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PackageJSON struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// Opens the package.json file in the provided directory and returns a struct that represents its contents
|
||||
func OpenPackageJSON(dir string) (PackageJSON, error) {
|
||||
reader, err := os.Open("package.json")
|
||||
if err != nil {
|
||||
return PackageJSON{}, err
|
||||
}
|
||||
|
||||
defer logAndClose(reader)
|
||||
|
||||
jsonObj := PackageJSON{}
|
||||
if err := json.NewDecoder(reader).Decode(&jsonObj); err != nil {
|
||||
return PackageJSON{}, err
|
||||
}
|
||||
|
||||
return jsonObj, nil
|
||||
}
|
||||
|
||||
// LinuxPackageVersion extracts the linux package version and iteration out of the version string. The version string is likely extracted from the package JSON.
|
||||
func LinuxPackageVersion(v string, buildID string) (string, string) {
|
||||
var (
|
||||
version = v
|
||||
iteration = ""
|
||||
)
|
||||
|
||||
// handle pre version stuff (deb / rpm does not support semver)
|
||||
parts := strings.Split(v, "-")
|
||||
|
||||
if len(parts) > 1 {
|
||||
version = parts[0]
|
||||
iteration = parts[1]
|
||||
}
|
||||
|
||||
if buildID == "" {
|
||||
return version, iteration
|
||||
}
|
||||
|
||||
// add timestamp to iteration
|
||||
if buildID != "0" {
|
||||
iteration = strings.Join([]string{buildID, iteration}, "")
|
||||
return version, iteration
|
||||
}
|
||||
|
||||
return version, fmt.Sprintf("%d%s", time.Now().Unix(), iteration)
|
||||
}
|
||||
|
||||
func shortenBuildID(buildID string) string {
|
||||
buildID = strings.Replace(buildID, "-", "", -1)
|
||||
if len(buildID) < 9 {
|
||||
return buildID
|
||||
}
|
||||
return buildID[0:8]
|
||||
}
|
||||
Reference in New Issue
Block a user