236 lines
6.4 KiB
Go
236 lines
6.4 KiB
Go
// Copyright (c) 2024 Joshua Rich <joshua.rich@gmail.com>
|
|
//
|
|
// This software is released under the MIT License.
|
|
// https://opensource.org/licenses/MIT
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"slices"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/magefile/mage/sh"
|
|
)
|
|
|
|
// isCI checks whether we are currently running as part of a CI pipeline (i.e.
|
|
// in a GitHub runner).
|
|
func isCI() bool {
|
|
return os.Getenv("CI") != ""
|
|
}
|
|
|
|
// isRoot checks whether we are running as the root user or with elevated
|
|
// privileges.
|
|
func isRoot() bool {
|
|
euid := syscall.Geteuid()
|
|
uid := syscall.Getuid()
|
|
egid := syscall.Getegid()
|
|
gid := syscall.Getgid()
|
|
|
|
if uid != euid || gid != egid || uid == 0 {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// sudoWrap will "wrap" the given command with sudo if needed.
|
|
func sudoWrap(cmd string, args ...string) error {
|
|
if isRoot() {
|
|
if err := sh.RunV(cmd, args...); err != nil {
|
|
return fmt.Errorf("could not run command: %w", err)
|
|
}
|
|
} else {
|
|
if err := sh.RunV("sudo", slices.Concat([]string{cmd}, args)...); err != nil {
|
|
return fmt.Errorf("could not run command: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getFlags gets all the compile flags to set the version and stuff.
|
|
func getFlags() (string, error) {
|
|
// pkgPath is where flags are derived from.
|
|
pkgPath := "github.com/joshuar/go-hass-agent/internal/preferences"
|
|
|
|
var version, hash, date string
|
|
|
|
var err error
|
|
|
|
if version, err = getVersion(); err != nil {
|
|
return "", fmt.Errorf("failed to retrieve version from git: %w", err)
|
|
}
|
|
|
|
if hash, err = getGitHash(); err != nil {
|
|
return "", fmt.Errorf("failed to retrieve hash from git: %w", err)
|
|
}
|
|
|
|
if date, err = getBuildDate(); err != nil {
|
|
return "", fmt.Errorf("failed to retrieve build date from git: %w", err)
|
|
}
|
|
|
|
var flags strings.Builder
|
|
|
|
flags.WriteString("-X " + pkgPath + ".gitVersion=" + version)
|
|
flags.WriteString(" ")
|
|
flags.WriteString("-X " + pkgPath + ".gitCommit=" + hash)
|
|
flags.WriteString(" ")
|
|
flags.WriteString("-X " + pkgPath + ".buildDate=" + date)
|
|
|
|
return flags.String(), nil
|
|
}
|
|
|
|
// getVersion returns a string that can be used as a version string.
|
|
func getVersion() (string, error) {
|
|
// Use the version already set in the environment (i.e., by the CI run).
|
|
if version, ok := os.LookupEnv("APPVERSION"); ok {
|
|
return version, nil
|
|
}
|
|
// Else, derive a version from git.
|
|
version, err := sh.Output("git", "describe", "--tags", "--always", "--dirty")
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not get version from git: %w", err)
|
|
}
|
|
|
|
return version, nil
|
|
}
|
|
|
|
// getGitHash returns the git hash for the current repo or "" if none.
|
|
func getGitHash() (string, error) {
|
|
hash, err := sh.Output("git", "rev-parse", "--short", "HEAD")
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not get git hash: %w", err)
|
|
}
|
|
|
|
return hash, nil
|
|
}
|
|
|
|
// getBuildDate returns the build date.
|
|
func getBuildDate() (string, error) {
|
|
date, err := sh.Output("git", "log", "--date=iso8601-strict", "-1", "--pretty=%ct")
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not get build date from git: %w", err)
|
|
}
|
|
|
|
return date, nil
|
|
}
|
|
|
|
// generateBuildEnv will create a map[string]string containing environment
|
|
// variables and their values necessary for building Go Hass Agent on the given
|
|
// architecture.
|
|
func generateBuildEnv() (map[string]string, error) {
|
|
envMap := make(map[string]string)
|
|
|
|
// CGO_ENABLED is required.
|
|
envMap["CGO_ENABLED"] = "1"
|
|
|
|
version, err := getVersion()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not generate environment: %w", err)
|
|
}
|
|
// Set APPVERSION to current version.
|
|
envMap["APPVERSION"] = version
|
|
|
|
// Get the value of BUILDPLATFORM (if set) from the environment, which
|
|
// indicates cross-compilation has been requested.
|
|
_, arch, ver := parseBuildPlatform()
|
|
|
|
if arch != "" && arch != runtime.GOARCH {
|
|
slog.Info("Setting up cross-compilation.")
|
|
// Set additional build-related variables based on the target arch.
|
|
switch arch {
|
|
case "arm":
|
|
envMap["CC"] = "arm-linux-gnueabihf-gcc"
|
|
envMap["PKG_CONFIG_PATH"] = "/usr/lib/arm-linux-gnueabihf/pkgconfig"
|
|
envMap["GOARCH"] = arch
|
|
envMap["GOARM"] = ver
|
|
envMap["PLATFORMPAIR"] = arch + ver
|
|
case "arm64":
|
|
envMap["CC"] = "aarch64-linux-gnu-gcc"
|
|
envMap["PKG_CONFIG_PATH"] = "/usr/lib/aarch64-linux-gnu/pkgconfig"
|
|
envMap["GOARCH"] = arch
|
|
envMap["PLATFORMPAIR"] = arch
|
|
default:
|
|
return nil, ErrUnsupportedArch
|
|
}
|
|
} else {
|
|
envMap["GOARCH"] = runtime.GOARCH
|
|
envMap["PLATFORMPAIR"] = runtime.GOARCH
|
|
}
|
|
|
|
// Set an appropriate output file based on the arch to build for.
|
|
envMap["OUTPUT"] = filepath.Join(distPath, "/go-hass-agent-"+envMap["PLATFORMPAIR"])
|
|
|
|
return envMap, nil
|
|
}
|
|
|
|
// generatePkgEnv will create a map[string]string containing environment
|
|
// variables and their values necessary for packaging Go Hass Agent on the given
|
|
// architecture.
|
|
func generatePkgEnv() (map[string]string, error) {
|
|
envMap := make(map[string]string)
|
|
|
|
version, err := getVersion()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not generate env: %w", err)
|
|
}
|
|
// Set APPVERSION to current version.
|
|
envMap["APPVERSION"] = version
|
|
// Set NFPM_ARCH so that nfpm knows how to package for this arch.
|
|
envMap["NFPM_ARCH"] = runtime.GOARCH
|
|
// Parse the build platform from the environment.
|
|
_, arch, ver := parseBuildPlatform()
|
|
// For arm, set the NFPM_ARCH to include the revision.
|
|
if arch != "" && arch != runtime.GOARCH {
|
|
slog.Info("Setting up cross-compilation.")
|
|
// Update NFPM_ARCH to the target arch.
|
|
envMap["NFPM_ARCH"] = arch + ver
|
|
}
|
|
|
|
return envMap, nil
|
|
}
|
|
|
|
// parseBuildPlatform reads the TARGETPLATFORM environment variable, which should
|
|
// always be set, and extracts the value into appropriate GOOS, GOARCH and GOARM
|
|
// (if applicable) variables.
|
|
func parseBuildPlatform() (operatingsystem, architecture, version string) {
|
|
var buildPlatform string
|
|
|
|
var ok bool
|
|
|
|
if buildPlatform, ok = os.LookupEnv(platformENV); !ok {
|
|
return runtime.GOOS, runtime.GOARCH, ""
|
|
}
|
|
|
|
buildComponents := strings.Split(buildPlatform, "/")
|
|
operatingsystem = buildComponents[0]
|
|
|
|
if len(buildComponents) > 1 {
|
|
architecture = buildComponents[1]
|
|
}
|
|
|
|
if len(buildComponents) > 2 {
|
|
version = strings.TrimPrefix(buildComponents[2], "v")
|
|
}
|
|
|
|
return operatingsystem, architecture, version
|
|
}
|
|
|
|
func cleanDir(path string) error {
|
|
if err := os.RemoveAll(path); err != nil {
|
|
return fmt.Errorf("could not clean directory %s: %w", path, err)
|
|
}
|
|
|
|
if err := os.MkdirAll(path, 0o755); err != nil {
|
|
return fmt.Errorf("could not create directory %s: %w", path, err)
|
|
}
|
|
|
|
return nil
|
|
}
|