diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..35049cbc --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --package xtask --" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a5252cb..e14df7f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,9 +47,38 @@ jobs: - name: Check formatting run: cargo fmt --all -- --check - build: - # We don't use ubuntu-latest because we care about the apt packages available. + build-container-image: runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: + - target: aarch64-unknown-linux-gnu + - target: aarch64-unknown-linux-musl + - target: riscv64-unknown-linux-gnu + - target: riscv64-unknown-linux-musl + - target: x86_64-unknown-linux-gnu + - target: x86_64-unknown-linux-musl + name: container target=${{ matrix.target }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + + - name: Log in to GitHub Container Registry + run: | + set -euxo pipefail + echo "${{ secrets.GITHUB_TOKEN }}" | \ + docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Build container image + run: | + set -euxo pipefail + cargo xtask build-container-image \ + --target ${{ matrix.target }} \ + --push + + build: + runs-on: ${{ matrix.target.os }} strategy: fail-fast: false matrix: @@ -57,41 +86,66 @@ jobs: - stable - beta - nightly - llvm: - - 19 - - 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 + target-llvm: aarch64-apple-darwin + - os: macos-13 + target: x86_64-apple-darwin + target-llvm: x86_64-apple-darwin + # We use the GNU builds of LLVM both for GNU and musl builds of + # bpf-linker - it doesn't affect the type of libc being picked by + # Cargo and musl Rust toolchains come with their own copies of + # libc.a. + - os: ubuntu-22.04 + packages: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross qemu-user + target: aarch64-unknown-linux-gnu + target-llvm: aarch64-linux-gnu + - os: ubuntu-22.04 + packages: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross musl-dev qemu-user + target: aarch64-unknown-linux-musl + target-llvm: aarch64-linux-gnu + - os: ubuntu-22.04 + packages: gcc-riscv64-linux-gnu g++-riscv64-linux-gnu libc6-dev-riscv64-cross qemu-user + target: riscv64gc-unknown-linux-gnu + target-llvm: riscv64-linux-gnu + - os: ubuntu-22.04 + packages: + target: x86_64-unknown-linux-gnu + target-llvm: x86_64-linux-gnu + - os: ubuntu-22.04 + packages: + target: x86_64-unknown-linux-musl + target-llvm: x86_64-linux-gnu + name: rustc=${{ matrix.rust }} target=${{ matrix.target.target }} needs: llvm env: + CARGO_BUILD_TARGET: ${{ matrix.target.target }} + # We use the GNU sysroot as an LD path both for GNU and musl builds of + # bpf-linker - the user-space emulator and the path are used only for + # executing `llvm-config` and test binaries. + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER: qemu-aarch64 -L /usr/aarch64-linux-gnu + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUNNER: qemu-aarch64 -L /usr/aarch64-linux-gnu + CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_RUNNER: qemu-aarch64 -L /usr/riscv64-linux-gnu + CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_MUSL_RUNNER: qemu-aarch64 -L /usr/riscv64-linux-gnu RUST_BACKTRACE: full steps: - uses: actions/checkout@v4 - name: Install Rust ${{ matrix.rust }} - if: matrix.rust != 'nightly' - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.rust }} - - - name: Install Rust ${{ matrix.rust }} - if: matrix.rust == 'nightly' uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} components: rust-src + targets: ${{ matrix.target.target }} - uses: Swatinem/rust-cache@v2 - - name: Check (default features, no system LLVM) - run: cargo check - - - name: Build (default features, no system LLVM) - run: cargo build - - - name: Install prerequisites - if: matrix.rust == 'nightly' + - name: Install dependencies + if: runner.os == 'Linux' # 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`. # @@ -103,49 +157,39 @@ jobs: run: | set -euxo pipefail sudo apt update - sudo apt -y install gcc-multilib + sudo apt -y install ${{ matrix.target.packages }} echo /usr/lib/llvm-15/bin >> $GITHUB_PATH - - name: Install LLVM - if: matrix.llvm != 'source' + - name: Install dependencies + 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 + brew install llvm + echo $(brew --prefix)/opt/llvm/bin >> $GITHUB_PATH - 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 + - name: Check (default features, no system LLVM) + run: cargo check + + - name: Build (default features, no system LLVM) + run: cargo build - 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-llvm)] }} fail-on-cache-miss: true - - name: Add LLVM to PATH && LD_LIBRARY_PATH - if: matrix.llvm == 'source' + - name: Point llvm-sys to the restored LLVM run: | set -euxo pipefail - echo "${{ github.workspace }}/llvm-install/bin" >> $GITHUB_PATH - # LD_LIBRARY_PATH is needed because we're going to link everything dynamically below. This - # doesn't affect behavior, but greatly reduces disk usage. - echo "LD_LIBRARY_PATH=${{ github.workspace }}/llvm-install/lib" >> $GITHUB_ENV + llvm_libdir="${{ github.workspace }}/llvm-install/lib" + rustflags="-L $llvm_libdir " + rustflags+=$(find $llvm_libdir -type f -name "*.a" -printf '%f\n' | \ + sed -e 's/^lib//' -e 's/\.a$//' | \ + sed 's/^/-l /' | paste -sd ' ') + echo "RUSTFLAGS=$rustflags" >> $GITHUB_ENV # llvm-sys discovers link flags at build script time; these are cached by cargo. The cached # flags may be incorrect when the cache is reused across LLVM versions. @@ -158,14 +202,14 @@ jobs: - uses: taiki-e/install-action@cargo-hack - name: Check - run: cargo hack check --feature-powerset + run: cargo hack check --features llvm-sys/no-llvm-linking --feature-powerset - name: Build - run: cargo hack build --feature-powerset + run: cargo hack build --features llvm-sys/no-llvm-linking --feature-powerset - name: Test if: matrix.rust == 'nightly' - run: cargo hack test --feature-powerset + run: cargo hack test --features llvm-sys/no-llvm-linking --feature-powerset - uses: actions/checkout@v4 if: matrix.rust == 'nightly' @@ -179,6 +223,10 @@ jobs: run: cargo install --path . --no-default-features - 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 + + # - name: Setup tmate session + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3 diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index 41926739..d6691f15 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -3,15 +3,58 @@ 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-linux-gnu: + value: ${{ jobs.llvm.outputs.cache-key-aarch64-linux-gnu }} + cache-key-riscv64-linux-gnu: + value: ${{ jobs.llvm.outputs.cache-key-riscv64-linux-gnu }} + cache-key-x86_64-linux-gnu: + value: ${{ jobs.llvm.outputs.cache-key-x86_64-linux-gnu }} jobs: llvm: - runs-on: ubuntu-22.04 - name: llvm + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: macos-13 + os-name: macOS + processor: arm64 + system: Darwin + target: aarch64-apple-darwin + - os: macos-13 + os-name: macOS + processor: x86_64 + system: Darwin + target: x86_64-apple-darwin + - os: ubuntu-22.04 + os-name: Linux + packages: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross + processor: aarch64 + system: Linux + target: aarch64-linux-gnu + - os: ubuntu-22.04 + os-name: Linux + packages: gcc-riscv64-linux-gnu g++-riscv64-linux-gnu libc6-dev-riscv64-cross + processor: riscv64 + system: Linux + target: riscv64-linux-gnu + - os: ubuntu-22.04 + os-name: Linux + packages: + processor: x86_64 + system: Linux + target: x86_64-linux-gnu + 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-linux-gnu: ${{ steps.cache-key.outputs.cache-key-aarch64-linux-gnu }} + cache-key-riscv64-linux-gnu: ${{ steps.cache-key.outputs.cache-key-riscv64-linux-gnu }} + cache-key-x86_64-linux-gnu: ${{ steps.cache-key.outputs.cache-key-x86_64-linux-gnu }} steps: - id: ls-remote run: | @@ -20,18 +63,25 @@ 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: runner.os == 'Linux' && steps.cache-llvm.outputs.cache-hit != 'true' + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + swap-storage: false + - name: Install Tools - if: steps.cache-llvm.outputs.cache-hit != 'true' + if: runner.os == 'Linux' && steps.cache-llvm.outputs.cache-hit != 'true' run: | set -euxo pipefail wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | \ @@ -41,7 +91,39 @@ jobs: sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null sudo apt update - sudo apt -y install cmake ninja-build clang lld + sudo apt -y install \ + cmake \ + ninja-build \ + ${{ matrix.packages }} + + - name: Install Tools + if: runner.os == 'macOS' && steps.cache-llvm.outputs.cache-hit != 'true' + # TODO(vadorovsky): There are LLVM binary tarballs for macOS on GitHub, + # but unfortunately they don't work correctly[0]. Once the issue is + # fixed, we could run the next step ("Install LLVM") on all systems. + # + # For now, install LLVM from homebrew. + # + # [0] https://github.com/llvm/llvm-project/issues/92260 + run: | + set -euxo pipefail + brew update + brew install llvm ninja + echo $(brew --prefix)/opt/llvm/bin >> $GITHUB_PATH + + - name: Install LLVM + if: runner.os == 'Linux' && steps.cache-llvm.outputs.cache-hit != 'true' + run: | + set -euxo pipefail + llvm_tarball_url=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + https://api.github.com/repos/llvm/llvm-project/releases | jq -r \ + 'first | .assets | map(select(.name | + endswith("${{ matrix.os-name }}-X64.tar.xz"))) | first | + .browser_download_url') + mkdir -p /tmp/llvm-upstream + wget -q -O - $llvm_tarball_url | tar -xJ --strip-components 1 \ + -C /tmp/llvm-upstream + echo /tmp/llvm-upstream/bin >> $GITHUB_PATH - name: Checkout LLVM Source if: steps.cache-llvm.outputs.cache-hit != 'true' @@ -60,13 +142,20 @@ jobs: -B llvm-build \ -G Ninja \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_ASM_COMPILER=clang \ + -DCMAKE_ASM_COMPILER_TARGET="${{ matrix.target }}" \ -DCMAKE_C_COMPILER=clang \ + -DCMAKE_C_COMPILER_TARGET="${{ matrix.target }}" \ -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_CXX_COMPILER_TARGET="${{ matrix.target }}" \ -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/llvm-install" \ + -DCMAKE_SYSTEM_NAME="${{ matrix.system }}" \ + -DCMAKE_SYSTEM_PROCESSOR="${{ matrix.processor }}" \ -DLLVM_BUILD_LLVM_DYLIB=ON \ -DLLVM_ENABLE_ASSERTIONS=ON \ -DLLVM_ENABLE_PROJECTS= \ -DLLVM_ENABLE_RUNTIMES= \ + -DLLVM_HOST_TRIPLE="${{ matrix.target }}" \ -DLLVM_INSTALL_UTILS=ON \ -DLLVM_LINK_LLVM_DYLIB=ON \ -DLLVM_TARGETS_TO_BUILD=BPF \ @@ -74,28 +163,4 @@ jobs: - name: Install 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 {} + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1cb4c1c3..8385b302 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,15 +9,63 @@ 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 + target-llvm: aarch64-apple-darwin + - os: macos-13 + target: x86_64-apple-darwin + target-llvm: x86_64-apple-darwin + - os: ubuntu-22.04 + packages: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross + target: aarch64-unknown-linux-gnu + target-llvm: aarch64-linux-gnu + - os: ubuntu-22.04 + packages: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev-arm64-cross + target: aarch64-unknown-linux-musl + target-llvm: aarch64-linux-gnu + - os: ubuntu-22.04 + packages: gcc-riscv64-linux-gnu g++-riscv64-linux-gnu libc6-dev-riscv64-cross + target: riscv64-unknown-linux-gnu + target-llvm: riscv64-linux-gnu + - os: ubuntu-22.04 + packages: + target: x86_64-unknown-linux-gnu + target-llvm: x86_64-linux-gnu + - os: ubuntu-22.04 + packages: + target: x86_64-unknown-linux-musl + target-llvm: x86_64-linux-gnu needs: llvm + + env: + CARGO_BUILD_TARGET: ${{ matrix.target }} + # We use the GNU sysroot as an LD path both for GNU and musl builds of + # bpf-linker - the user-space emulator and the path are used only for + # executing `llvm-config` and test binaries. + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER: qemu-aarch64 -L /usr/aarch64-linux-gnu + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUNNER: qemu-aarch64 -L /usr/aarch64-linux-gnu + CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_RUNNER: qemu-aarch64 -L /usr/aarch64-linux-gnu + CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_MUSL_RUNNER: qemu-aarch64 -L /usr/aarch64-linux-gnu + LLVM_SYS_CROSS_LD_PREFIX: /usr/${{ matrix.target-llvm }} + RUST_BACKTRACE: full + steps: + - name: Install Tools + if: runner.os == 'Linux' + run: | + set -euxo pipefail + sudo apt update + sudo apt -y install ${{ matrix.packages }} + - 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 diff --git a/Cargo.lock b/Cargo.lock index 1ba7a86a..80f6a4ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.13" @@ -71,6 +86,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69" +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "aya-rustc-llvm-proxy" version = "0.9.3" @@ -124,6 +145,12 @@ dependencies = [ "which", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "camino" version = "1.1.6" @@ -158,9 +185,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.90" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -168,6 +198,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.4", +] + [[package]] name = "clap" version = "4.5.18" @@ -235,6 +279,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crossbeam-channel" version = "0.5.12" @@ -390,6 +440,29 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indexmap" version = "2.2.5" @@ -406,6 +479,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -514,6 +596,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -526,9 +617,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "overload" @@ -542,6 +636,12 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -762,6 +862,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "smallvec" version = "1.13.2" @@ -791,6 +897,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.10.1" @@ -991,6 +1103,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", +] + [[package]] name = "valuable" version = "0.1.0" @@ -1013,6 +1134,61 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + [[package]] name = "which" version = "6.0.3" @@ -1057,6 +1233,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -1251,3 +1436,34 @@ name = "winsafe" version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "xshell" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db0ab86eae739efd1b054a8d3d16041914030ac4e01cd1dca0cf252fd8b6437" +dependencies = [ + "xshell-macros", +] + +[[package]] +name = "xshell-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "clap", + "once_cell", + "regex", + "target-lexicon", + "thiserror", + "uuid", + "which", + "xshell", +] diff --git a/Cargo.toml b/Cargo.toml index 49604331..63cf30cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,3 +54,26 @@ debug = true [patch.crates-io] compiletest_rs = { git = "https://github.com/Manishearth/compiletest-rs.git" } + +[workspace] +members = [ + "xtask", +] + +[workspace.package] +version = "0.9.13" +license = "MIT OR Apache-2.0" +repository = "https://github.com/aya-rs/bpf-linker" +edition = "2021" + +[workspace.dependencies] +anyhow = "1.0.89" +chrono = "0.4" +clap = { version = "4.5", features = ["derive"] } +once_cell = "1.20" +regex = { version = "1.10.6", default-features = false } +target-lexicon = "0.12" +thiserror = { version = "1.0.64" } +uuid = { version = "1.10", features = ["v4"] } +which = "6.0" +xshell = "0.2" diff --git a/docker/Dockerfile.cross-aarch64-unknown-linux-gnu b/docker/Dockerfile.cross-aarch64-unknown-linux-gnu new file mode 100644 index 00000000..e62cd4a6 --- /dev/null +++ b/docker/Dockerfile.cross-aarch64-unknown-linux-gnu @@ -0,0 +1,29 @@ +FROM docker.io/debian:bookworm + +ENV PATH="/root/.cargo/bin:/usr/lib/llvm-15/bin:${PATH}" + +# Even though we are using clang as a C compiler, we need the libgcc_s and +# libstdc++. +RUN dpkg --add-architecture arm64 \ + && apt update \ + && apt install -y \ + build-essential \ + clang-15 \ + cmake \ + curl \ + gcc-aarch64-linux-gnu \ + g++-aarch64-linux-gnu \ + libc6-dev-arm64-cross \ + libzstd-dev \ + libzstd-dev:arm64 \ + lld-15 \ + ninja-build \ + qemu-user \ + zlib1g-dev \ + zlib1g-dev:arm64 \ + && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ + && rustup toolchain install stable --component rust-src \ + && rustup toolchain install nightly --component rust-src \ + && rustup target add aarch64-unknown-linux-gnu \ + && rustup +nightly target add aarch64-unknown-linux-gnu \ + && rm -rf /var/lib/apt/lists/* diff --git a/docker/Dockerfile.cross-aarch64-unknown-linux-musl b/docker/Dockerfile.cross-aarch64-unknown-linux-musl new file mode 100644 index 00000000..d62f5eec --- /dev/null +++ b/docker/Dockerfile.cross-aarch64-unknown-linux-musl @@ -0,0 +1,75 @@ +FROM docker.io/gentoo/stage3:musl-llvm + +ENV PATH="/root/.cargo/bin:/usr/lib/llvm/18/bin:${PATH}" +# Install only aarch64 user-space wrapper when installing app-emulation/qemu. +ENV QEMU_USER_TARGETS="aarch64" +# Enable static libraries for installed packages (zstd, zlib etc.). +ENV USE="static-libs" + +# Use clang-musl-overlay patches[0], needed for QEMU to build successfully. +# +# Install llvm-libgcc, which is a drop-in replacement for libgcc_s runtime +# library. It's needed by Rust binaries provided by rustup[1][2]. It's provided +# in vadorovsky's private overlay. +# +# Create a cross sysroot using crossdev[3], which also creates wrappers for: +# - clang, which can be used for compiling C/C++ projects without doing the +# whole dance with `--target` and `--sysroot` arguments. +# - emerge, which let you install packages in the cross sysroot. +# +# Unpack the stage3 tarball into that sysroot to avoiding compilation of the +# whole base system from scratch. Otherwise, +# `emerge-aarch64-gentoo-linux-musl @system` would take an eternity to run on +# free GitHub runners. +# +# Patch the clang config to use libc++ and libunwind, before the necessary fix +# in crossdev gets merged[4]. +# +# [0] https://github.com/clang-musl-overlay/gentoo-patchset +# [1] https://github.com/rust-lang/rust/issues/119504 +# [2] https://github.com/rust-lang/rustup/issues/2213#issuecomment-1888615413 +# [3] https://wiki.gentoo.org/wiki/Crossdev +# [4] https://github.com/gentoo/crossdev/pull/23 +RUN emerge --sync --quiet \ + && emerge \ + app-emulation/qemu \ + app-eselect/eselect-repository \ + dev-vcs/git \ + sys-devel/crossdev \ + && eselect repository add vadorovsky git \ + https://gitlab.com/vadorovsky/overlay \ + && git clone --depth 1 \ + https://github.com/clang-musl-overlay/gentoo-patchset \ + /etc/portage/patches \ + && emerge --sync --quiet \ + && emerge \ + app-emulation/qemu \ + sys-libs/llvm-libgcc \ + && eselect repository create crossdev \ + && crossdev --llvm --target aarch64-gentoo-linux-musl \ + && curl -L "https://ftp-osl.osuosl.org/pub/gentoo/releases/arm64/autobuilds/current-stage3-arm64-musl-llvm/$(\ + curl -L "https://ftp-osl.osuosl.org/pub/gentoo/releases/arm64/autobuilds/current-stage3-arm64-musl-llvm/latest-stage3-arm64-musl-llvm.txt" | \ + grep tar.xz | cut -d ' ' -f 1)" | \ + tar -xJpf - -C /usr/aarch64-gentoo-linux-musl --exclude=dev --skip-old-files \ + && ln -s \ + /etc/portage/repos.conf \ + /usr/aarch64-gentoo-linux-musl/etc/portage/repos.conf \ + && PORTAGE_CONFIGROOT=/usr/aarch64-gentoo-linux-musl eselect profile set \ + default/linux/arm64/23.0/musl/llvm \ + && sed -i -e "s/--unwindlib=none/--unwindlib=libunwind/" \ + /etc/clang/cross/aarch64-gentoo-linux-musl.cfg \ + && echo "--stdlib=libc++" >> \ + /etc/clang/cross/aarch64-gentoo-linux-musl.cfg \ + && aarch64-gentoo-linux-musl-emerge --sync --quiet \ + && aarch64-gentoo-linux-musl-emerge \ + app-arch/zstd \ + sys-libs/zlib \ + && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ + && rustup toolchain install stable --component rust-src \ + && rustup toolchain install nightly --component rust-src \ + && rustup target add aarch64-unknown-linux-musl \ + && rustup +nightly target add aarch64-unknown-linux-musl \ + && rm -rf \ + /var/cache/binpkgs/* \ + /var/cache/distfiles/* \ + /var/tmp/portage/* diff --git a/docker/Dockerfile.cross-riscv64-unknown-linux-musl b/docker/Dockerfile.cross-riscv64-unknown-linux-musl new file mode 100644 index 00000000..ffdbc870 --- /dev/null +++ b/docker/Dockerfile.cross-riscv64-unknown-linux-musl @@ -0,0 +1,76 @@ + +FROM docker.io/gentoo/stage3:musl-llvm + +ENV PATH="/root/.cargo/bin:/usr/lib/llvm/18/bin:${PATH}" +# Install only riscv64 user-space wrapper when installing app-emulation/qemu. +ENV QEMU_USER_TARGETS="riscv64" +# Enable static libraries for installed packages (zstd, zlib etc.). +ENV USE="static-libs" + +# Use clang-musl-overlay patches[0], needed for QEMU to build successfully. +# +# Install llvm-libgcc, which is a drop-in replacement for libgcc_s runtime +# library. It's needed by Rust binaries provided by rustup[1][2]. It's provided +# in vadorovsky's private overlay. +# +# Create a cross sysroot using crossdev[3], which also creates wrappers for: +# - clang, which can be used for compiling C/C++ projects without doing the +# whole dance with `--target` and `--sysroot` arguments. +# - emerge, which let you install packages in the cross sysroot. +# +# Unpack the stage3 tarball into that sysroot to avoiding compilation of the +# whole base system from scratch. Otherwise, +# `emerge-riscv64-gentoo-linux-musl @system` would take an eternity to run on +# free GitHub runners. +# +# Patch the clang config to use libc++ and libunwind, before the necessary fix +# in crossdev gets merged[4]. +# +# [0] https://github.com/clang-musl-overlay/gentoo-patchset +# [1] https://github.com/rust-lang/rust/issues/119504 +# [2] https://github.com/rust-lang/rustup/issues/2213#issuecomment-1888615413 +# [3] https://wiki.gentoo.org/wiki/Crossdev +# [4] https://github.com/gentoo/crossdev/pull/23 +RUN emerge --sync --quiet \ + && emerge \ + app-emulation/qemu \ + app-eselect/eselect-repository \ + dev-vcs/git \ + sys-devel/crossdev \ + && eselect repository add vadorovsky git \ + https://gitlab.com/vadorovsky/overlay \ + && git clone --depth 1 \ + https://github.com/clang-musl-overlay/gentoo-patchset \ + /etc/portage/patches \ + && emerge --sync --quiet \ + && emerge \ + app-emulation/qemu \ + sys-libs/llvm-libgcc \ + && eselect repository create crossdev \ + && crossdev --llvm --target riscv64-gentoo-linux-musl \ + && curl -L "https://ftp-osl.osuosl.org/pub/gentoo/releases/arm64/autobuilds/current-stage3-arm64-musl-llvm/$(\ + curl -L "https://ftp-osl.osuosl.org/pub/gentoo/releases/arm64/autobuilds/current-stage3-arm64-musl-llvm/latest-stage3-arm64-musl-llvm.txt" | \ + grep tar.xz | cut -d ' ' -f 1)" | \ + tar -xJpf - -C /usr/riscv64-gentoo-linux-musl --exclude=dev --skip-old-files \ + && ln -s \ + /etc/portage/repos.conf \ + /usr/riscv64-gentoo-linux-musl/etc/portage/repos.conf \ + && PORTAGE_CONFIGROOT=/usr/riscv64-gentoo-linux-musl eselect profile set \ + default/linux/arm64/23.0/musl/llvm \ + && sed -i -e "s/--unwindlib=none/--unwindlib=libunwind/" \ + /etc/clang/cross/riscv64-gentoo-linux-musl.cfg \ + && echo "--stdlib=libc++" >> \ + /etc/clang/cross/riscv64-gentoo-linux-musl.cfg \ + && riscv64-gentoo-linux-musl-emerge --sync --quiet \ + && riscv64-gentoo-linux-musl-emerge \ + app-arch/zstd \ + sys-libs/zlib \ + && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ + && rustup toolchain install stable --component rust-src \ + && rustup toolchain install nightly --component rust-src \ + && rustup target add riscv64-unknown-linux-musl \ + && rustup +nightly target add riscv64-unknown-linux-musl \ + && rm -rf \ + /var/cache/binpkgs/* \ + /var/cache/distfiles/* \ + /var/tmp/portage/* diff --git a/docker/Dockerfile.native-x86_64-unknown-linux-gnu b/docker/Dockerfile.native-x86_64-unknown-linux-gnu new file mode 100644 index 00000000..02cb7180 --- /dev/null +++ b/docker/Dockerfile.native-x86_64-unknown-linux-gnu @@ -0,0 +1,18 @@ +FROM docker.io/debian:bookworm + +ENV PATH="/root/.cargo/bin:/usr/lib/llvm-15/bin:${PATH}" + +RUN apt update \ + && apt install -y \ + build-essential \ + clang-15 \ + cmake \ + curl \ + libzstd-dev \ + lld-15 \ + ninja-build \ + zlib1g-dev \ + && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ + && rustup toolchain install stable --component rust-src \ + && rustup toolchain install nightly --component rust-src \ + && rm -rf /var/lib/apt/lists/* diff --git a/docker/Dockerfile.native-x86_64-unknown-linux-musl b/docker/Dockerfile.native-x86_64-unknown-linux-musl new file mode 100644 index 00000000..6d75c504 --- /dev/null +++ b/docker/Dockerfile.native-x86_64-unknown-linux-musl @@ -0,0 +1,27 @@ +FROM docker.io/gentoo/stage3:musl-llvm + +ENV PATH="/root/.cargo/bin:/usr/lib/llvm/18/bin:${PATH}" +# Enable static libraries for installed packages (zstd, zlib etc.). +ENV USE="static-libs" + +# Install llvm-libgcc, which is a drop-in replacement for libgcc_s runtime +# library. It's needed by Rust binaries provided by rustup[0][1]. +# +# [0] https://github.com/rust-lang/rust/issues/119504 +# [1] https://github.com/rust-lang/rustup/issues/2213#issuecomment-1888615413 +RUN emerge --sync --quiet \ + && emerge \ + app-arch/zstd \ + app-eselect/eselect-repository \ + dev-vcs/git \ + sys-libs/zlib \ + && eselect repository add vadorovsky git https://gitlab.com/vadorovsky/overlay \ + && emerge --sync --quiet \ + && emerge sys-libs/llvm-libgcc \ + && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ + && rustup toolchain install stable --component rust-src \ + && rustup toolchain install nightly --component rust-src \ + && rm -rf \ + /var/cache/binpkgs/* \ + /var/cache/distfiles/* \ + /var/tmp/portage/* diff --git a/tests/tests.rs b/tests/tests.rs index 813c4dcf..49963edf 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -93,6 +93,11 @@ where } } +// bpftool doesn't work on macOS, skip the tests requiring it. +// +// TODO(vadorovsky): Make our own BTF dump tooling as part of aya-tool and +// use it here to make BTF tests possible on macOS. +#[cfg(not(target_os = "macos"))] fn btf_dump(src: &Path, dst: &Path) { let dst = std::fs::File::create(dst) .unwrap_or_else(|err| panic!("could not open btf dump file '{}': {err}", dst.display())); @@ -141,6 +146,8 @@ fn compile_test() { Some(&directory), None::, ); + + #[cfg(not(target_os = "macos"))] run_mode( target, "assembly", diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000..e69fa2e9 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = { workspace = true } +chrono = { workspace = true } +clap = { workspace = true } +once_cell = { workspace = true } +regex = { workspace = true } +target-lexicon = { workspace = true } +thiserror = { workspace = true } +uuid = { workspace = true } +which = { workspace = true } +xshell = { workspace = true } diff --git a/xtask/src/build.rs b/xtask/src/build.rs new file mode 100644 index 00000000..0933d919 --- /dev/null +++ b/xtask/src/build.rs @@ -0,0 +1,7 @@ +use std::ffi::OsStr; + +use crate::cargo::{run_cargo, CargoArgs}; + +pub fn build(args: CargoArgs) -> anyhow::Result<()> { + run_cargo(args, OsStr::new("build")) +} diff --git a/xtask/src/build_llvm.rs b/xtask/src/build_llvm.rs new file mode 100644 index 00000000..e90dae3c --- /dev/null +++ b/xtask/src/build_llvm.rs @@ -0,0 +1,240 @@ +use std::{ + ffi::{OsStr, OsString}, + fs::{self, create_dir_all, remove_dir_all}, + path::Path, + process::{Command, Stdio}, +}; + +use clap::Parser; +use target_lexicon::Triple; +use thiserror::Error; + +use crate::{ + containers::{ContainerEngine, ContainerError}, + target::{SupportedTriple, TripleExt}, + tempdir::TempDir, +}; + +#[derive(Debug, Error)] +pub enum LlvmBuildError { + #[error(transparent)] + Container(ContainerError), + #[error("target {0} is not supported")] + TargetNotSupported(String), + #[error("cmake build failed")] + CmakeBuild, +} + +#[derive(Parser)] +pub struct BuildLlvmArgs { + /// Container engine (if not provided, is going to be autodetected). + #[arg(long)] + container_engine: Option, + + /// Prefix in which LLVM libraries are going to be installed after build. + #[arg(long)] + llvm_install_dir: Option, + + /// Path to the LLVM repository directory. If not provided, it will be + /// cloned automatically in a temporary location. + #[arg(long)] + llvm_repository_dir: Option, + + /// URL to the LLVM repository. Irrelevant if `--llvm-repository-dir` is + /// specified. + #[arg(long, default_value = "https://github.com/aya-rs/llvm-project")] + llvm_repository_url: String, + + /// Branch of the LLVM repository. Irrelevant if `--llvm-repository-dir` is + /// specified. + #[arg(long, default_value = "rustc/19.1-2024-09-17")] + llvm_repository_branch: String, + + /// Preserve the build directory. + #[arg(long)] + preserve_build_dir: bool, + + /// Target triple (optional). + #[arg(short, long)] + target: Option, +} + +fn clone_repo( + llvm_repository_url: &String, + llvm_repository_branch: &str, + destination: &OsStr, +) -> anyhow::Result<()> { + // NOTE(vadorovsky): Sadly, git2 crate doesn't support specyfing depth when + // cloning. + Command::new("git") + .arg("clone") + .arg("--depth") + .arg("1") + .arg("--branch") + .arg(&llvm_repository_branch) + .arg(&llvm_repository_url) + .arg(destination) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status()?; + + Ok(()) +} + +pub fn build_llvm(args: BuildLlvmArgs) -> anyhow::Result<()> { + let BuildLlvmArgs { + container_engine, + llvm_install_dir, + llvm_repository_dir, + llvm_repository_url, + llvm_repository_branch, + preserve_build_dir, + target, + } = args; + + let build_tempdir = TempDir::new("aya-llvm-build", preserve_build_dir)?; + + let llvm_repository_dir = match llvm_repository_dir { + Some(llvm_repository_dir) => llvm_repository_dir, + None => { + let destination = build_tempdir.to_os_string(); + clone_repo(&llvm_repository_url, &llvm_repository_branch, &destination)?; + destination + } + }; + println!( + "Building LLVM in directory {}", + llvm_repository_dir.to_string_lossy() + ); + + let triple: Triple = match target { + Some(target) => target.into(), + None => target_lexicon::HOST, + }; + + let llvm_install_dir = match llvm_install_dir { + Some(llvm_install_dir) => llvm_install_dir, + None => Path::new("/tmp") + .join(format!("aya-llvm-{triple}")) + .into_os_string(), + }; + if Path::new(&llvm_install_dir).exists() { + remove_dir_all(&llvm_install_dir)?; + } + create_dir_all(&llvm_install_dir)?; + + let llvm_build_config = triple + .llvm_build_config(&llvm_install_dir) + .ok_or(LlvmBuildError::TargetNotSupported(triple.to_string()))?; + + let cmake_args = llvm_build_config.cmake_args(); + + let build_dir = format!("aya-build-{}", llvm_build_config.target_triple); + let build_dir_path = Path::new(&llvm_repository_dir).join(&build_dir); + if build_dir_path.exists() { + fs::remove_dir_all(Path::new(&llvm_repository_dir).join(&build_dir))?; + } + + match triple.container_image() { + Some((container_image, _)) => { + println!("Using container image {container_image}"); + + let container_engine = + container_engine.unwrap_or(ContainerEngine::autodetect().ok_or( + LlvmBuildError::Container(ContainerError::ContainerEngineNotFound), + )?); + + let mut cmd = Command::new(container_engine.to_string()); + cmd.args([ + "run", + "--rm", + "-it", + "-w", + "/usr/local/src/llvm", + "-v", + &format!( + "{}:/usr/local/src/llvm:z", + llvm_repository_dir.to_string_lossy() + ), + "-v", + &format!( + "{}:{}", + llvm_install_dir.to_string_lossy(), + llvm_install_dir.to_string_lossy() + ), + &container_image, + "cmake", + ]) + .args(cmake_args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + println!("{cmd:?}"); + if !cmd.status()?.success() { + return Err(LlvmBuildError::CmakeBuild.into()); + } + + let mut cmd = Command::new(container_engine.to_string()); + cmd.args([ + "run", + "--rm", + "-e", + // "PATH=/usr/lib/llvm/18/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "-it", + "-w", + "/usr/local/src/llvm", + "-v", + &format!( + "{}:/usr/local/src/llvm", + llvm_repository_dir.to_string_lossy() + ), + "-v", + &format!( + "{}:{}", + llvm_install_dir.to_string_lossy(), + llvm_install_dir.to_string_lossy() + ), + &container_image, + "cmake", + "--build", + &build_dir, + "-j", + "--target", + "install", + ]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + println!("{cmd:?}"); + if !cmd.status()?.success() { + return Err(LlvmBuildError::CmakeBuild.into()); + } + } + None => { + println!("Building on host"); + + let mut cmd = Command::new("cmake"); + cmd.args(cmake_args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + println!("{cmd:?}"); + if !cmd.status()?.success() { + return Err(LlvmBuildError::CmakeBuild.into()); + } + + let mut cmd = Command::new("cmake"); + cmd.args(["--build", &build_dir, "-j", "--target", "install"]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + println!("{cmd:?}"); + if !cmd.status()?.success() { + return Err(LlvmBuildError::CmakeBuild.into()); + } + } + } + + println!( + "Installed LLVM artifacts in {}", + llvm_install_dir.to_string_lossy() + ); + + Ok(()) +} diff --git a/xtask/src/cargo.rs b/xtask/src/cargo.rs new file mode 100644 index 00000000..680178eb --- /dev/null +++ b/xtask/src/cargo.rs @@ -0,0 +1,290 @@ +use std::{ + env, + ffi::{OsStr, OsString}, + fs::read_dir, + os::unix::ffi::OsStringExt, + path::Path, + process::{Command, Stdio}, +}; + +use anyhow::Context; +use clap::{ArgAction, Parser, ValueEnum}; +use target_lexicon::{Environment, Triple}; +use thiserror::Error; + +use crate::{ + containers::{ContainerEngine, ContainerError}, + target::{SupportedTriple, TripleExt}, +}; + +#[derive(Debug, Error)] +pub enum CargoError { + #[error(transparent)] + Container(ContainerError), + #[error("cargo build failed")] + CargoBuild, + #[error("could not find a git repository")] + RepositoryNotFound, +} + +#[derive(Clone, ValueEnum)] +pub enum LinkType { + Dynamic, + Static, +} + +impl ToString for LinkType { + fn to_string(&self) -> String { + match self { + Self::Dynamic => "dylib".to_owned(), + Self::Static => "static".to_owned(), + } + } +} + +impl LinkType { + fn default(triple: &Triple) -> Self { + // Link system libraries dynamically only on GNU/Linux. The reason + // being - Debian doesn't ship static zlib and zstd. + // Static linking works fine on other systems (BSDs, macOS, + // musl/Linux). + if triple.environment == Environment::Gnu { + Self::Dynamic + } else { + Self::Static + } + } +} + +#[derive(Parser)] +pub struct CargoArgs { + /// Container engine (if not provided, is going to be autodetected). + #[arg(long)] + container_engine: Option, + + /// Space or comma separated list of features to activate. + #[arg(short, long)] + features: Vec, + + /// Activate all available features. + #[arg(long)] + all_features: bool, + + /// Do not activate the `default` feature. + #[arg(long)] + no_default_features: bool, + + #[arg(long)] + link_type: Option, + + /// Prefix in which LLVM libraries are going to be installed after build. + #[arg(long)] + llvm_install_dir: Option, + + /// Build artifacts in release mode, with optimizations. + #[arg(long)] + release: bool, + + /// Target triple (optional). + #[arg(short, long)] + target: Option, + + /// Use verbose output (-vv very verbose/build.rs output). + #[arg(short, long, action = ArgAction::Count)] + verbose: u8, +} + +pub fn run_cargo(args: CargoArgs, command: &OsStr) -> anyhow::Result<()> { + let CargoArgs { + container_engine, + mut features, + all_features, + no_default_features, + link_type, + llvm_install_dir, + release, + target, + verbose, + } = args; + + // Disable the LLVM linking capabilities from llvm-sys, they don't support + // cross-compilation. Instead, we are building our own linking flags, based + // on the specified `llvm_install_dir`. + features.push(OsString::from("llvm-sys/no-llvm-linking")); + + let triple: Triple = match target { + Some(target) => target.into(), + None => target_lexicon::HOST, + }; + + let link_type = link_type.unwrap_or(LinkType::default(&triple)); + let sysroot = triple.sysroot(); + + let llvm_install_dir = match llvm_install_dir { + Some(llvm_install_dir) => llvm_install_dir, + None => Path::new("/tmp") + .join(format!("aya-llvm-{triple}")) + .into_os_string(), + }; + + let workdir = Command::new("git") + .args(["rev-parse", "--show-toplevel"]) + .output(); + let workdir = match workdir { + Ok(output) if output.status.success() => { + OsString::from_vec( + // Remove the trailing `\n` character. + output.stdout[..output.stdout.len() - 1].to_vec(), + ) + } + Ok(_) => { + return Err(CargoError::RepositoryNotFound.into()); + } + Err(_) => { + return Err(CargoError::RepositoryNotFound.into()); + } + }; + + let mut rustflags = OsString::from("RUSTFLAGS=-C linker=clang"); + if triple.is_cross() { + rustflags.push(" -C link-arg=--target="); + rustflags.push(triple.to_string()); + } + if let Some(sysroot) = sysroot { + rustflags.push(" -C link-arg=--sysroot="); + rustflags.push(sysroot.clone()); + } + for libdir in triple.libdirs() { + rustflags.push(" -L native="); + rustflags.push(libdir); + } + rustflags.push(" -L native="); + rustflags.push(Path::new(&llvm_install_dir).join("lib")); + rustflags.push(format!(" -l {}=c", link_type.to_string())); + rustflags.push(format!(" -l {}=rt", link_type.to_string())); + rustflags.push(format!(" -l {}=dl", link_type.to_string())); + rustflags.push(format!(" -l {}=m", link_type.to_string())); + rustflags.push(format!(" -l {}=z", link_type.to_string())); + rustflags.push(format!(" -l {}=zstd", link_type.to_string())); + if triple.environment == Environment::Gnu { + rustflags.push(format!(" -l {}=stdc++", link_type.to_string())); + } else { + rustflags.push(format!(" -l {}=c++_static", link_type.to_string())); + rustflags.push(format!(" -l {}=c++abi", link_type.to_string())); + } + + for entry in read_dir(Path::new(&llvm_install_dir).join("lib")) + .context("LLVM build directory not found")? + { + let entry = entry.context("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") { + rustflags.push(" -l static="); + rustflags.push( + path.file_name() + .unwrap() + .to_string_lossy() + .strip_prefix("lib") + .unwrap() + .strip_suffix(".a") + .unwrap(), + ); + } + } + + match triple.container_image() { + Some((container_image, _)) => { + println!("Using container image {container_image}"); + + let container_engine = + container_engine.unwrap_or(ContainerEngine::autodetect().ok_or( + CargoError::Container(ContainerError::ContainerEngineNotFound), + )?); + + let mut llvm_prefix = OsString::from("LLVM_SYS_191_PREFIX="); + llvm_prefix.push(&llvm_install_dir); + + let rustup_toolchain = env::var("RUSTUP_TOOLCHAIN").unwrap(); + let rustup_toolchain = rustup_toolchain.split('-').next().unwrap(); + let mut rustup_toolchain_triple = target_lexicon::HOST; + rustup_toolchain_triple.environment = triple.environment; + let rustup_toolchain = + format!("{rustup_toolchain}-{}", rustup_toolchain_triple.to_string()); + let mut rustup_toolchain_arg = OsString::from("RUSTUP_TOOLCHAIN="); + rustup_toolchain_arg.push(rustup_toolchain); + + let mut workdir_arg = workdir; + workdir_arg.push(":/usr/local/src/bpf-linker"); + + let mut llvm_arg = llvm_install_dir.clone(); + llvm_arg.push(":"); + llvm_arg.push(&llvm_install_dir); + + let mut cmd = Command::new(container_engine.to_string()); + cmd.args([ + OsStr::new("run"), + OsStr::new("--rm"), + OsStr::new("-e"), + &llvm_prefix, + OsStr::new("-e"), + &rustflags, + OsStr::new("-e"), + &rustup_toolchain_arg, + OsStr::new("-it"), + OsStr::new("-w"), + OsStr::new("/usr/local/src/bpf-linker"), + OsStr::new("-v"), + &workdir_arg, + OsStr::new("-v"), + &llvm_arg, + OsStr::new(&container_image), + OsStr::new("cargo"), + OsStr::new("--config"), + OsStr::new("target.aarch64-unknown-linux-gnu.runner = 'qemu-aarch64 -L /usr/aarch64-linux-gnu/lib -L /usr/lib/aarch64-linux-gnu'"), + OsStr::new("--config"), + OsStr::new("target.aarch64-unknown-linux-musl.runner = 'qemu-aarch64'"), + OsStr::new("--config"), + OsStr::new("target.riscv64gc-unknown-linux-gnu.runner = 'qemu-riscv64 -L /usr/riscv64-linux-gnu/lib -L /usr/lib/riscv64-linux-gnu'"), + OsStr::new("--config"), + OsStr::new("target.riscv64gc-unknown-linux-musl.runner = 'qemu-riscv64'"), + OsStr::new("--config"), + OsStr::new("target.x86_64-unknown-linux-gnu.runner = 'qemu-x86_64 -L /usr/x86_64-linux-gnu/lib -L /usr/lib/x86_64-linux-gnu'"), + OsStr::new("--config"), + OsStr::new("target.x86_64-unknown-linux-musl.runner = 'qemu-x86_64'"), + command, + OsStr::new("--target"), + OsStr::new(&triple.to_string()), + ]); + match verbose { + 0 => {} + 1 => { + cmd.arg("-v"); + } + _ => { + cmd.arg("-vv"); + } + } + if release { + cmd.arg("--release"); + } + if !features.is_empty() { + cmd.arg("--features"); + cmd.args(features); + } + if all_features { + cmd.arg("--all-features"); + } + if no_default_features { + cmd.arg("--no-default-features"); + } + cmd.stdout(Stdio::inherit()).stderr(Stdio::inherit()); + println!("{cmd:?}"); + if !cmd.status()?.success() { + return Err(CargoError::CargoBuild.into()); + } + } + None => {} + } + + Ok(()) +} diff --git a/xtask/src/containers.rs b/xtask/src/containers.rs new file mode 100644 index 00000000..dc005fb1 --- /dev/null +++ b/xtask/src/containers.rs @@ -0,0 +1,146 @@ +use std::process::{Command, Stdio}; + +use chrono::Utc; +use clap::{Parser, ValueEnum}; +use target_lexicon::Triple; +use thiserror::Error; +use which::which; + +use crate::target::{SupportedTriple, TripleExt}; + +#[derive(Debug, Error)] +pub enum ContainerError { + #[error("no supported container engine (docker, podman) was found")] + ContainerEngineNotFound, + #[error("containerized builds are not supported for target {0}")] + UnsupportedTarget(String), + #[error("failed to build a container image")] + ContainerImageBuild, + #[error("failed to push a container image")] + ContainerImagePush, + #[error("failed to tag a container image as latest")] + ContainerImageTag, +} + +#[derive(Clone, ValueEnum)] +pub enum ContainerEngine { + Docker, + Podman, +} + +impl ToString for ContainerEngine { + fn to_string(&self) -> String { + match self { + Self::Docker => "docker".to_owned(), + Self::Podman => "podman".to_owned(), + } + } +} + +impl ContainerEngine { + pub fn autodetect() -> Option { + if which("docker").is_ok() { + return Some(Self::Docker); + } + if which("podman").is_ok() { + return Some(Self::Podman); + } + None + } +} + +#[derive(Parser)] +pub struct BuildContainerImageArgs { + /// Container engine (if not provided, is going to be autodetected) + #[arg(long)] + container_engine: Option, + + /// Do not use existing cached images for the container build. Build from + /// the start with a new set of cached layers. + #[arg(long)] + no_cache: bool, + + /// Push the image after build. + #[arg(long)] + push: bool, + + /// Target triple (optional) + #[arg(short, long)] + target: Option, +} + +fn push_image(container_engine: &ContainerEngine, tag: &str) -> anyhow::Result<()> { + let mut cmd = Command::new(container_engine.to_string()); + cmd.args(["push", tag]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + + println!("{cmd:?}"); + if !cmd.status()?.success() { + return Err(ContainerError::ContainerImagePush.into()); + } + Ok(()) +} + +pub fn build_container_image(args: BuildContainerImageArgs) -> anyhow::Result<()> { + let BuildContainerImageArgs { + container_engine, + no_cache, + push, + target, + } = args; + + let triple: Triple = match target { + Some(target) => target.into(), + None => target_lexicon::HOST, + }; + + match triple.container_image() { + Some((tag, dockerfile)) => { + let container_engine = container_engine.unwrap_or( + ContainerEngine::autodetect().ok_or(ContainerError::ContainerEngineNotFound)?, + ); + + let date = Utc::now().format("%Y%m%d"); + let tag_with_date = format!("{tag}:{date}"); + let tag_latest = format!("{tag}:latest"); + + let mut cmd = Command::new(container_engine.to_string()); + cmd.args([ + "buildx", + "build", + "-t", + &tag_with_date, + "-f", + &dockerfile, + ".", + ]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + if no_cache { + cmd.arg("--no-cache"); + } + println!("{cmd:?}"); + if !cmd.status()?.success() { + return Err(ContainerError::ContainerImageBuild.into()); + } + + let mut cmd = Command::new(container_engine.to_string()); + cmd.args(["tag", &tag_with_date, &tag_latest]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + println!("{cmd:?}"); + if !cmd.status()?.success() { + return Err(ContainerError::ContainerImageTag.into()); + } + + if push { + push_image(&container_engine, &tag_with_date)?; + push_image(&container_engine, &tag_latest)?; + } + + Ok(()) + } + None => Err(ContainerError::UnsupportedTarget(triple.to_string()).into()), + } +} diff --git a/xtask/src/llvm.rs b/xtask/src/llvm.rs new file mode 100644 index 00000000..f5e7853b --- /dev/null +++ b/xtask/src/llvm.rs @@ -0,0 +1,128 @@ +use std::{ffi::OsString, path::Path}; + +pub enum System { + Darwin, + Linux, +} + +impl ToString for System { + fn to_string(&self) -> String { + match self { + Self::Darwin => "Darwin".to_owned(), + Self::Linux => "Linux".to_owned(), + } + } +} + +pub enum Processor { + Aarch64, + Riscv64, + X86_64, +} + +impl ToString for Processor { + fn to_string(&self) -> String { + match self { + Self::Aarch64 => "aarch64".to_owned(), + Self::Riscv64 => "riscv64".to_owned(), + Self::X86_64 => "x86_64".to_owned(), + } + } +} + +pub struct LlvmBuildConfig { + pub c_compiler: String, + pub cxx_compiler: String, + pub compiler_target: Option, + pub cxxflags: Option, + pub ldflags: Option, + pub install_prefix: OsString, + pub skip_install_rpath: bool, + pub system: System, + pub processor: Processor, + pub target_triple: String, +} + +impl LlvmBuildConfig { + pub fn cmake_args(&self) -> Vec { + let LlvmBuildConfig { + c_compiler, + cxx_compiler, + compiler_target, + cxxflags, + ldflags, + install_prefix, + skip_install_rpath, + system, + processor, + target_triple, + } = self; + + // NOTE(vadorovsky): I wish there was a `format!` equivalent for + // `OsString`... + let mut install_arg = OsString::from("-DCMAKE_INSTALL_PREFIX="); + install_arg.push(install_prefix); + let mut rpath_arg = OsString::from("-DCMAKE_INSTALL_RPATH="); + rpath_arg.push(Path::new(install_prefix).join("lib")); + + let mut args = vec![ + OsString::from("-S"), + OsString::from("llvm"), + OsString::from("-B"), + OsString::from(format!("aya-build-{}", target_triple)), + OsString::from("-DCMAKE_BUILD_TYPE=RelWithDebInfo"), + OsString::from(format!("-DCMAKE_ASM_COMPILER={c_compiler}")), + OsString::from("-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON"), + OsString::from(format!("-DCMAKE_C_COMPILER={c_compiler}")), + OsString::from(format!("-DCMAKE_CXX_COMPILER={cxx_compiler}")), + install_arg, + rpath_arg, + OsString::from(format!("-DCMAKE_SYSTEM_NAME={}", system.to_string())), + OsString::from(format!( + "-DCMAKE_SYSTEM_PROCESSOR={}", + processor.to_string() + )), + OsString::from("-DLLVM_BUILD_EXAMPLES=OFF"), + OsString::from("-DLLVM_BUILD_STATIC=ON"), + OsString::from("-DLLVM_ENABLE_ASSERTIONS=ON"), + OsString::from("-DLLVM_ENABLE_LIBCXX=ON"), + OsString::from("-DLLVM_ENABLE_LIBXML2=OFF"), + OsString::from("-DLLVM_ENABLE_PROJECTS="), + OsString::from("-DLLVM_ENABLE_RUNTIMES="), + OsString::from(format!("-DLLVM_HOST_TRIPLE={target_triple}")), + OsString::from("-DLLVM_INCLUDE_TESTS=OFF"), + OsString::from("-DLLVM_INCLUDE_TOOLS=OFF"), + OsString::from("-DLLVM_INCLUDE_UTILS=OFF"), + OsString::from("-DLLVM_TARGETS_TO_BUILD=BPF"), + OsString::from("-DLLVM_USE_LINKER=lld"), + ]; + + if let Some(compiler_target) = compiler_target { + args.push(OsString::from(format!( + "-DCMAKE_ASM_COMPILER_TARGET={compiler_target}" + ))); + args.push(OsString::from(format!( + "-DCMAKE_C_COMPILER_TARGET={compiler_target}" + ))); + args.push(OsString::from(format!( + "-DCMAKE_CXX_COMPILER_TARGET={compiler_target}" + ))); + } + if let Some(cxxflags) = cxxflags { + args.push(OsString::from(format!("-DCMAKE_CXX_FLAGS='{cxxflags}'"))); + } + if let Some(ldflags) = ldflags { + args.push(OsString::from(format!( + "-DCMAKE_EXE_LINKER_FLAGS='{ldflags}'" + ))); + args.push(OsString::from(format!( + "-DCMAKE_SHARED_LINKER_FLAGS='{ldflags}" + ))); + } + if *skip_install_rpath { + args.push(OsString::from("-DCMAKE_SKIP_INSTALL_RPATH=ON".to_owned())); + } + + args + } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000..69f192f5 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,56 @@ +use clap::{Parser, Subcommand}; + +mod build; +mod build_llvm; +mod cargo; +mod containers; +mod llvm; +mod target; +mod tempdir; +mod test; + +use crate::{ + build::build, + build_llvm::{build_llvm, BuildLlvmArgs}, + cargo::CargoArgs, + containers::{build_container_image, BuildContainerImageArgs}, + test::test, +}; + +/// The `xtask` CLI. +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// Subcommands + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Build bpf-linker. + Build(CargoArgs), + /// Build container image. + BuildContainerImage(BuildContainerImageArgs), + /// Build LLVM in a container. + BuildLlvm(BuildLlvmArgs), + /// Test bpf-linker. + Test(CargoArgs), +} + +fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + match cli.command { + Commands::Build(args) => build(args)?, + Commands::BuildContainerImage(args) => { + build_container_image(args)?; + } + Commands::BuildLlvm(args) => { + build_llvm(args)?; + } + Commands::Test(args) => test(args)?, + } + + Ok(()) +} diff --git a/xtask/src/target.rs b/xtask/src/target.rs new file mode 100644 index 00000000..25fb1d06 --- /dev/null +++ b/xtask/src/target.rs @@ -0,0 +1,410 @@ +use std::ffi::{OsStr, OsString}; + +use clap::ValueEnum; +use target_lexicon::{ + Aarch64Architecture, Architecture, BinaryFormat, Environment, OperatingSystem, + Riscv64Architecture, Triple, Vendor, +}; + +use crate::llvm::{LlvmBuildConfig, Processor, System}; + +#[derive(Clone)] +pub enum SupportedTriple { + Aarch64AppleDarwin, + Aarch64UnknownLinuxGnu, + Aarch64UnknownLinuxMusl, + Riscv64UnknownLinuxGnu, + Riscv64UnknownLinuxMusl, + X86_64AppleDarwin, + X86_64UnknownLinuxGnu, + X86_64UnknownLinuxMusl, +} + +impl ValueEnum for SupportedTriple { + fn value_variants<'a>() -> &'a [Self] { + &[ + Self::Aarch64AppleDarwin, + Self::Aarch64UnknownLinuxGnu, + Self::Aarch64UnknownLinuxMusl, + Self::Riscv64UnknownLinuxGnu, + Self::Riscv64UnknownLinuxMusl, + Self::X86_64AppleDarwin, + Self::X86_64UnknownLinuxGnu, + Self::X86_64UnknownLinuxMusl, + ] + } + + fn to_possible_value(&self) -> Option { + Some(match self { + Self::Aarch64AppleDarwin => clap::builder::PossibleValue::new("aarch64-apple-darwin"), + Self::Aarch64UnknownLinuxGnu => { + clap::builder::PossibleValue::new("aarch64-unknown-linux-gnu") + } + Self::Aarch64UnknownLinuxMusl => { + clap::builder::PossibleValue::new("aarch64-unknown-linux-musl") + } + Self::Riscv64UnknownLinuxGnu => { + clap::builder::PossibleValue::new("riscv64-unknown-linux-gnu") + } + Self::Riscv64UnknownLinuxMusl => { + clap::builder::PossibleValue::new("riscv64-unknown-linux-musl") + } + Self::X86_64AppleDarwin => clap::builder::PossibleValue::new("x86_64-apple-darwin"), + Self::X86_64UnknownLinuxGnu => { + clap::builder::PossibleValue::new("x86_64-unknown-linux-gnu") + } + Self::X86_64UnknownLinuxMusl => { + clap::builder::PossibleValue::new("x86_64-unknown-linux-musl") + } + }) + } +} + +impl From for Triple { + fn from(value: SupportedTriple) -> Self { + match value { + SupportedTriple::Aarch64AppleDarwin => Triple { + architecture: Architecture::Aarch64(Aarch64Architecture::Aarch64), + vendor: Vendor::Apple, + operating_system: OperatingSystem::Darwin, + environment: Environment::Unknown, + binary_format: BinaryFormat::Macho, + }, + SupportedTriple::Aarch64UnknownLinuxGnu => Triple { + architecture: Architecture::Aarch64(Aarch64Architecture::Aarch64), + vendor: Vendor::Unknown, + operating_system: OperatingSystem::Linux, + environment: Environment::Gnu, + binary_format: BinaryFormat::Elf, + }, + SupportedTriple::Aarch64UnknownLinuxMusl => Triple { + architecture: Architecture::Aarch64(Aarch64Architecture::Aarch64), + vendor: Vendor::Unknown, + operating_system: OperatingSystem::Linux, + environment: Environment::Musl, + binary_format: BinaryFormat::Elf, + }, + SupportedTriple::Riscv64UnknownLinuxGnu => Triple { + architecture: Architecture::Riscv64(Riscv64Architecture::Riscv64gc), + vendor: Vendor::Unknown, + operating_system: OperatingSystem::Linux, + environment: Environment::Musl, + binary_format: BinaryFormat::Elf, + }, + SupportedTriple::Riscv64UnknownLinuxMusl => Triple { + architecture: Architecture::Riscv64(Riscv64Architecture::Riscv64gc), + vendor: Vendor::Unknown, + operating_system: OperatingSystem::Linux, + environment: Environment::Musl, + binary_format: BinaryFormat::Elf, + }, + SupportedTriple::X86_64AppleDarwin => Triple { + architecture: Architecture::X86_64, + vendor: Vendor::Apple, + operating_system: OperatingSystem::Darwin, + environment: Environment::Unknown, + binary_format: BinaryFormat::Macho, + }, + SupportedTriple::X86_64UnknownLinuxGnu => Triple { + architecture: Architecture::X86_64, + vendor: Vendor::Unknown, + operating_system: OperatingSystem::Linux, + environment: Environment::Gnu, + binary_format: BinaryFormat::Elf, + }, + SupportedTriple::X86_64UnknownLinuxMusl => Triple { + architecture: Architecture::X86_64, + vendor: Vendor::Unknown, + operating_system: OperatingSystem::Linux, + environment: Environment::Musl, + binary_format: BinaryFormat::Elf, + }, + } + } +} + +pub trait TripleExt { + fn containerized_build(&self) -> bool; + fn container_image(&self) -> Option<(String, String)>; + fn llvm_build_config(&self, install_prefix: &OsStr) -> Option; + fn libdirs(&self) -> Vec; + fn sysroot(&self) -> Option; + fn is_cross(&self) -> bool; +} + +impl TripleExt for Triple { + fn containerized_build(&self) -> bool { + let Triple { + operating_system, .. + } = self; + *operating_system == OperatingSystem::Linux + } + + fn container_image(&self) -> Option<(String, String)> { + let prefix = if self.is_cross() { "cross" } else { "native" }; + if self.containerized_build() { + let tag = format!("{prefix}-{self}"); + let full_tag = format!("ghcr.io/aya-rs/bpf-linker/{tag}"); + let dockerfile = format!("docker/Dockerfile.{tag}"); + Some((full_tag, dockerfile)) + } else { + None + } + } + + fn llvm_build_config(&self, install_prefix: &OsStr) -> Option { + let Triple { + architecture, + operating_system, + environment, + .. + } = self; + let install_prefix = install_prefix.to_owned(); + + match (architecture, operating_system, environment) { + (Architecture::Aarch64(_), OperatingSystem::Darwin, Environment::Unknown) => { + Some(LlvmBuildConfig { + c_compiler: "clang".to_owned(), + cxx_compiler: "clang++".to_owned(), + compiler_target: None, + cxxflags: None, + ldflags: None, + install_prefix, + skip_install_rpath: false, + system: System::Darwin, + processor: Processor::Aarch64, + target_triple: "aarch64-apple-darwin".to_owned(), + }) + } + (Architecture::Aarch64(_), OperatingSystem::Linux, Environment::Gnu) => { + Some(LlvmBuildConfig { + c_compiler: "clang".to_owned(), + cxx_compiler: "clang++".to_owned(), + compiler_target: Some("aarch64-linux-gnu".to_owned()), + cxxflags: None, + ldflags: None, + install_prefix, + skip_install_rpath: false, + system: System::Linux, + processor: Processor::Aarch64, + target_triple: "aarch64-linux-gnu".to_owned(), + }) + } + (Architecture::Aarch64(_), OperatingSystem::Linux, Environment::Musl) => { + // Gentoo's crossdev doesn't work with triples not containing + // the `gentoo` vendor. + Some(LlvmBuildConfig { + c_compiler: if self.is_cross() { + "aarch64-gentoo-linux-musl-clang".to_owned() + } else { + "clang".to_owned() + }, + cxx_compiler: if self.is_cross() { + "aarch64-gentoo-linux-musl-clang++".to_owned() + } else { + "clang++".to_owned() + }, + // The clang wrapper specified above takes care of setting + // the target. + compiler_target: None, + cxxflags: Some("-stdlib=libc++".to_owned()), + ldflags: Some( + "-rtlib=compiler-rt -unwindlib=libunwind -lc++ -lc++abi".to_owned(), + ), + install_prefix, + skip_install_rpath: false, + system: System::Linux, + processor: Processor::Aarch64, + target_triple: "aarch64-gentoo-linux-musl".to_owned(), + }) + } + (Architecture::Riscv64(_), OperatingSystem::Linux, Environment::Gnu) => { + Some(LlvmBuildConfig { + c_compiler: "clang".to_owned(), + cxx_compiler: "clang++".to_owned(), + compiler_target: Some("riscv64-linux-gnu".to_owned()), + cxxflags: None, + ldflags: None, + install_prefix, + skip_install_rpath: false, + system: System::Linux, + processor: Processor::Riscv64, + target_triple: "riscv64-linux-gnu".to_owned(), + }) + } + (Architecture::Riscv64(_), OperatingSystem::Linux, Environment::Musl) => { + // NOTE(vadorovsky): Gentoo's crossdev doesn't work with + // triples not containing the `gentoo` vendor. + Some(LlvmBuildConfig { + c_compiler: if self.is_cross() { + "riscv64-gentoo-linux-musl-clang".to_owned() + } else { + "clang".to_owned() + }, + cxx_compiler: if self.is_cross() { + "riscv64-gentoo-linux-musl-clang++".to_owned() + } else { + "clang++".to_owned() + }, + // The clang wrapper specified above takes care of setting + // the target. + compiler_target: None, + cxxflags: None, + ldflags: None, + install_prefix, + skip_install_rpath: false, + system: System::Linux, + processor: Processor::Riscv64, + target_triple: "riscv64-gentoo-linux-musl".to_owned(), + }) + } + (Architecture::X86_64, OperatingSystem::Darwin, Environment::Unknown) => { + Some(LlvmBuildConfig { + c_compiler: "clang".to_owned(), + cxx_compiler: "clang++".to_owned(), + cxxflags: None, + compiler_target: None, + ldflags: None, + install_prefix, + skip_install_rpath: false, + system: System::Darwin, + processor: Processor::X86_64, + target_triple: "x86_64-apple-darwin".to_owned(), + }) + } + (Architecture::X86_64, OperatingSystem::Linux, Environment::Gnu) => { + Some(LlvmBuildConfig { + c_compiler: "clang".to_owned(), + cxx_compiler: "clang++".to_owned(), + compiler_target: Some("x86_64-linux-gnu".to_owned()), + cxxflags: None, + ldflags: None, + install_prefix, + skip_install_rpath: false, + system: System::Linux, + processor: Processor::X86_64, + target_triple: "x86_64-linux-gnu".to_owned(), + }) + } + (Architecture::X86_64, OperatingSystem::Linux, Environment::Musl) => { + // NOTE(vadorovsky): Gentoo's crossdev doesn't work with + // triples not containing the `gentoo` vendor. + Some(LlvmBuildConfig { + c_compiler: if self.is_cross() { + "x86_64-gentoo-linux-musl-clang".to_owned() + } else { + "clang".to_owned() + }, + cxx_compiler: if self.is_cross() { + "x86_64-gentoo-linux-musl-clang++".to_owned() + } else { + "clang++".to_owned() + }, + // The clang wrapper specified above takes care of setting + // the target. + compiler_target: None, + cxxflags: None, + ldflags: None, + install_prefix, + skip_install_rpath: false, + system: System::Linux, + processor: Processor::X86_64, + target_triple: "x86_64-gentoo-linux-musl".to_owned(), + }) + } + (_, _, _) => None, + } + } + + fn libdirs(&self) -> Vec { + if !self.is_cross() { + return vec![OsString::from("/lib"), OsString::from("/usr/lib")]; + } + + let Triple { + architecture, + operating_system, + environment, + .. + } = self; + + // Cross packages in Debian (which we use for GNU builds) install + // libraries either in: + // + // - `/usr//lib` + // - `/usr/lib/` + // + // Cross packages in Gentoo (which we use for musl builds) are always + // installed in the sysroot (`/usr/`), which follows the same + // directory structure as host environments. + match (architecture, operating_system, environment) { + (Architecture::Aarch64(_), OperatingSystem::Linux, Environment::Gnu) => { + vec![ + OsString::from("/usr/aarch64-linux-gnu/lib"), + OsString::from("/usr/lib/aarch64-linux-gnu"), + ] + } + (Architecture::Aarch64(_), OperatingSystem::Linux, Environment::Musl) => { + vec![ + OsString::from("/usr/aarch64-gentoo-linux-musl/lib"), + OsString::from("/usr/aarch64-gentoo-linux-musl/usr/lib"), + ] + } + (Architecture::Riscv64(_), OperatingSystem::Linux, Environment::Gnu) => { + vec![ + OsString::from("/usr/riscv64-linux-gnu/lib"), + OsString::from("/usr/lib/riscv64-linux-gnu"), + ] + } + (Architecture::Riscv64(_), OperatingSystem::Linux, Environment::Musl) => { + vec![ + OsString::from("/usr/riscv64-gentoo-linux-musl/lib"), + OsString::from("/usr/riscv64-gentoo-linux-musl/usr/lib"), + ] + } + (Architecture::X86_64, OperatingSystem::Linux, Environment::Gnu) => { + vec![ + OsString::from("/usr/x86_64-linux-gnu/lib"), + OsString::from("/usr/lib/x86_64-linux-gnu"), + ] + } + (Architecture::X86_64, OperatingSystem::Linux, Environment::Musl) => { + vec![ + OsString::from("/usr/x86_64-gentoo-linux-musl/lib"), + OsString::from("/usr/x86_64-gentoo-linux-musl/usr/lib"), + ] + } + (_, _, _) => vec![OsString::from("/lib"), OsString::from("/usr/lib")], + } + } + + fn sysroot(&self) -> Option { + if !self.is_cross() { + return None; + } + + let Triple { + architecture, + operating_system, + environment, + .. + } = self; + + match (architecture, operating_system, environment) { + (Architecture::Aarch64(_), OperatingSystem::Linux, Environment::Musl) => { + Some(OsString::from("/usr/aarch64-gentoo-linux-musl")) + } + (Architecture::Riscv64(_), OperatingSystem::Linux, Environment::Musl) => { + Some(OsString::from("/usr/riscv64-gentoo-linux-musl")) + } + (Architecture::X86_64, OperatingSystem::Linux, Environment::Musl) => { + Some(OsString::from("/usr/x86_64-gentoo-linux-musl")) + } + (_, _, _) => None, + } + } + + fn is_cross(&self) -> bool { + self.architecture != target_lexicon::HOST.architecture + } +} diff --git a/xtask/src/tempdir.rs b/xtask/src/tempdir.rs new file mode 100644 index 00000000..7413d3eb --- /dev/null +++ b/xtask/src/tempdir.rs @@ -0,0 +1,51 @@ +use std::{ + env, + ffi::{OsStr, OsString}, + fs::{self, create_dir}, + io, + path::{Path, PathBuf}, +}; +use uuid::Uuid; + +/// A temporary directory which is cleaned on `drop` (unless the `preserve` field +/// is `true`. +pub struct TempDir { + /// Path to the temp directory. + dir_path: PathBuf, + /// Whether to preserve the temp directory after `drop`. If `false`, it + /// gets removed automatically. + preserve: bool, +} + +impl TempDir { + pub fn new(prefix: &str, preserve: bool) -> io::Result { + let dir_path = env::temp_dir().join(format!("{prefix}-{}", Uuid::new_v4())); + create_dir(dir_path.as_path())?; + Ok(Self { dir_path, preserve }) + } + + pub fn to_os_string(&self) -> OsString { + self.dir_path.as_os_str().to_owned() + } +} + +impl AsRef for TempDir { + fn as_ref(&self) -> &OsStr { + self.dir_path.as_os_str() + } +} + +impl AsRef for TempDir { + fn as_ref(&self) -> &Path { + self.dir_path.as_path() + } +} + +impl Drop for TempDir { + /// Removes the temp directory if requested. + fn drop(&mut self) { + if !self.preserve && self.dir_path.exists() { + let _ = fs::remove_dir_all(&self.dir_path); + } + } +} diff --git a/xtask/src/test.rs b/xtask/src/test.rs new file mode 100644 index 00000000..6225458e --- /dev/null +++ b/xtask/src/test.rs @@ -0,0 +1,7 @@ +use std::ffi::OsStr; + +use crate::cargo::{run_cargo, CargoArgs}; + +pub fn test(args: CargoArgs) -> anyhow::Result<()> { + run_cargo(args, OsStr::new("test")) +}