From 746d07932ac8ec7e3b8569767ddb8cc1f792f464 Mon Sep 17 00:00:00 2001 From: Michal Rostecki Date: Mon, 5 May 2025 23:00:54 +0200 Subject: [PATCH] ci: Build statically linked bpf-linker in CI Build LLVM and bpf-linker with statically linked libLLVM for multiple architectures. Include the resulting build artifacts on the release pages, so they can be installed with `cargo binstall` --- .github/actions/free-disk-space/action.yaml | 42 ++++++ .github/workflows/ci.yml | 147 +++++++++++++++----- .github/workflows/llvm.yml | 122 ++++++++-------- .github/workflows/release.yml | 44 ++++-- Cargo.lock | 7 + Cargo.toml | 3 + README.md | 81 +++++++++-- build-llvm.sh | 130 +++++++++++++++++ build.rs | 70 ++++++++++ 9 files changed, 530 insertions(+), 116 deletions(-) create mode 100644 .github/actions/free-disk-space/action.yaml create mode 100755 build-llvm.sh create mode 100644 build.rs diff --git a/.github/actions/free-disk-space/action.yaml b/.github/actions/free-disk-space/action.yaml new file mode 100644 index 00000000..3e2a7058 --- /dev/null +++ b/.github/actions/free-disk-space/action.yaml @@ -0,0 +1,42 @@ +name: Free disk space +description: Clean up the runner from unnecessary files and packages + +runs: + using: composite + steps: + - name: Free disk space + shell: bash + run: | + set -euxo pipefail + sudo apt -y remove \ + azure-cli \ + '^aspnetcore-.*' \ + '^dotnet-.*' \ + firefox \ + '^google-cloud-.*' \ + '^google-chrome-*' \ + '^imagemagick.*' \ + java-common \ + kubectl \ + '^libmagick.*' \ + '^libmono.*' \ + '^libnginx.*' \ + '^microsoft-edge.*' \ + '^mongodb-.*' \ + mono-devel \ + '^mysql-.*' \ + '^php.*' \ + powershell \ + '^r-.*' + sudo rm -rf \ + /opt/ghc \ + /opt/hostedtoolcache \ + /usr/lib/jvm \ + /usr/local/.ghcup \ + /usr/local/aws* \ + /usr/local/bin/{aliyun,azcopy,bicep,helm,minikube,oc,occopy,packer,pulumi*,stack,terraform} \ + /usr/local/julia* \ + /usr/local/lib/android \ + /usr/local/share/{chromium,powershell,vcpkg} \ + /usr/share/dotnet || true + sudo docker image prune --all --force || true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21b1af6e..94599707 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,7 @@ jobs: run: cargo fmt --all -- --check build: - # We don't use ubuntu-latest because we care about the apt packages available. - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.target.os }} strategy: fail-fast: false matrix: @@ -57,18 +56,37 @@ jobs: - stable - beta - nightly - llvm: - - 20 - - source - name: rustc=${{ matrix.rust }} llvm=${{ matrix.llvm }} + # We don't use ubuntu-latest because we care about the apt packages available. + target: + - os: macos-14 + target: aarch64-apple-darwin + build-type: native + - os: macos-13 + target: x86_64-apple-darwin + build-type: native + - os: ubuntu-22.04 + target: aarch64-unknown-linux-musl + build-type: cross + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + build-type: native + - os: ubuntu-22.04 + target: x86_64-unknown-linux-musl + build-type: cross + name: rustc=${{ matrix.rust }} target=${{ matrix.target.target }} needs: llvm env: RUST_BACKTRACE: full + RUSTUP_TOOLCHAIN: ${{ matrix.rust }} steps: - uses: actions/checkout@v4 + - name: Free disk space + if: runner.os == 'Linux' + uses: ./.github/actions/free-disk-space + - name: Install Rust ${{ matrix.rust }} if: matrix.rust != 'nightly' uses: dtolnay/rust-toolchain@master @@ -85,65 +103,60 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Check (default features, no system LLVM) + if: matrix.target.build-type == 'native' run: cargo check - name: Build (default features, no system LLVM) + if: matrix.target.build-type == 'native' run: cargo build - name: Install btfdump - if: matrix.rust == 'nightly' + if: matrix.rust == 'nightly' && matrix.target.build-type == 'native' run: cargo install btfdump + - name: Install icedragon + if: runner.os == 'Linux' + run: cargo install --git https://github.com/exein-io/icedragon --branch persistent-rustup + + - name: Install btfdump + if: matrix.rust == 'nightly' && matrix.target.build-type == 'cross' + run: icedragon cargo -- install btfdump + - name: Install prerequisites - if: matrix.rust == 'nightly' + if: matrix.rust == 'nightly' && runner.os == 'Linux' && startsWith(matrix.target.target, 'x86_64') # ubuntu-22.04 comes with clang 13-15[0]; support for signed and 64bit # enum values was added in clang 15[1] which isn't in `$PATH`. # # gcc-multilib provides at least which is referenced by libbpf. # + # libelf is a dependency of libbpf. + # # [0] https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2204-Readme.md # # [1] https://github.com/llvm/llvm-project/commit/dc1c43d run: | set -euxo pipefail sudo apt update - sudo apt -y install gcc-multilib + sudo apt -y install gcc-multilib libelf-dev echo /usr/lib/llvm-15/bin >> $GITHUB_PATH - - name: Install LLVM - if: matrix.llvm != 'source' + - name: Install prerequisites + if: runner.os == 'macOS' + # We need system-wide LLVM only for FileCheck. run: | set -euxo pipefail - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc - echo -e deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-${{ matrix.llvm }} main | sudo tee /etc/apt/sources.list.d/llvm.list - - sudo apt update - # TODO(vadorovsky): Remove the requirement of libpolly. - # - # Packages from apt.llvm.org are being built all at once, with one - # cmake build with superset of options, then different binaries and - # libraries are being included in different packages. - # - # That results in `llvm-config --libname --link-static` mentioning - # libpolly, even if it's not installed. The output of that command is - # being used in build.rs of llvm-sys, so building llvm-sys on such - # system is complaining about lack of libpolly. - # - # Hopefully that nightmare goes away once we switch to binstalls and - # ditch the system LLVM option. - sudo apt -y install llvm-${{ matrix.llvm }}-dev libpolly-${{ matrix.llvm }}-dev - echo /usr/lib/llvm-${{ matrix.llvm }}/bin >> $GITHUB_PATH + brew install llvm + echo $(brew --prefix)/opt/llvm/bin >> $GITHUB_PATH - name: Restore LLVM - if: matrix.llvm == 'source' uses: actions/cache/restore@v4 with: path: llvm-install - key: ${{ needs.llvm.outputs.cache-key }} + key: ${{ needs.llvm.outputs[format('cache-key-{0}', matrix.target.target)] }} fail-on-cache-miss: true - name: Add LLVM to PATH && LD_LIBRARY_PATH - if: matrix.llvm == 'source' + if: matrix.target.build-type == 'native' run: | set -euxo pipefail echo "${{ github.workspace }}/llvm-install/bin" >> $GITHUB_PATH @@ -160,17 +173,63 @@ jobs: cargo clean -p llvm-sys --release - uses: taiki-e/install-action@cargo-hack + if: matrix.target.build-type == 'native' + + # Run cargo commands with `cargo hack` for native targets. + # Run cargo commands with `icedragon cargo` for cross targets. Running + # `cargo hack` wouldn't work, because cross builds need a custom LLVM + # build and can't use the rustc proxy, therefore `--feature-powerset` + # won't work. - name: Check + if: matrix.target.build-type == 'native' run: cargo hack check --feature-powerset + - name: Check + if: matrix.target.build-type == 'cross' + env: + BPF_LINKER_LLVM_PREFIX: "/llvm-install" + run: | + icedragon cargo \ + --target ${{ matrix.target.target }} \ + --volume "${{ github.workspace }}/llvm-install:/llvm-install" \ + -- \ + check \ + --no-default-features --features llvm-sys/no-llvm-linking + - name: Build + if: matrix.target.build-type == 'native' run: cargo hack build --feature-powerset + - name: Build + if: matrix.target.build-type == 'cross' + env: + BPF_LINKER_LLVM_PREFIX: "/llvm-install" + run: | + icedragon cargo \ + --target ${{ matrix.target.target }} \ + --volume "${{ github.workspace }}/llvm-install:/llvm-install" \ + -- \ + build \ + --no-default-features --features llvm-sys/no-llvm-linking + - name: Test - if: matrix.rust == 'nightly' + if: matrix.rust == 'nightly' && matrix.target.build-type == 'native' run: cargo hack test --feature-powerset + - name: Test + # We can run tests only for the x86_64-unknown-linux-musl cross target. + if: matrix.rust == 'nightly' && matrix.target.build-type == 'cross' && startsWith(matrix.target.target, 'x86_64') + env: + BPF_LINKER_LLVM_PREFIX: "/llvm-install" + run: | + icedragon cargo \ + --target ${{ matrix.target.target }} \ + --volume "${{ github.workspace }}/llvm-install:/llvm-install" \ + -- \ + test \ + --no-default-features --features llvm-sys/no-llvm-linking + - uses: actions/checkout@v4 if: matrix.rust == 'nightly' with: @@ -179,10 +238,24 @@ jobs: submodules: recursive - name: Install - if: matrix.rust == 'nightly' - run: cargo install --path . --no-default-features + if: matrix.rust == 'nightly' && runner.os == 'Linux' && matrix.target.build-type == 'native' && startsWith(matrix.target.target, 'x86_64') + run: | + cargo install --path . + + - name: Install + if: matrix.rust == 'nightly' && runner.os == 'Linux' && matrix.target.build-type == 'cross' && startsWith(matrix.target.target, 'x86_64') + env: + BPF_LINKER_LLVM_PREFIX: "/llvm-install" + run: | + icedragon cargo \ + --target ${{ matrix.target.target }} \ + --volume "${{ github.workspace }}/llvm-install:/llvm-install" \ + --volume ~/.cargo:/install \ + -- \ + install --path . --root /install \ + --no-default-features --features llvm-sys/no-llvm-linking - name: Run aya integration tests - if: matrix.rust == 'nightly' + if: matrix.rust == 'nightly' && runner.os == 'Linux' && startsWith(matrix.target.target, 'x86_64') working-directory: aya run: cargo xtask integration-test local diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 5e9a2b28..0b487ce3 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -3,16 +3,56 @@ name: LLVM on: workflow_call: outputs: - cache-key: - value: ${{ jobs.llvm.outputs.cache-key }} + cache-key-aarch64-apple-darwin: + value: ${{ jobs.llvm.outputs.cache-key-aarch64-apple-darwin }} + cache-key-x86_64-apple-darwin: + value: ${{ jobs.llvm.outputs.cache-key-x86_64-apple-darwin }} + cache-key-aarch64-unknown-linux-gnu: + value: ${{ jobs.llvm.outputs.cache-key-aarch64-unknown-linux-gnu }} + cache-key-aarch64-unknown-linux-musl: + value: ${{ jobs.llvm.outputs.cache-key-aarch64-unknown-linux-musl }} + cache-key-x86_64-unknown-linux-gnu: + value: ${{ jobs.llvm.outputs.cache-key-x86_64-unknown-linux-gnu }} + cache-key-x86_64-unknown-linux-musl: + value: ${{ jobs.llvm.outputs.cache-key-x86_64-unknown-linux-musl }} jobs: llvm: - runs-on: ubuntu-22.04 - name: llvm + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + # Runners with macOS >= 14 have ARM CPUs. + - os: macos-14 + target: aarch64-apple-darwin + # Runners with macOS 13 have Intel CPUs. + - os: macos-13 + target: x86_64-apple-darwin + # Even though GitHub offers ARM Linux runners, we still perform a + # cross build on x86_64 runners, because: + # + # * icedragon does not support running on arm64 hosts. + # * We might add RISC-V targets, so having the infrastructure for + # Linux cross builds is benefiial. + - os: ubuntu-22.04 + target: aarch64-unknown-linux-gnu + - os: ubuntu-22.04 + target: aarch64-unknown-linux-musl + - os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + - os: ubuntu-22.04 + target: x86_64-unknown-linux-musl + name: llvm ${{ matrix.target }} outputs: - cache-key: ${{ steps.cache-key.outputs.cache-key }} + cache-key-aarch64-apple-darwin: ${{ steps.cache-key.outputs.cache-key-aarch64-apple-darwin }} + cache-key-x86_64-apple-darwin: ${{ steps.cache-key.outputs.cache-key-x86_64-apple-darwin }} + cache-key-aarch64-unknown-linux-gnu: ${{ steps.cache-key.outputs.cache-key-aarch64-unknown-linux-gnu }} + cache-key-aarch64-unknown-linux-musl: ${{ steps.cache-key.outputs.cache-key-aarch64-unknown-linux-musl }} + cache-key-x86_64-unknown-linux-gnu: ${{ steps.cache-key.outputs.cache-key-x86_64-unknown-linux-gnu }} + cache-key-x86_64-unknown-linux-musl: ${{ steps.cache-key.outputs.cache-key-x86_64-unknown-linux-musl }} steps: + - uses: actions/checkout@v4 + - id: ls-remote run: | set -euxo pipefail @@ -20,18 +60,22 @@ jobs: echo "sha=$value" >> "$GITHUB_OUTPUT" - id: cache-key - run: echo "cache-key=llvm-${{ steps.ls-remote.outputs.sha }}-1" >> "$GITHUB_OUTPUT" + run: echo "cache-key-${{ matrix.target }}=llvm-${{ matrix.target }}-${{ steps.ls-remote.outputs.sha }}-1" >> "$GITHUB_OUTPUT" - name: Cache id: cache-llvm uses: actions/cache@v4 with: path: llvm-install - key: ${{ steps.cache-key.outputs.cache-key }} + key: ${{ steps.cache-key.outputs[format('cache-key-{0}', matrix.target)] }} lookup-only: true + - name: Free disk space + if: steps.cache-llvm.outputs.cache-hit != 'true' && runner.os == 'Linux' + uses: ./.github/actions/free-disk-space + - name: Install Tools - if: steps.cache-llvm.outputs.cache-hit != 'true' + if: steps.cache-llvm.outputs.cache-hit != 'true' && runner.os == 'Linux' && endsWith(matrix.target, 'gnu') run: | set -euxo pipefail wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | \ @@ -43,59 +87,21 @@ jobs: sudo apt update sudo apt -y install cmake ninja-build clang lld - - name: Checkout LLVM Source - if: steps.cache-llvm.outputs.cache-hit != 'true' - uses: actions/checkout@v4 - with: - repository: aya-rs/llvm-project - ref: ${{ steps.ls-remote.outputs.sha }} - path: llvm-project + - name: Install icedragon + if: steps.cache-llvm.outputs.cache-hit != 'true' && runner.os == 'Linux' && endsWith(matrix.target, 'musl') + run: cargo install --git https://github.com/exein-io/icedragon --branch persistent-rustup - - name: Configure LLVM - if: steps.cache-llvm.outputs.cache-hit != 'true' + - name: Install dependencies + if: steps.cache-llvm.outputs.cache-hit != 'true' && runner.os == 'macOS' run: | set -euxo pipefail - cmake \ - -S llvm-project/llvm \ - -B llvm-build \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DCMAKE_C_COMPILER=clang \ - -DCMAKE_CXX_COMPILER=clang++ \ - -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/llvm-install" \ - -DLLVM_BUILD_LLVM_DYLIB=ON \ - -DLLVM_ENABLE_ASSERTIONS=ON \ - -DLLVM_ENABLE_PROJECTS= \ - -DLLVM_ENABLE_RUNTIMES= \ - -DLLVM_INSTALL_UTILS=ON \ - -DLLVM_LINK_LLVM_DYLIB=ON \ - -DLLVM_TARGETS_TO_BUILD=BPF \ - -DLLVM_USE_LINKER=lld + # brew install cmake lld llvm ninja + brew install lld ninja + # echo $(brew --prefix)/opt/llvm/bin >> $GITHUB_PATH - - name: Install LLVM + - name: Build LLVM if: steps.cache-llvm.outputs.cache-hit != 'true' - env: - # Create symlinks rather than copies to conserve disk space. At the time of this writing, - # GitHub-hosted runners have 14GB of SSD space - # (https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources). - # - # Since the LLVM build creates a bunch of symlinks (and this setting does not turn those - # into symlinks-to-symlinks), use absolute symlinks so we can distinguish the two cases. - CMAKE_INSTALL_MODE: ABS_SYMLINK - run: cmake --build llvm-build --target install - - - name: Rewrite LLVM Symlinks - if: steps.cache-llvm.outputs.cache-hit != 'true' - # Move targets over the symlinks that point to them. - # - # This whole dance would be simpler if CMake supported CMAKE_INSTALL_MODE=MOVE. run: | - set -euxo pipefail - find llvm-install -type l -execdir sh -eux -c ' - for link in "$@"; do - target=$(readlink "$link") - case $target in - /*) mv "$target" "$link" ;; - esac - done - ' sh {} + + ./build-llvm.sh \ + --install-dir "${{ github.workspace }}/llvm-install" \ + --target ${{ matrix.target }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1cb4c1c3..7676a764 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,15 +9,31 @@ jobs: uses: ./.github/workflows/llvm.yml upload-bins: - # TODO: Build for macos someday. - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: macos-13 + target: aarch64-apple-darwin + - os: macos-13 + target: x86_64-apple-darwin + - os: ubuntu-22.04 + target: aarch64-unknown-linux-musl + - os: ubuntu-22.04 + target: riscv64-unknown-linux-gnu + - os: ubuntu-22.04 + target: x86_64-unknown-linux-musl needs: llvm + + env: + RUST_BACKTRACE: full + steps: - name: Restore LLVM uses: actions/cache/restore@v4 with: path: llvm-install - key: ${{ needs.llvm.outputs.cache-key }} + key: ${{ needs.llvm.outputs[format('cache-key-{0}', matrix.target-llvm)] }} fail-on-cache-miss: true - name: Add LLVM to PATH @@ -28,9 +44,21 @@ jobs: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - - uses: taiki-e/upload-rust-binary-action@v1 - with: - bin: bpf-linker - features: llvm-sys/force-static + - name: Build env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BPF_LINKER_LLVM_PREFIX: "/llvm-install" + run: | + icedragon cargo \ + --target ${{ matrix.target.target }} \ + --volume "${{ github.workspace }}:/llvm-install" \ + -- \ + build \ + --no-default-features --features llvm-sys/no-llvm-linking \ + --release + + - uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: target/${{ matrix.target }}/release/bpf-linker + asset_name: bpf-linker-${{ matrix.target }} + tag: ${{ github.ref }} diff --git a/Cargo.lock b/Cargo.lock index 9e146dc1..94aa56ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,7 @@ dependencies = [ "log", "regex", "rustc-build-sysroot", + "target-lexicon", "thiserror 2.0.12", "tracing", "tracing-appender", @@ -817,6 +818,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" + [[package]] name = "tempfile" version = "3.14.0" diff --git a/Cargo.toml b/Cargo.toml index 38dd8a50..2a161958 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,9 @@ log = { version = "0.4.27" } thiserror = { version = "2.0.12" } tracing = "0.1" +[build-dependencies] +target-lexicon = { version = "0.13.2", default-features = false } + [dev-dependencies] compiletest_rs = { version = "0.11.0" } regex = { version = "1.11.1", default-features = false } diff --git a/README.md b/README.md index 6aab6f6f..00b09c3a 100644 --- a/README.md +++ b/README.md @@ -17,36 +17,91 @@ files with embedded bitcode (.o), optionally stored inside ar archives (.a). ## Installation -The linker requires LLVM 19. It can use the same LLVM used by the rust compiler, -or it can use an external LLVM installation. +### cargo-binstall -If your target is `aarch64-unknown-linux-gnu` (i.e. Linux on Apple Silicon) you -will have to use the *external LLVM* method. +The recommended way is using cargo-binstall to fetch the ready to use, +statically linked binary: + +```sh +cargo binstall bpf-linker +``` + +### Manual download + +The same binaries can be manually downloaded from the GitHub releases. + +### cargo + +The linker can be installed with cargo and built from source. This method works +only for x86_64 Linux hosts with glibc (`x86_64-unknown-linux-gnu`), but does +not work with other architectures (e.g. aarch64) or with musl libc (any +`*-*-linux-musl` target). See the [Building] section below for more +explanation. + +```sh +cargo install bpf-linker +``` + +## Building + +bpf-linker relies on the most recent LLVM release (currently 20.x), matching +the one used by the nightly Rust compiler. Such recent versions are usually +not packaged in Linux distributions. Furthermore, Rust and bpf-linker might +occasionally need LLVM patches, which are not yet included in upstream releases. + +There are two methods of including LLVM in bpf-linker. ### Using LLVM provided by rustc +Rust toolchains for the `x86_64-unknown-linux-gnu` target provide a local copy +of a shared `libLLVM.so` library. rustc-llvm-proxy provides a possibility to +link bpf-linker to that library. + All you need to do is run: ```sh cargo install bpf-linker ``` -### Using external LLVM +However, this method does not work for any other target. -On Debian based distributions you need to install the `llvm-19-dev`, `libclang-19-dev` -and `libpolly-19-dev` packages. If your distro doesn't have them you can get them -from the official LLVM repo at https://apt.llvm.org. +### Using a locally built LLVM -On rpm based distribution you need the `llvm-devel` and `clang-devel` packages. -If your distro doesn't have them you can get them from Fedora Rawhide. +Aya community hosts its own fork of LLVM, which usually is no different from +the one hosted byi the Rust community, but occasionaly might contain our own +patches. -Once you have installed LLVM 19 you can install the linker running: +Linux builds (of both LLVM and bpf-linker) are using the icedragon tool, which +provides a containeried environment with all necessary dependencies. ```sh -cargo install bpf-linker --no-default-features +cargo install icedragon ``` -If you don't have cargo you can get it from https://rustup.rs or from your distro's package manager. +This repository provides a script to build a local copy of LLVM, with the +following syntax. + +```sh +./build-llvm.sh --install-dir --target +``` + +Examples: + +```sh +./build-llvm.sh --install-dir ~/llvm --target x86_64-unknown-linux-musl +``` +```sh +./build-llvm.sh --install-dir ~/llvm --target aarch64-unknown-linux-musl +``` +```sh +./build-llvm.sh --install-dir ~/llvm --target aarch64-apple-darwin +``` + +For Linux targets, it supports only musl libc (`*-*-linux-musl` targets). + +```sh +cargo install bpf-linker --no-default-features +``` # Usage diff --git a/build-llvm.sh b/build-llvm.sh new file mode 100755 index 00000000..5bba5aa8 --- /dev/null +++ b/build-llvm.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env sh + +set -eu + +LLVM_BRANCH="rustc/20.1-2025-02-13" + +help() { + echo "Usage: $0 --install-dir --target [--source-dir ]" +} + +INSTALL_DIR="" +SOURCE_DIR="" +TARGET="" + +while [ $# -gt 0 ]; do + case $1 in + --help) + help + exit 0 + ;; + --install-dir) + INSTALL_DIR=$2 + shift 2 + ;; + --source-dir) + SOURCE_DIR=$2 + shift 2 + ;; + --target) + TARGET=$2 + shift 2 + ;; + *) + help + exit 1 + ;; + esac +done + +if [ -z "${INSTALL_DIR}" ] || [ -z "${TARGET}" ]; then + echo "Arguments --install-dir and --target are required." + help + exit 1 +fi + +# Fetches the LLVM source code from our fork on GitHub. +fetch_llvm_src() { + mkdir "${SOURCE_DIR}" + trap "rm -rf ${SOURCE_DIR}" EXIT + curl -L --output - \ + "https://github.com/aya-rs/llvm-project/archive/${LLVM_BRANCH}.tar.gz" \ + | tar --strip-components=1 -C "${SOURCE_DIR}" -xzf - +} + +# Builds LLVM for musl/Linux targets using icedragon. +build_linux_musl() { + which icedragon || cargo install --git https://github.com/exein-io/icedragon --branch persistent-rustup + icedragon cmake \ + --container-image ghcr.io/exein-io/icedragon:persistent-rustup \ + --target "${TARGET}" \ + -- \ + -S llvm \ + -B build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_INSTALL_PREFIX=/install \ + -DLLVM_BUILD_EXAMPLES=OFF \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DLLVM_ENABLE_BINDINGS=OFF \ + -DLLVM_ENABLE_LIBCXX=ON \ + -DLLVM_ENABLE_LIBXML2=OFF \ + -DLLVM_ENABLE_PROJECTS= \ + -DLLVM_ENABLE_RUNTIMES= \ + "-DLLVM_HOST_TRIPLE=${TARGET}" \ + -DLLVM_INCLUDE_TESTS=OFF \ + -DLLVM_TARGETS_TO_BUILD=BPF \ + -DLLVM_USE_LINKER=lld + icedragon cmake \ + --container-image ghcr.io/exein-io/icedragon:persistent-rustup \ + --target "${TARGET}" \ + --volume "${INSTALL_DIR}:/install" \ + -- \ + --build build \ + --target install-llvm-config \ + --target install-llvm-libraries +} + +# Builds LLVM for any other target. Expects CMake and all dependencies to be +# present on the host system. +build() { + cmake \ + -S llvm \ + -B build \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + "-DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}" \ + -DLLVM_BUILD_EXAMPLES=OFF \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DLLVM_ENABLE_BINDINGS=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + -DLLVM_ENABLE_PROJECTS= \ + -DLLVM_ENABLE_RUNTIMES= \ + -DLLVM_INCLUDE_TESTS=OFF \ + -DLLVM_TARGETS_TO_BUILD=BPF \ + -DLLVM_USE_LINKER=lld + cmake \ + --build build \ + --target install-llvm-config \ + --target install-llvm-libraries +} + +# Create the `INSTALL_DIR` if it doesn't exist. +if [ ! -d "${INSTALL_DIR}" ]; then + mkdir -p "${INSTALL_DIR}" +fi + +# Fetch the LLVM source if `SOURCE_DIR` was not specified. +if [ -z "${SOURCE_DIR}" ]; then + SOURCE_DIR="/tmp/aya-llvm-$(uuidgen)" + fetch_llvm_src +fi +cd "${SOURCE_DIR}" + +if [ -z "${TARGET##*musl}" ]; then + build_linux_musl +else + build +fi + +echo "LLVM was successfully installed in ${INSTALL_DIR}" diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..e8307fce --- /dev/null +++ b/build.rs @@ -0,0 +1,70 @@ +//! Build script which optionally links libLLVM. + +use std::{env, fs::read_dir, path::Path, str::FromStr}; + +use target_lexicon::{Architecture, Environment, OperatingSystem, Triple}; + +fn main() { + // Execute the build script only when custom LLVM path is provided. + let llvm_prefix = match env::var("BPF_LINKER_LLVM_PREFIX") { + Ok(llvm_prefix) => llvm_prefix, + Err(_) => return, + }; + println!("cargo::rustc-link-search={llvm_prefix}/lib"); + + let target = env::var("TARGET").unwrap(); + let target = Triple::from_str(&target).unwrap(); + + // Use the expected sysroot directories. Otherwise, fall back to the + // standard ones. + match ( + target.architecture, + target.operating_system, + target.environment, + ) { + (Architecture::Aarch64(_), OperatingSystem::Linux, Environment::Musl) => { + println!("cargo::rustc-link-arg=--sysroot=/usr/aarch64-unknown-linux-musl"); + println!("cargo::rustc-link-search=/usr/aarch64-unknown-linux-musl/lib"); + println!("cargo::rustc-link-search=/usr/aarch64-unknown-linux-musl/usr/lib"); + } + (Architecture::Riscv64(_), OperatingSystem::Linux, Environment::Musl) => { + println!("cargo::rustc-link-arg=--sysroot=/usr/aarch64-unknown-linux-musl"); + println!("cargo::rustc-link-search=/usr/riscv64-unknown-linux-musl/lib"); + println!("cargo::rustc-link-search=/usr/riscv64-unknown-linux-musl/usr/lib"); + } + (Architecture::X86_64, OperatingSystem::Linux, Environment::Musl) => { + println!("cargo::rustc-link-arg=--sysroot=/usr/x86_64-unknown-linux-musl"); + println!("cargo::rustc-link-search=/usr/x86_64-unknown-linux-musl/lib"); + println!("cargo::rustc-link-search=/usr/x86_64-unknown-linux-musl/usr/lib"); + } + (_, _, _) => { + panic!("Unsupported target. Please use `LLVM_SYS_*_PREFIX` and llvm-sys (without `no-llvm-linking` feature) to link LLVM.") + } + } + + println!("cargo::rustc-link-lib=static=c++_static"); + println!("cargo::rustc-link-lib=static=c++abi"); + + println!("cargo::rustc-link-lib=static=rt"); + println!("cargo::rustc-link-lib=static=dl"); + println!("cargo::rustc-link-lib=static=m"); + println!("cargo::rustc-link-lib=static=z"); + println!("cargo::rustc-link-lib=static=zstd"); + + // Link libLLVM using the artifacts from the provided prefix. + for entry in + read_dir(Path::new(&llvm_prefix).join("lib")).expect("LLVM build directory not found") + { + let entry = entry.expect("failed to retrieve the file in the LLVM build directory"); + let path = entry.path(); + if path.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("a") { + let libname = path.file_name().unwrap().to_string_lossy(); + let libname = libname + .strip_prefix("lib") + .unwrap() + .strip_suffix(".a") + .unwrap(); + println!("cargo::rustc-link-lib=static={libname}") + } + } +}