build-patterns.md

Build Patterns

Language-specific templates for AUR packages. These templates use cd "${pkgname}-${pkgver}", which suits suffix-less and -bin packages. For -git packages, use cd "$pkgname" instead (the source entry "$pkgname::git+..." clones into $pkgname).

Based on the Arch Wiki's Go and Rust package guidelines.

Go

makedepends=('go')

Module caching

Keep Go modules in the build environment rather than polluting ~/go:

prepare() {
  cd "${pkgname}-${pkgver}"
  mkdir -p build/
  export GOPATH="${srcdir}"
  go mod download -modcacherw
}

If upstream lacks go.mod, initialise it before downloading (file an issue upstream):

prepare() {
  cd "$pkgname-$pkgver"
  go mod init "${url#https://}"
  go mod tidy
}

Building

The standard approach exports CGO flags and uses GOFLAGS:

build() {
  cd "${pkgname}-${pkgver}"
  export CGO_CPPFLAGS="${CPPFLAGS}"
  export CGO_CFLAGS="${CFLAGS}"
  export CGO_CXXFLAGS="${CXXFLAGS}"
  export CGO_LDFLAGS="${LDFLAGS}"
  export GOFLAGS="-buildmode=pie -trimpath -ldflags=-linkmode=external -mod=readonly -modcacherw"
  go build -o build .
}

When upstream embeds version info via -ldflags, pass flags explicitly instead:

build() {
  cd "${pkgname}-${pkgver}"
  export CGO_CPPFLAGS="${CPPFLAGS}"
  export CGO_CFLAGS="${CFLAGS}"
  export CGO_CXXFLAGS="${CXXFLAGS}"
  export CGO_LDFLAGS="${LDFLAGS}"
  go build \
      -trimpath \
      -buildmode=pie \
      -mod=readonly \
      -modcacherw \
      -o build/"$pkgname" \
      -ldflags "-linkmode external -extldflags \"${LDFLAGS}\"" \
      .
}

Use -o build with a mkdir -p build/ in prepare() to control output location. For projects with multiple binaries: go build -o build ./cmd/...

If upstream uses a Makefile, verify it passes hardening flags — many overwrite GOFLAGS. Bypass the Makefile if needed and invoke go build directly.

Testing and packaging

check() {
  cd "${pkgname}-${pkgver}"
  go test ./...
}

package() {
  cd "${pkgname}-${pkgver}"
  install -Dm755 build/"$pkgname" "$pkgdir"/usr/bin/"$pkgname"
}

FULL RELRO

If namcap reports ELF file lacks FULL RELRO, add -bindnow to ldflags (requires Go ≥ 1.23):

-ldflags "-linkmode external -extldflags \"${LDFLAGS}\" -bindnow"

Or in the GOFLAGS style:

export GOFLAGS="-buildmode=pie -trimpath -ldflags='-linkmode=external -bindnow' -mod=readonly -modcacherw"

Vendored dependencies

If upstream ships a vendor/ directory with modules.txt, change -mod=readonly to -mod=vendor.

Rust

makedepends=('cargo')

Source

Prefer source tarballs or GitHub release archives. When unavailable, use crates.io:

source=("$pkgname-$pkgver.tar.gz::https://static.crates.io/crates/$pkgname/$pkgname-$pkgver.crate")

Note: crates.io archives often lack test files, license files, and other assets.

Prepare, build, check

prepare() {
  cd "$pkgname-$pkgver"
  export RUSTUP_TOOLCHAIN=stable
  cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}

build() {
  cd "$pkgname-$pkgver"
  export RUSTUP_TOOLCHAIN=stable
  export CARGO_TARGET_DIR=target
  cargo build --frozen --release --all-features
}

check() {
  cd "$pkgname-$pkgver"
  export RUSTUP_TOOLCHAIN=stable
  export CARGO_TARGET_DIR=target
  cargo test --frozen --all-features
}
  • --locked / --frozen: Respect Cargo.lock exactly for reproducible builds.
  • CARGO_TARGET_DIR=target: Avoids user-preference side effects outside chroots.
  • RUSTUP_TOOLCHAIN=stable: Ensures default toolchain (unnecessary if upstream has rust-toolchain.toml).
  • Do not use --release for check() — it enables optimisations that hide bugs.
  • For cargo workspaces (check for [workspace] in Cargo.toml), add --workspace to cargo test.

Packaging

package() {
  cd "$pkgname-$pkgver"
  install -Dm0755 -t "$pkgdir/usr/bin/" "target/release/$pkgname"
}

For multiple binaries:

package() {
  cd "$pkgname-$pkgver"
  find target/release \
      -maxdepth 1 \
      -executable \
      -type f \
      -exec install -Dm0755 -t "$pkgdir/usr/bin/" {} +
}

VCS packages

For -git packages where upstream doesn't keep Cargo.lock in sync between releases, add cargo update before cargo fetch in prepare().