diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 741e135..d960bd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,12 +21,14 @@ concurrency: jobs: build: - runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: + runner: + - macos-13 # x86 + - macos-latest # arm64 + - ubuntu-latest # x86 rust: - - stable - 1.80.1 program: - kprobe @@ -48,6 +50,7 @@ jobs: - raw_tracepoint - tp_btf - tracepoint + runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@v4 @@ -57,6 +60,21 @@ jobs: components: rust-src,rustfmt - uses: dtolnay/rust-toolchain@master + if: runner.os == 'macOS' && runner.arch == 'X64' + with: + toolchain: ${{ matrix.rust }} + targets: x86_64-unknown-linux-musl + components: clippy + + - uses: dtolnay/rust-toolchain@master + if: runner.os == 'macOS' && runner.arch == 'ARM64' + with: + toolchain: ${{ matrix.rust }} + targets: aarch64-unknown-linux-musl + components: clippy + + - uses: dtolnay/rust-toolchain@master + if: runner.os != 'macOS' with: toolchain: ${{ matrix.rust }} components: clippy @@ -65,8 +83,16 @@ jobs: - uses: taiki-e/install-action@v2 with: - tool: bpf-linker,cargo-generate + tool: cargo-generate + + - run: brew install filosottile/musl-cross/musl-cross llvm + if: runner.os == 'macos' + + - run: cargo install bpf-linker --git https://github.com/aya-rs/bpf-linker.git --no-default-features + if: runner.os == 'macos' + + - run: cargo install bpf-linker --git https://github.com/aya-rs/bpf-linker.git + if: runner.os != 'macos' + - - run: sudo apt update - - run: sudo apt install expect - - run: ./test.sh ${{ github.workspace }} ${{ matrix.program }} + - run: ./test.py ${{ github.workspace }} ${{ matrix.program }} diff --git a/README.md b/README.md index cb40246..af1f095 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,12 @@ experience; this compromise necessitates the use of `xtask` to actually build th Cross compilation should work on both Intel and Apple Silicon Macs. -```bash +```shell AYA_BUILD_EBPF=true \ CC=${ARCH}-linux-musl-gcc \ -RUSTFLAGS="-C linker=${ARCH}-linux-musl-gcc" \ - cargo build --package {{project-name}} --release --target=${ARCH}-unknown-linux-musl + cargo build --package {{project-name}} --release \ + --target=${ARCH}-unknown-linux-musl \ + --config=target.${ARCH}-unknown-linux-musl.linker=\"${ARCH}-linux-musl-gcc\" ``` The cross-compiled program `target/${ARCH}-unknown-linux-musl/release/{{project-name}}` can be copied to a Linux server or VM and run there. diff --git a/cargo-generate.toml b/cargo-generate.toml index 6026555..efa3ccc 100644 --- a/cargo-generate.toml +++ b/cargo-generate.toml @@ -1,6 +1,6 @@ [template] cargo_generate_version = ">=0.10.0" -ignore = [".github", "test.sh"] +ignore = [".github", "test.py"] [placeholders.program_type] type = "string" diff --git a/test.py b/test.py new file mode 100755 index 0000000..dfb037e --- /dev/null +++ b/test.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 + +import argparse +import io +import os +import platform +import subprocess +import sys +import tempfile +from typing import TypedDict + + +class SubprocessArgs(TypedDict, total=False): + cwd: str + env: dict[str, str] + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Generate and build a Rust project using cargo." + ) + parser.add_argument("template_dir", help="Template directory") + parser.add_argument("program_type", help="Program type") + args = parser.parse_args() + + match args.program_type: + case "cgroup_sockopt": + additional_args = ["-d", "sockopt_target=getsockopt"] + case "classifier" | "cgroup_skb": + additional_args = ["-d", "direction=Ingress"] + case "fentry" | "fexit": + additional_args = ["-d", "fn_name=try_to_wake_up"] + case "kprobe" | "kretprobe": + additional_args = ["-d", "kprobe=do_unlinkat"] + case "lsm": + additional_args = ["-d", "lsm_hook=file_open"] + case "raw_tracepoint": + additional_args = ["-d", "tracepoint_name=sys_enter"] + case "sk_msg": + additional_args = ["-d", "sock_map=SOCK_MAP"] + case "tp_btf": + additional_args = ["-d", "tracepoint_name=net_dev_queue"] + case "tracepoint": + additional_args = [ + "-d", + "tracepoint_category=net", + "-d", + "tracepoint_name=net_dev_queue", + ] + case "uprobe" | "uretprobe": + additional_args = [ + "-d", + "uprobe_target=/proc/self/exe", + "-d", + "uprobe_fn_name=main", + ] + case _: + additional_args = [] + + CRATE_NAME = "aya-test-crate" + with tempfile.TemporaryDirectory() as tmp_dir: + cmds: list[tuple[list[str], SubprocessArgs]] = [ + ( + [ + "cargo", + "generate", + "--path", + args.template_dir, + "-n", + CRATE_NAME, + "-d", + f"program_type={args.program_type}", + ] + + additional_args, + {"cwd": tmp_dir}, + ), + ] + project_dir = os.path.join(tmp_dir, CRATE_NAME) + match platform.system(): + case "Linux": + cmds.extend( + (cmd, {"cwd": project_dir}) + for cmd in ( + ["cargo", "+nightly", "fmt", "--all", "--", "--check"], + ["cargo", "build", "--package", CRATE_NAME], + ["cargo", "build", "--package", CRATE_NAME, "--release"], + # We cannot run clippy over the whole workspace at once due to feature unification. + # Since both ${CRATE_NAME} and ${CRATE_NAME}-ebpf depend on ${CRATE_NAME}-common and + # ${CRATE_NAME} activates ${CRATE_NAME}-common's aya dependency, we end up trying to + # compile the panic handler twice: once from the bpf program, and again from std via + # aya. + [ + "cargo", + "clippy", + "--exclude", + f"{CRATE_NAME}-ebpf", + "--all-targets", + "--workspace", + "--", + "--deny", + "warnings", + ], + [ + "cargo", + "clippy", + "--package", + f"{CRATE_NAME}-ebpf", + "--all-targets", + "--", + "--deny", + "warnings", + ], + ) + ) + case "Darwin": + arch = platform.machine() + if arch == "arm64": + arch = "aarch64" + target = f"{arch}-unknown-linux-musl" + cmds.append( + ( + [ + "cargo", + "build", + "--package", + CRATE_NAME, + "--release", + "--target", + target, + "--config", + f'target.{target}.linker = "rust-lld"', + ], + { + "cwd": project_dir, + "env": os.environ + | { + "AYA_BUILD_EBPF": "true", + "CC": f"{arch}-linux-musl-gcc", + }, + }, + ) + ) + + for cmd, kwargs in cmds: + print(f"Running command: {' '.join(cmd)} with kwargs: {kwargs}") + subprocess.check_call(cmd, **kwargs) + + if platform.system() == "Linux": + with subprocess.Popen( + ["cargo", "xtask", "run"], + cwd=project_dir, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=sys.stderr, + ) as process: + assert process.stdin is not None + assert process.stdout is not None + for line in io.TextIOWrapper(process.stdout, encoding="utf-8"): + sys.stdout.write(line) + if "Waiting for Ctrl-C" in line: + process.stdin.write(b"\x03") + retcode = process.returncode + if retcode != 0: + raise subprocess.CalledProcessError(retcode, process.args) + + +if __name__ == "__main__": + main() diff --git a/test.sh b/test.sh deleted file mode 100755 index 0d39c59..0000000 --- a/test.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -TEMPLATE_DIR=$1 -if [ -z "${TEMPLATE_DIR}" ]; then echo "template dir required"; exit 1; fi -PROG_TYPE=$2 -if [ -z "${PROG_TYPE}" ]; then echo "program type required"; exit 1; fi -CRATE_NAME=aya-test-crate - -case "${PROG_TYPE}" in - "cgroup_sockopt") - ADDITIONAL_ARGS=(-d sockopt_target=getsockopt) - ;; - "classifier"|"cgroup_skb") - ADDITIONAL_ARGS=(-d direction=Ingress) - ;; - "fentry"|"fexit") - ADDITIONAL_ARGS=(-d fn_name=try_to_wake_up) - ;; - "kprobe"|"kretprobe") - ADDITIONAL_ARGS=(-d kprobe=do_unlinkat) - ;; - "lsm") - ADDITIONAL_ARGS=(-d lsm_hook=file_open) - ;; - "raw_tracepoint") - ADDITIONAL_ARGS=(-d tracepoint_name=sys_enter) - ;; - "sk_msg") - ADDITIONAL_ARGS=(-d sock_map=SOCK_MAP) - ;; - "tp_btf") - ADDITIONAL_ARGS=(-d tracepoint_name=net_dev_queue) - ;; - "tracepoint") - ADDITIONAL_ARGS=(-d tracepoint_category=net -d tracepoint_name=net_dev_queue) - ;; - "uprobe"|"uretprobe") - ADDITIONAL_ARGS=(-d uprobe_target=/proc/self/exe -d uprobe_fn_name=main) - ;; - *) - ADDITIONAL_ARGS=() -esac - -TMP_DIR=$(mktemp -d) -clean_up() { - # shellcheck disable=SC2317 - rm -rf "${TMP_DIR}" -} -trap clean_up EXIT - -pushd "${TMP_DIR}" -cargo generate --path "${TEMPLATE_DIR}" -n "${CRATE_NAME}" -d program_type="${PROG_TYPE}" "${ADDITIONAL_ARGS[@]}" -pushd "${CRATE_NAME}" - -cargo +nightly fmt --all -- --check -cargo build --package "${CRATE_NAME}" -cargo build --package "${CRATE_NAME}" --release -# We cannot run clippy over the whole workspace at once due to feature unification. Since both -# ${CRATE_NAME} and ${CRATE_NAME}-ebpf depend on ${CRATE_NAME}-common and ${CRATE_NAME} activates -# ${CRATE_NAME}-common's aya dependency, we end up trying to compile the panic handler twice: once -# from the bpf program, and again from std via aya. -cargo clippy --exclude "${CRATE_NAME}-ebpf" --all-targets --workspace -- --deny warnings -cargo clippy --package "${CRATE_NAME}-ebpf" --all-targets -- --deny warnings - -expect << EOF - set timeout 30 ;# Increase timeout if necessary - spawn cargo xtask run - expect { - -re "Waiting for Ctrl-C.*" { - send -- \003 ;# Send Ctrl-C - } - timeout { - puts "Error: Timed out waiting for 'Waiting for Ctrl-C...'" - exit 1 - } - eof { - puts "Error: Process exited prematurely" - exit 1 - } - } - - expect { - -re "Exiting.*" { } - eof { } - } -EOF