Skip to content

Commit

Permalink
Add cross-compilation to CI
Browse files Browse the repository at this point in the history
Updates the README to use cargo target config instead of RUSTFLAGS to
avoid setting the linker for ebpf in cargo-in-cargo.

Rewrite test.sh in Go since we need careful signal handling that is
harder to do with e.g. python.
  • Loading branch information
tamird committed Oct 18, 2024
1 parent 20ce988 commit 897520f
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 117 deletions.
82 changes: 58 additions & 24 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,42 @@ concurrency:

jobs:
build:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
runner:
- ubuntu-latest # x86
rust:
- stable
- 1.80.1
program:
- kprobe
- kretprobe
- fentry
- fexit
- uprobe
- uretprobe
- sock_ops
- socket_filter
- sk_msg
- xdp
- cgroup_skb
- cgroup_sockopt
- cgroup_sysctl
- classifier
- lsm
- perf_event
- raw_tracepoint
- tp_btf
- tracepoint
# - kretprobe
# - fentry
# - fexit
# - uprobe
# - uretprobe
# - sock_ops
# - socket_filter
# - sk_msg
# - xdp
# - cgroup_skb
# - cgroup_sockopt
# - cgroup_sysctl
# - classifier
# - lsm
# - perf_event
# - raw_tracepoint
# - tp_btf
# - tracepoint
include:
- runner: macos-13 # x86
rust: 1.80.1
program: kprobe
- runner: macos-14 # arm64
rust: 1.80.1
program: kprobe

runs-on: ${{ matrix.runner }}

steps:
- uses: actions/checkout@v4
Expand All @@ -57,6 +66,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
Expand All @@ -65,8 +89,18 @@ 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: sudo apt update
- run: sudo apt install expect
- run: ./test.sh ${{ github.workspace }} ${{ matrix.program }}
- 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'

- uses: actions/setup-go@v5
with:
go-version: stable
- run: go run main.go ${{ github.workspace }} ${{ matrix.program }}
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion cargo-generate.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[template]
cargo_generate_version = ">=0.10.0"
ignore = [".github", "test.sh"]
ignore = [".github", "test.py"]

[placeholders.program_type]
type = "string"
Expand Down
209 changes: 209 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package main

import (
"bufio"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
)

type Command struct {
Cmd []string
Cwd string
Env []string
}

func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
}

func run() error {
if len(os.Args) != 3 {
return fmt.Errorf("Usage: %s <template_dir> <program_type>\n", os.Args[0])
}

templateDir := os.Args[1]
programType := os.Args[2]

additionalArgs := []string{}
switch programType {
case "cgroup_sockopt":
additionalArgs = append(additionalArgs, "-d", "sockopt_target=getsockopt")
case "classifier", "cgroup_skb":
additionalArgs = append(additionalArgs, "-d", "direction=Ingress")
case "fentry", "fexit":
additionalArgs = append(additionalArgs, "-d", "fn_name=try_to_wake_up")
case "kprobe", "kretprobe":
additionalArgs = append(additionalArgs, "-d", "kprobe=do_unlinkat")
case "lsm":
additionalArgs = append(additionalArgs, "-d", "lsm_hook=file_open")
case "raw_tracepoint":
additionalArgs = append(additionalArgs, "-d", "tracepoint_name=sys_enter")
case "sk_msg":
additionalArgs = append(additionalArgs, "-d", "sock_map=SOCK_MAP")
case "tp_btf":
additionalArgs = append(additionalArgs, "-d", "tracepoint_name=net_dev_queue")
case "tracepoint":
additionalArgs = append(additionalArgs,
"-d", "tracepoint_category=net",
"-d", "tracepoint_name=net_dev_queue",
)
case "uprobe", "uretprobe":
additionalArgs = append(additionalArgs,
"-d", "uprobe_target=/proc/self/exe",
"-d", "uprobe_fn_name=main",
)
}

tmpDir, err := os.MkdirTemp("", "")
if err != nil {
return fmt.Errorf("os.MkdirTemp(): %w", err)
}
defer os.RemoveAll(tmpDir)

const CRATE_NAME = "aya-test-crate"
cmds := []Command{
{
Cmd: append([]string{
"cargo",
"generate",
"--path",
templateDir,
"-n",
CRATE_NAME,
"-d",
fmt.Sprintf("program_type=%s", programType),
}, additionalArgs...),
Cwd: tmpDir,
},
}

projectDir := filepath.Join(tmpDir, CRATE_NAME)

if runtime.GOOS == "linux" {
ebpf_crate_name := fmt.Sprintf("%s-ebpf", CRATE_NAME)
linuxCmds := [][]string{
{"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", ebpf_crate_name,
"--all-targets",
"--workspace",
"--",
"--deny", "warnings",
},
{
"cargo", "clippy",
"--package", ebpf_crate_name,
"--all-targets",
"--",
"--deny", "warnings",
},
}
for _, cmd := range linuxCmds {
cmds = append(cmds, Command{
Cmd: cmd,
Cwd: projectDir,
})
}
} else if runtime.GOOS == "darwin" {
arch := runtime.GOARCH
switch arch {
case "amd64":
arch = "x86_64"
case "arm64":
arch = "aarch64"
}
target := fmt.Sprintf("%s-unknown-linux-musl", arch)
cmds = append(cmds, Command{
Cmd: []string{
"cargo",
"build",
"--package",
CRATE_NAME,
"--release",
"--target",
target,
"--config",
fmt.Sprintf("target.%s.linker = \"rust-lld\"", target),
},
Cwd: projectDir,
Env: append([]string{
"AYA_BUILD_EBPF=true",
fmt.Sprintf("CC=%s-linux-musl-gcc", arch),
}, os.Environ()...),
})
}

for _, cmdSpec := range cmds {
cmd := exec.Command(cmdSpec.Cmd[0], cmdSpec.Cmd[1:]...)
cmd.Dir = cmdSpec.Cwd
cmd.Env = cmdSpec.Env
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("%+v\n", cmdSpec)
if err := cmd.Run(); err != nil {
return fmt.Errorf("%+v failed: %w", cmdSpec, err)
}
}

if runtime.GOOS == "linux" {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

cmd := exec.CommandContext(ctx, "cargo", "xtask", "run")
cmd.Dir = projectDir
cmd.Stderr = os.Stderr
// Prevent the child process from being in our process group so that we can send it a SIGINT
// without sending one to ourselves.
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}

stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("Failed to get stdout pipe: %w", err)
}

fmt.Printf("Running %s in %s\n", cmd, cmd.Dir)
if err := cmd.Start(); err != nil {
return fmt.Errorf("%s failed to start: %w", cmd, err)
}

scanner := bufio.NewScanner(stdoutPipe)
for scanner.Scan() {
text := scanner.Text()
if _, err := fmt.Fprintln(os.Stdout, text); err != nil {
panic(err)
}
if strings.Contains(text, "Waiting for Ctrl-C") {
syscall.Kill(-cmd.Process.Pid, syscall.SIGINT)
}
}
if err := scanner.Err(); err != nil {
panic(err)
}

if err := cmd.Wait(); err != nil {
return fmt.Errorf("%s failed: %w", cmd, err)
}
}

return nil
}
Loading

0 comments on commit 897520f

Please sign in to comment.