Skip to content

Commit

Permalink
build(mage): ✨ rework mage build system to support native and cross-c…
Browse files Browse the repository at this point in the history
…ompilation

- mage now supports setting TARGETARCH environment variable to specify cross-compilation
- update build/test github action workflows for mage changes
- update Dockerfile for mage changes
- adjust codeql github action workflow to "autobuild"
  • Loading branch information
joshuar committed May 27, 2024
1 parent 325c05c commit 7759eba
Show file tree
Hide file tree
Showing 14 changed files with 140 additions and 149 deletions.
10 changes: 7 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:

env:
GO_VERSION: 1.22
MAGEARGS: "-v -d build/magefiles -w ."
MAGEARGS: -d build/magefiles -w .

jobs:
check_release:
Expand Down Expand Up @@ -64,9 +64,13 @@ jobs:
with:
install-only: true
- name: Build with Mage
run: mage ${MAGEARGS} build:ci ${{ matrix.arch }}
run: mage ${MAGEARGS} build:ci
env:
TARGETARCH: ${{ matrix.arch }}
- name: Package with Mage
run: mage ${MAGEARGS} package:ci ${{ matrix.arch }}
run: mage ${MAGEARGS} package:ci
env:
TARGETARCH: ${{ matrix.arch }}
- name: Install cosign
id: cosign_install
uses: sigstore/[email protected]
Expand Down
14 changes: 6 additions & 8 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ permissions:

env:
GO_VERSION: 1.22
ARCH: amd64
MAGEARGS: -v -d build/magefiles -w .
MAGEARGS: -d build/magefiles -w .

jobs:
analyze:
Expand All @@ -30,7 +29,7 @@ jobs:
matrix:
include:
- language: go
build-mode: manual
build-mode: autobuild
steps:
- name: Harden Runner
uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v1
Expand All @@ -45,22 +44,21 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- if: matrix.build-mode == 'manual'
name: Install Mage
- name: Install Mage
uses: magefile/mage-action@v3
with:
install-only: true
- name: Install build deps
run: mage ${MAGEARGS} preps:deps ${ARCH}
run: mage ${MAGEARGS} preps:deps
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
packs: githubsecuritylab/codeql-${{ matrix.language }}-queries
- if: matrix.build-mode == 'manual'
name: Build with Mage
run: mage ${MAGEARGS} build:full ${ARCH}
name: Build
run: go build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ permissions:

env:
GO_VERSION: 1.22
ARCH: amd64
MAGEARGS: "-v -d build/magefiles -w ."
MAGEARGS: -v -d build/magefiles -w .

jobs:
test:
Expand All @@ -37,7 +36,7 @@ jobs:
with:
install-only: true
- name: Run tests
run: mage ${MAGEARGS} tests:ci ${ARCH}
run: mage ${MAGEARGS} tests:ci
continue-on-error: true
- name: Upload Coverage
id: upload_coverage
Expand Down Expand Up @@ -66,7 +65,7 @@ jobs:
with:
install-only: true
- name: Install build deps
run: mage ${MAGEARGS} preps:deps ${ARCH}
run: mage ${MAGEARGS} preps:deps
- name: golangci-lint
uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1
with:
Expand Down
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# https://opensource.org/licenses/MIT

ARG GO_VERSION=1.22
FROM docker.io/golang:${GO_VERSION} AS builder
FROM docker.io/golang:$GO_VERSION AS builder
WORKDIR /usr/src/go-hass-agent

ARG TARGETARCH
Expand All @@ -14,13 +14,13 @@ ADD . .
# install mage
RUN go install github.com/magefile/mage@latest
# install build dependencies
RUN mage -v -d build/magefiles -w . preps:deps ${TARGETARCH}}
RUN mage -v -d build/magefiles -w . preps:deps
# build the binary
RUN mage -v -d build/magefiles -w . build:full ${TARGETARCH}}
RUN mage -v -d build/magefiles -w . build:full

FROM ubuntu
# copy binary over from builder stage
COPY --from=builder /usr/src/go-hass-agent/dist/go-hass-agent-${TARGETARCH} /usr/bin/go-hass-agent
COPY --from=builder /usr/src/go-hass-agent/dist/go-hass-agent-$TARGETARCH /usr/bin/go-hass-agent
# reinstall minimum libraries for running
RUN mkdir /etc/dpkg/dpkg.conf.d
COPY <<EOF /etc/dpkg/dpkg.conf.d/excludes
Expand Down
32 changes: 15 additions & 17 deletions build/magefiles/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package main

import (
"errors"
"log/slog"
"fmt"

"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
Expand All @@ -18,8 +18,8 @@ type Build mg.Namespace
var ErrBuildFailed = errors.New("build failed")

// Full runs all prep steps and then builds the binary.
func (Build) Full(arch string) error {
slog.Info("Starting full build", "arch", arch)
func (Build) Full() error {
fmt.Println("Starting full build.")

// Make everything nice, neat, and proper
mg.Deps(Preps.Tidy)
Expand All @@ -29,32 +29,28 @@ func (Build) Full(arch string) error {
// Record all licenses in a registry
mg.Deps(Checks.Licenses)

return buildProject(arch)
return buildProject()
}

// Fast just builds the binary and does not run any prep steps. It will fail if
// the prep steps have not run.
func (Build) Fast(arch string) error {
return buildProject(arch)
func (Build) Fast() error {
return buildProject()
}

func (b Build) CI(arch string) error {
func (b Build) CI() error {
if !isCI() {
return ErrNotCI
}
arch, err := validateArch(arch)
if err != nil {
return err
}

mg.SerialDeps(mg.F(Preps.Deps, arch))
mg.SerialDeps(Preps.Deps)

mg.SerialDeps(mg.F(b.Full, arch))
mg.SerialDeps(b.Full)
return nil
}

func buildProject(arch string) error {
envMap, err := GenerateEnv(arch)
func buildProject() error {
envMap, err := GenerateEnv()
if err != nil {
return errors.Join(ErrBuildFailed, err)
}
Expand All @@ -64,8 +60,10 @@ func buildProject(arch string) error {
return errors.Join(ErrBuildFailed, err)
}

output := "dist/go-hass-agent-" + arch
output := "dist/go-hass-agent-" + targetArch

slog.Info("Running go build...")
fmt.Println("Running go build...")
fmt.Println("Output binary", output)
fmt.Println("Additional ldflags", ldflags)
return sh.RunWithV(envMap, "go", "build", "-ldflags="+ldflags, "-o", output)
}
10 changes: 5 additions & 5 deletions build/magefiles/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
package main

import (
"log/slog"
"fmt"
"os"

"github.com/magefile/mage/mg"
Expand All @@ -21,13 +21,13 @@ type Checks mg.Namespace

// Lint runs various static checkers to ensure you follow The Rules(tm).
func (Checks) Lint() error {
slog.Info("Running linter (go vet)...")
fmt.Println("Running linter (go vet)...")

Check failure on line 24 in build/magefiles/checks.go

View workflow job for this annotation

GitHub Actions / golangci

unhandled-error: Unhandled error in call to function fmt.Println (revive)
if err := sh.RunV("golangci-lint", "run"); err != nil {
slog.Warn("linter had problems")
fmt.Println("WARN: linter had problems")

Check failure on line 26 in build/magefiles/checks.go

View workflow job for this annotation

GitHub Actions / golangci

unhandled-error: Unhandled error in call to function fmt.Println (revive)
}

if FoundOrInstalled("staticcheck", "honnef.co/go/tools/cmd/staticcheck@latest") {
slog.Info("Running linter (staticcheck)...")
fmt.Println("Running linter (staticcheck)...")

Check failure on line 30 in build/magefiles/checks.go

View workflow job for this annotation

GitHub Actions / golangci

unhandled-error: Unhandled error in call to function fmt.Println (revive)
if err := sh.RunV("staticcheck", "-f", "stylish", "./..."); err != nil {
return err
}
Expand All @@ -38,7 +38,7 @@ func (Checks) Lint() error {

// Licenses pulls down any dependent project licenses, checking for "forbidden ones".
func (Checks) Licenses() error {
slog.Info("Running go-licenses...")
fmt.Println("Running go-licenses...")

// Make the directory for the license files
err := os.MkdirAll("licenses", os.ModePerm)
Expand Down
71 changes: 42 additions & 29 deletions build/magefiles/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ package main
import (
"errors"
"fmt"
"log/slog"
"os"
"os/exec"
"runtime"
"slices"
"strings"
"syscall"

Expand Down Expand Up @@ -42,17 +43,23 @@ func isRoot() bool {
return false
}

// SudoWrap will "wrap" the given command with sudo if needed.
func SudoWrap(cmd string, args ...string) error {
if isRoot() {
return sh.RunV(cmd, args...)
}
return sh.RunV("sudo", slices.Concat([]string{cmd}, args)...)
}

// FoundOrInstalled checks for existence then installs a file if it's not there.
func FoundOrInstalled(executableName, installURL string) (isInstalled bool) {
_, missing := exec.LookPath(executableName)
if missing != nil {
slog.Info("Installing build tool...", "tool", executableName, "url", installURL)
fmt.Println("Installing tool", executableName, "from url", installURL)
err := sh.Run("go", "install", installURL)
if err != nil {
slog.Warn("Could not install tool, skipping...", "tool", executableName)
return false
}
slog.Info("Tool installed...", "tool", executableName)
}
return true
}
Expand Down Expand Up @@ -115,41 +122,47 @@ func BuildDate() (string, error) {
// GenerateEnv will create a map[string]string containing environment variables
// and their values necessary for building the package on the given
// architecture.
func GenerateEnv(arch string) (map[string]string, error) {
func GenerateEnv() (map[string]string, error) {
envMap := make(map[string]string)

envMap["NFPM_ARCH"] = arch
// CGO_ENABLED is required.
envMap["CGO_ENABLED"] = "1"

// Set APPVERSION to current version.
version, err := Version()
if err != nil {
return nil, fmt.Errorf("could not generate environment: %w", err)
}
envMap["APPVERSION"] = version

switch arch {
case "arm":
envMap["CC"] = "arm-linux-gnueabihf-gcc"
envMap["PKG_CONFIG_PATH"] = "/usr/lib/arm-linux-gnueabihf/pkgconfig"
envMap["GOARCH"] = "arm"
envMap["GOARM"] = "7"
case "arm64":
envMap["CC"] = "aarch64-linux-gnu-gcc"
envMap["PKG_CONFIG_PATH"] = "/usr/lib/aarch64-linux-gnu/pkgconfig"
envMap["GOARCH"] = arch
}
return envMap, nil
}
// Set NFPM_ARCH so that nfpm knows how to package for this arch.
envMap["NFPM_ARCH"] = targetArch

func validateArch(arch string) (string, error) {
switch arch {
case "amd64", "linux/amd64":
return "amd64", nil
case "arm", "linux/arm/6", "linux/arm/7":
return "arm", nil
case "arm64", "linux/arm64":
return "arm64", nil
default:
return "", errors.New("unsupported arch")
// Get the value of TARGETARCH (if set) from the environment, which
// indicates cross-compilation has been requested.
if v, ok := os.LookupEnv("TARGETARCH"); ok {
targetArch = v
}
if targetArch != "" && targetArch != runtime.GOARCH {
fmt.Println("Cross compilation request from host arch", runtime.GOARCH, "to target arch", targetArch)

// Update NFPM_ARCH tro the target arch.
envMap["NFPM_ARCH"] = targetArch

// Set additional build-related variables based on the target arch.
switch targetArch {
case "arm":
envMap["CC"] = "arm-linux-gnueabihf-gcc"
envMap["PKG_CONFIG_PATH"] = "/usr/lib/arm-linux-gnueabihf/pkgconfig"
envMap["GOARCH"] = "arm"
envMap["GOARM"] = "7"
case "arm64":
envMap["CC"] = "aarch64-linux-gnu-gcc"
envMap["PKG_CONFIG_PATH"] = "/usr/lib/aarch64-linux-gnu/pkgconfig"
envMap["GOARCH"] = "arm64"
default:
return nil, fmt.Errorf("unsupport target architecture: %s", targetArch)
}
}
return envMap, nil
}
4 changes: 4 additions & 0 deletions build/magefiles/magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@

package main

import "runtime"

// Default sets the default target for Mage
// var Default = Build.All

var targetArch = runtime.GOARCH
Loading

0 comments on commit 7759eba

Please sign in to comment.