diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 3c32d251..00000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.aarch64-unknown-linux-gnu] -linker = "aarch64-linux-gnu-gcc" diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml deleted file mode 100644 index 30e24622..00000000 --- a/.github/workflows/binaries.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Binaries - -on: - push: - branches: ["main"] - tags: ["*"] - -env: - CARGO_TERM_COLOR: always - -jobs: - release-linux-binaries: - permissions: - contents: "read" - id-token: "write" - runs-on: ubuntu-20.04 - strategy: - matrix: - target: [x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl, aarch64-unknown-linux-gnu, aarch64-unknown-linux-musl] - include: - - target: x86_64-unknown-linux-gnu - - target: aarch64-unknown-linux-gnu - dependencies: gcc-aarch64-linux-gnu - - target: x86_64-unknown-linux-musl - dependencies: musl-tools - docker: registry.gitlab.com/rust_musl_docker/image:stable-latest - options: -v /home/runner/work/radicle-tui/radicle-tui:/workdir -v /home/runner/.cargo/git:/root/.cargo/git -v /home/runner/.cargo/registry:/root/.cargo/registry - - target: aarch64-unknown-linux-musl - docker: messense/rust-musl-cross:aarch64-musl - options: -v /home/runner/work/radicle-tui/radicle-tui:/home/rust/src -v /home/runner/.cargo/git:/root/.cargo/git -v /home/runner/.cargo/registry:/root/.cargo/registry - steps: - - name: Checkout source code - uses: actions/checkout@v4 - - name: Configure build cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} - - uses: dtolnay/rust-toolchain@1.80 - - name: Build release binaries - if: contains(matrix.target, 'gnu') - run: | - sudo apt-get update --yes && sudo apt-get install --yes ${{ matrix.dependencies }} - rustup target add "${{ matrix.target }}" - cargo build --release --target "${{ matrix.target }}" - - name: Build release binaries (statically linked) - if: contains(matrix.target, 'musl') - uses: addnab/docker-run-action@v3 - with: - image: ${{ matrix.docker }} - options: ${{ matrix.options }} - run: | - rustup target add "${{ matrix.target }}" - cargo build --release --target "${{ matrix.target }}" - - name: Upload release binaries - uses: actions/upload-artifact@v4 - with: - name: radicle-tui_${{ github.ref_name }}_${{ matrix.target }} - path: target/${{ matrix.target }}/release/rad-tui - - release-macos-binaries: - permissions: - contents: "read" - id-token: "write" - runs-on: macos-13 - strategy: - matrix: - target: - - aarch64-apple-darwin - - x86_64-apple-darwin - steps: - - uses: actions/checkout@v4 - - name: Configure build cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} - - uses: dtolnay/rust-toolchain@1.77 - - name: Build release binaries - run: | - rustup target add "${{ matrix.target }}" - cargo build --release --target "${{ matrix.target }}" - - name: Upload release binaries - uses: actions/upload-artifact@v4 - with: - name: radicle-tui_${{ github.ref_name }}_${{ matrix.target }} - path: target/${{ matrix.target }}/release/rad-tui diff --git a/.gitignore b/.gitignore index 2f315c93..f3a45816 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ # Nix /result + +# Release +build/artifacts diff --git a/build/TARGETS b/build/TARGETS new file mode 100644 index 00000000..eec92287 --- /dev/null +++ b/build/TARGETS @@ -0,0 +1,4 @@ +x86_64-unknown-linux-musl +aarch64-unknown-linux-musl +x86_64-apple-darwin +aarch64-apple-darwin diff --git a/build/build b/build/build new file mode 100755 index 00000000..298049aa --- /dev/null +++ b/build/build @@ -0,0 +1,16 @@ +#!/bin/sh +set -e + +main() { + export PACKAGE_NAME="radicle-tui" + export BINARY_NAME="rad-tui" + export CONFIG_PATH="build/config.toml" + export TARGETS_PATH="build/TARGETS" + export OUTDIR="build/artifacts" + export VERSION_CMD_ARGS="--no-forward" + + build/radicle-build/build +} + +# Run build. +main "$@" diff --git a/build/config.toml b/build/config.toml new file mode 100644 index 00000000..b50ab676 --- /dev/null +++ b/build/config.toml @@ -0,0 +1,13 @@ +[target.x86_64-unknown-linux-musl] +rustflags = [ + "-C", "codegen-units=1", + "-C", "incremental=false", + "-C", "opt-level=3", +] + +[target.aarch64-unknown-linux-musl] +rustflags = [ + "-C", "codegen-units=1", + "-C", "incremental=false", + "-C", "opt-level=3", +] diff --git a/build/radicle-build/Dockerfile b/build/radicle-build/Dockerfile new file mode 100644 index 00000000..2b935598 --- /dev/null +++ b/build/radicle-build/Dockerfile @@ -0,0 +1,91 @@ +# Builds release binaries for Radicle. +ARG RUST_VERSION="1.85" +ARG ALPINE_VERSION="3.20" + +FROM rust:${RUST_VERSION}-alpine${ALPINE_VERSION} as builder +LABEL maintainer="Erik Kundt " +WORKDIR /src +COPY . . + +ARG TZ +ARG LC_ALL +ARG SOURCE_DATE_EPOCH +ARG PACKAGE_NAME +ARG BINARY_NAME +ARG VERSION +ARG GIT_HEAD +ARG SDK_PATH +ARG CONFIG_PATH + +# Copy cargo configuration we're going to use to specify compiler options. +RUN mkdir -p .cargo && cp "$CONFIG_PATH" .cargo/config.toml +# Install dependencies. +RUN apk update && apk add --no-cache git musl-dev xz zig +# Add cargo targets. +RUN rustup target add \ + x86_64-unknown-linux-musl \ + aarch64-unknown-linux-musl \ + x86_64-apple-darwin \ + aarch64-apple-darwin + +# Install dependencies for cross-compiling to macOS. +# We use Zig as the linker to perform the compilation from a Linux host. +# Zig is not yet available on Debian, so we download the official binary. +# Compilation is done via `cargo-zigbuild` which is a wrapper around `zig`. +RUN cargo install --locked cargo-zigbuild@0.20.0 + + +# Parts of the macOS SDK are required to build Radicle, we make these available +# here. So far only `CoreFoundation` and `Security` frameworks are needed. +RUN xz -d -c "$SDK_PATH" | tar -x +# This env var is used by `cargo-zigbuild` to find the SDK. +ENV SDKROOT /src/macos-sdk-11.3 + +# Build binaries. +RUN cargo zigbuild --locked --release \ + --target=x86_64-apple-darwin \ + --target=aarch64-apple-darwin \ + --target=aarch64-unknown-linux-musl \ + --target=x86_64-unknown-linux-musl \ + -p "$PACKAGE_NAME" + +# Now copy the files to a new image without all the intermediary artifacts to +# save some space. +FROM alpine:3.20 as packager + +ARG VERSION +ARG SOURCE_DATE_EPOCH +ARG PACKAGE_NAME +ARG BINARY_NAME + +COPY --from=builder \ + /src/target/x86_64-unknown-linux-musl/release/${BINARY_NAME} \ + /builds/x86_64-unknown-linux-musl/bin/ +COPY --from=builder \ + /src/target/aarch64-unknown-linux-musl/release/${BINARY_NAME} \ + /builds/aarch64-unknown-linux-musl/bin/ +COPY --from=builder \ + /src/target/aarch64-apple-darwin/release/${BINARY_NAME} \ + /builds/aarch64-apple-darwin/bin/ +COPY --from=builder \ + /src/target/x86_64-apple-darwin/release/${BINARY_NAME} \ + /builds/x86_64-apple-darwin/bin/ + +# Create and compress reproducible archive. +WORKDIR /builds +RUN apk update && apk add --no-cache tar xz +RUN find * -maxdepth 0 -type d -exec mv '{}' "$PACKAGE_NAME-$VERSION-{}" \; && \ + find * -maxdepth 0 -type d -exec tar \ + --sort=name \ + --verbose \ + --mtime="@$SOURCE_DATE_EPOCH" \ + --owner=0 \ + --group=0 \ + --numeric-owner \ + --format=posix \ + --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime \ + --mode='go+u,go-w' \ + --remove-files \ + --create --xz \ + --file="{}.tar.xz" \ + '{}' \; diff --git a/build/radicle-build/build b/build/radicle-build/build new file mode 100755 index 00000000..03577655 --- /dev/null +++ b/build/radicle-build/build @@ -0,0 +1,100 @@ +#!/bin/sh +set -e + +main() { + # Use UTC time for everything. + export TZ=UTC0 + # Set minimal locale. + export LC_ALL=C + # Set source date. This is honored by `asciidoctor` and other tools. + SOURCE_DATE_EPOCH="$(git log -1 --pretty=%ct)" + export SOURCE_DATE_EPOCH + + if ! command -v rad > /dev/null; then + echo "fatal: rad is not installed" >&2 ; exit 1 + fi + + if ! command -v podman > /dev/null; then + echo "fatal: podman is not installed" >&2 ; exit 1 + fi + + if ! command -v sha256sum > /dev/null; then + echo "fatal: sha256sum is not installed" >&2 ; exit 1 + fi + + dir=$(dirname "${BASH_SOURCE[0]}") + version="$("$dir/version")" + rev="$(git rev-parse HEAD)" + gitarchive="$OUTDIR/$PACKAGE_NAME-$rev.tar.gz" + keypath="$(rad path)/keys/radicle.pub" + image=radicle-build-$version + rust_version="$("$dir/rust-version")" + sdk_path="$dir/macos-sdk-11.3.tar.xz" + + if [ ! -f "$keypath" ]; then + echo "fatal: no key found at $keypath" >&2 ; exit 1 + fi + # Authenticate user for signing + rad auth + + # Create output directory + mkdir -p $OUTDIR + + echo "Building $PACKAGE_NAME $version.." + echo "Creating archive of repository at $rev in $gitarchive.." + git archive --format tar.gz -o "$gitarchive" HEAD + + echo "Building image ($image).." + podman --cgroup-manager=cgroupfs build \ + --build-arg "RUST_VERSION=$rust_version" \ + --build-arg SOURCE_DATE_EPOCH \ + --build-arg TZ \ + --build-arg LC_ALL \ + --build-arg "PACKAGE_NAME=$PACKAGE_NAME" \ + --build-arg "BINARY_NAME=$BINARY_NAME" \ + --build-arg "VERSION=$version" \ + --build-arg "VERSION_CMD_ARGS=$VERSION_CMD_ARGS" \ + --build-arg "GIT_HEAD=$rev" \ + --build-arg "SDK_PATH=$sdk_path" \ + --build-arg "CONFIG_PATH=$CONFIG_PATH" \ + --arch amd64 --tag "$image" -f "$dir/Dockerfile" - < "$gitarchive" + + echo "Creating container (radicle-build-container).." + podman --cgroup-manager=cgroupfs create --ulimit=host --replace --name radicle-build-container "$image" + + # Copy build artifacts to output folder. + podman cp --overwrite radicle-build-container:/builds/. $OUTDIR/ + + while IFS= read -r target + do + echo "Signing artifacts for $target.." + + filename="$PACKAGE_NAME-$version-$target.tar.xz" + filepath="$OUTDIR/$filename" + + # Output SHA256 digest of archive. + checksum="$(cd $OUTDIR && sha256sum "$filename")" + echo "Checksum of $filepath is $(echo "$checksum" | cut -d' ' -f1)" + echo "$checksum" > "${filepath}.sha256" + + # Sign archive and verify archive. + rm -f "${filepath}.sig" # Delete existing signature + ssh-keygen -Y sign -n file -f "$keypath" "$filepath" + ssh-keygen -Y check-novalidate -n file -s "$filepath.sig" < "$filepath" + done < "$TARGETS_PATH" + + # Remove build artifacts that aren't needed anymore. + podman rm radicle-build-container > /dev/null + podman rmi --ignore "localhost/$image" +} + +# Run build. +echo "Running build.." +main "$@" + +# Show artifact checksums. +echo +$dir/checksums +echo + +echo "Build successful." diff --git a/build/radicle-build/checksums b/build/radicle-build/checksums new file mode 100755 index 00000000..2b7a8b9a --- /dev/null +++ b/build/radicle-build/checksums @@ -0,0 +1,2 @@ +#!/bin/sh +find "$OUTDIR" -type f -name '*.sha256' -exec cat {} + diff --git a/build/radicle-build/macos-sdk-11.3.tar.xz b/build/radicle-build/macos-sdk-11.3.tar.xz new file mode 100644 index 00000000..02fad1f9 Binary files /dev/null and b/build/radicle-build/macos-sdk-11.3.tar.xz differ diff --git a/build/radicle-build/rust-version b/build/radicle-build/rust-version new file mode 100755 index 00000000..e500392d --- /dev/null +++ b/build/radicle-build/rust-version @@ -0,0 +1,11 @@ +#! /usr/bin/env bash +set -euo pipefail + +if ! version=$(grep -m 1 -oP '^\s*channel\s*=\s*"?\K([\d\.]+)' rust-toolchain.toml) +then + echo "fatal: no rust version found via rust-toolchain.toml" >&2 + exit 1 +else + echo "$version" +fi + diff --git a/build/radicle-build/version b/build/radicle-build/version new file mode 100755 index 00000000..b1de9c37 --- /dev/null +++ b/build/radicle-build/version @@ -0,0 +1,9 @@ +#!/bin/sh + +if ! version="$(git describe --match='releases/*' --candidates=1 2>/dev/null)"; then + echo "fatal: no version tag found by 'git describe'" >&2 ; exit 1 +fi +# Remove the `releases/` prefix from version. +version=${version#releases/} + +echo "$version"