diff --git a/build.go b/build.go index 45e1bff35ae..50b8e4e2c35 100644 --- a/build.go +++ b/build.go @@ -3,476 +3,14 @@ package main import ( - "bytes" - "crypto/md5" - "crypto/sha256" - "encoding/json" - "flag" - "fmt" - "go/build" - "io" - "io/ioutil" "log" "os" - "os/exec" - "path" - "path/filepath" - "runtime" - "strconv" - "strings" - "time" -) - -const ( - windows = "windows" - linux = "linux" -) -var ( - //versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`) - goarch string - goos string - gocc string - cgo bool - libc string - pkgArch string - version string = "v1" - buildTags []string - // deb & rpm does not support semver so have to handle their version a little differently - linuxPackageVersion string = "v1" - linuxPackageIteration string = "" - race bool - workingDir string - includeBuildId bool = true - buildId string = "0" - serverBinary string = "grafana-server" - cliBinary string = "grafana-cli" - binaries []string = []string{serverBinary, cliBinary} - isDev bool = false - enterprise bool = false - skipRpmGen bool = false - skipDebGen bool = false - printGenVersion bool = false + "github.com/grafana/grafana/pkg/build" ) func main() { log.SetOutput(os.Stdout) log.SetFlags(0) - - var buildIdRaw string - var buildTagsRaw string - - flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH") - flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS") - flag.StringVar(&gocc, "cc", "", "CC") - flag.StringVar(&libc, "libc", "", "LIBC") - flag.StringVar(&buildTagsRaw, "build-tags", "", "Sets custom build tags") - flag.BoolVar(&cgo, "cgo-enabled", cgo, "Enable cgo") - flag.StringVar(&pkgArch, "pkg-arch", "", "PKG ARCH") - flag.BoolVar(&race, "race", race, "Use race detector") - flag.BoolVar(&includeBuildId, "includeBuildId", includeBuildId, "IncludeBuildId in package name") - flag.BoolVar(&enterprise, "enterprise", enterprise, "Build enterprise version of Grafana") - flag.StringVar(&buildIdRaw, "buildId", "0", "Build ID from CI system") - flag.BoolVar(&isDev, "dev", isDev, "optimal for development, skips certain steps") - flag.BoolVar(&skipRpmGen, "skipRpm", skipRpmGen, "skip rpm package generation (default: false)") - flag.BoolVar(&skipDebGen, "skipDeb", skipDebGen, "skip deb package generation (default: false)") - flag.BoolVar(&printGenVersion, "gen-version", printGenVersion, "generate Grafana version and output (default: false)") - flag.Parse() - - buildId = shortenBuildId(buildIdRaw) - - readVersionFromPackageJson() - - if pkgArch == "" { - pkgArch = goarch - } - - if printGenVersion { - printGeneratedVersion() - return - } - - if len(buildTagsRaw) > 0 { - buildTags = strings.Split(buildTagsRaw, ",") - } - - log.Printf("Version: %s, Linux Version: %s, Package Iteration: %s\n", version, linuxPackageVersion, linuxPackageIteration) - - if flag.NArg() == 0 { - log.Println("Usage: go run build.go build") - return - } - - workingDir, _ = os.Getwd() - - for _, cmd := range flag.Args() { - switch cmd { - case "setup": - setup() - - case "build-srv", "build-server": - clean() - doBuild("grafana-server", "./pkg/cmd/grafana-server", buildTags) - - case "build-cli": - clean() - doBuild("grafana-cli", "./pkg/cmd/grafana-cli", buildTags) - - case "build": - //clean() - for _, binary := range binaries { - doBuild(binary, "./pkg/cmd/"+binary, buildTags) - } - - case "build-frontend": - yarn("build") - - case "sha-dist": - shaFilesInDist() - - case "latest": - makeLatestDistCopies() - - case "clean": - clean() - - default: - log.Fatalf("Unknown command %q", cmd) - } - } -} - -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) { - runError("cp", path.Join("dist", file.Name()), fullName) - } - } - } -} - -func readVersionFromPackageJson() { - reader, err := os.Open("package.json") - if err != nil { - log.Fatal("Failed to open package.json") - return - } - defer reader.Close() - - jsonObj := map[string]interface{}{} - jsonParser := json.NewDecoder(reader) - - if err := jsonParser.Decode(&jsonObj); err != nil { - log.Fatal("Failed to decode package.json") - } - - version = jsonObj["version"].(string) - linuxPackageVersion = version - linuxPackageIteration = "" - - // handle pre version stuff (deb / rpm does not support semver) - parts := strings.Split(version, "-") - - if len(parts) > 1 { - linuxPackageVersion = parts[0] - linuxPackageIteration = parts[1] - } - - // add timestamp to iteration - if includeBuildId { - if buildId != "0" { - linuxPackageIteration = fmt.Sprintf("%s%s", buildId, linuxPackageIteration) - } else { - linuxPackageIteration = fmt.Sprintf("%d%s", time.Now().Unix(), linuxPackageIteration) - } - } -} - -func yarn(params ...string) { - runPrint(`yarn run`, params...) -} - -func genPackageVersion() string { - if includeBuildId { - return fmt.Sprintf("%v-%v", linuxPackageVersion, linuxPackageIteration) - } else { - return version - } -} - -func setup() { - args := []string{"install", "-v"} - if goos == windows { - args = append(args, "-buildmode=exe") - } - args = append(args, "./pkg/cmd/grafana-server") - runPrint("go", args...) -} - -func printGeneratedVersion() { - fmt.Print(genPackageVersion()) -} - -func test(pkg string) { - setBuildEnv() - args := []string{"test", "-short", "-timeout", "60s"} - if goos == windows { - args = append(args, "-buildmode=exe") - } - args = append(args, pkg) - runPrint("go", args...) -} - -func doBuild(binaryName, pkg string, tags []string) { - libcPart := "" - if libc != "" { - libcPart = fmt.Sprintf("-%s", libc) - } - binary := fmt.Sprintf("./bin/%s-%s%s/%s", goos, goarch, libcPart, binaryName) - if isDev { - //don't include os/arch/libc in output path in dev environment - binary = fmt.Sprintf("./bin/%s", binaryName) - } - - if goos == windows { - binary += ".exe" - } - - if !isDev { - rmr(binary, binary+".md5") - } - args := []string{"build", "-ldflags", ldflags()} - if goos == windows { - // Work around a linking error on Windows: "export ordinal too large" - args = append(args, "-buildmode=exe") - } - if len(tags) > 0 { - args = append(args, "-tags", strings.Join(tags, ",")) - } - if race { - args = append(args, "-race") - } - - args = append(args, "-o", binary) - args = append(args, pkg) - - if !isDev { - setBuildEnv() - runPrint("go", "version") - libcPart := "" - if libc != "" { - libcPart = fmt.Sprintf("/%s", libc) - } - fmt.Printf("Targeting %s/%s%s\n", goos, goarch, libcPart) - } - - runPrint("go", args...) - - if !isDev { - // Create an md5 checksum of the binary, to be included in the archive for - // automatic upgrades. - err := md5File(binary) - if err != nil { - log.Fatal(err) - } - } -} - -func ldflags() string { - var b bytes.Buffer - b.WriteString("-w") - b.WriteString(fmt.Sprintf(" -X main.version=%s", 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() -} - -func rmr(paths ...string) { - for _, path := range paths { - log.Println("rm -r", path) - os.RemoveAll(path) - } -} - -func clean() { - if isDev { - return - } - - rmr("dist") - rmr("tmp") - rmr(filepath.Join(build.Default.GOPATH, fmt.Sprintf("pkg/%s_%s/github.com/grafana", goos, goarch))) -} - -func setBuildEnv() { - os.Setenv("GOOS", goos) - if goos == windows { - // require windows >=7 - os.Setenv("CGO_CFLAGS", "-D_WIN32_WINNT=0x0601") - } - if goarch != "amd64" || goos != linux { - // needed for all other archs - cgo = true - } - if strings.HasPrefix(goarch, "armv") { - os.Setenv("GOARCH", "arm") - os.Setenv("GOARM", goarch[4:]) - } else { - os.Setenv("GOARCH", goarch) - } - if cgo { - os.Setenv("CGO_ENABLED", "1") - } - if gocc != "" { - os.Setenv("CC", gocc) - } -} - -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) -} - -func buildStamp() int64 { - // use SOURCE_DATE_EPOCH if set. - if s, _ := strconv.ParseInt(os.Getenv("SOURCE_DATE_EPOCH"), 10, 64); s > 0 { - return s - } - - bs, err := runError("git", "show", "-s", "--format=%ct") - if err != nil { - return time.Now().Unix() - } - s, _ := strconv.ParseInt(string(bs), 10, 64) - return s -} - -func runError(cmd string, args ...string) ([]byte, error) { - 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, " ")) - ecmd := exec.Command(cmd, args...) - ecmd.Stdout = os.Stdout - ecmd.Stderr = os.Stderr - err := ecmd.Run() - if err != nil { - log.Fatal(err) - } -} - -func md5File(file string) error { - fd, err := os.Open(file) - if err != nil { - return err - } - defer fd.Close() - - 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() -} - -func shaFilesInDist() { - filepath.Walk("./dist", func(path string, f os.FileInfo, err error) error { - if path == "./dist" { - return nil - } - - if !strings.Contains(path, ".sha256") { - err := shaFile(path) - if err != nil { - log.Printf("Failed to create sha file. error: %v\n", err) - } - } - return nil - }) -} - -func shaFile(file string) error { - fd, err := os.Open(file) - if err != nil { - return err - } - defer fd.Close() - - h := sha256.New() - _, err = io.Copy(h, fd) - 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 shortenBuildId(buildId string) string { - buildId = strings.Replace(buildId, "-", "", -1) - if len(buildId) < 9 { - return buildId - } - return buildId[0:8] + os.Exit(build.RunCmd()) } diff --git a/pkg/build/cmd.go b/pkg/build/cmd.go new file mode 100644 index 00000000000..b70dde154cb --- /dev/null +++ b/pkg/build/cmd.go @@ -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))) +} diff --git a/pkg/build/docs.go b/pkg/build/docs.go new file mode 100644 index 00000000000..ca9549e8799 --- /dev/null +++ b/pkg/build/docs.go @@ -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 diff --git a/pkg/build/exec.go b/pkg/build/exec.go new file mode 100644 index 00000000000..ad13eb5ba99 --- /dev/null +++ b/pkg/build/exec.go @@ -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) + } +} diff --git a/pkg/build/fs.go b/pkg/build/fs.go new file mode 100644 index 00000000000..fe1eaba2577 --- /dev/null +++ b/pkg/build/fs.go @@ -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) + } + } +} diff --git a/pkg/build/git.go b/pkg/build/git.go new file mode 100644 index 00000000000..6c86d74e004 --- /dev/null +++ b/pkg/build/git.go @@ -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) +} diff --git a/pkg/build/opts.go b/pkg/build/opts.go new file mode 100644 index 00000000000..8e5486d22d1 --- /dev/null +++ b/pkg/build/opts.go @@ -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 +} diff --git a/pkg/build/version.go b/pkg/build/version.go new file mode 100644 index 00000000000..9ecb6b5b972 --- /dev/null +++ b/pkg/build/version.go @@ -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] +}