diff --git a/.github/Dockerfile-rust-musl b/.github/Dockerfile-rust-musl new file mode 100644 index 0000000..fde72cd --- /dev/null +++ b/.github/Dockerfile-rust-musl @@ -0,0 +1,34 @@ +# Checks that the rust-musl image links a C dependency for both musl targets. +# aws-lc-sys compiled against glibc >= 2.38 headers emits __isoc23_* symbols +# that musl lacks; calling into it keeps the linker from dropping its archive. +# CI passes the freshly built rust-musl image so the check tests this PR's image. +ARG RUST_MUSL_IMAGE=ghcr.io/linkerd/dev:ci-rust-musl +FROM ${RUST_MUSL_IMAGE} +# aws-lc-rs pulls in aws-lc-sys, a C library sensitive to which libc headers it sees. +ARG AWS_LC_RS_VERSION=1.18.2 # repo=aws/aws-lc-rs,prefix=v +WORKDIR /musl-link +RUN cargo init --quiet --name musl-link && \ + cargo add --quiet "aws-lc-rs@=${AWS_LC_RS_VERSION}" +# Calls into aws-lc so the linker must resolve its symbols; an unused dependency would be dropped. +COPY <&2 ; \ + exit 1 ; \ + fi ; \ + done diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 078d7fd..08c2f45 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -4,7 +4,9 @@ on: pull_request: paths: - .devcontainer/** + - .github/Dockerfile-rust-musl - .github/workflows/devcontainer.yml + - bin/just-cargo - Dockerfile - justfile @@ -19,6 +21,16 @@ jobs: - uses: ./actions/setup-tools - run: just build + # Builds the rust-musl image, then links a C dependency with it for both musl + # targets (see .github/Dockerfile-rust-musl). + dev-musl-link: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + - uses: ./actions/setup-tools + - run: just load=true version=ci targets=rust-musl build + - run: docker build --build-arg RUST_MUSL_IMAGE=ghcr.io/linkerd/dev:ci-rust-musl -f .github/Dockerfile-rust-musl .github + dev-shellcheck: runs-on: ubuntu-latest steps: diff --git a/Dockerfile b/Dockerfile index a96d5b9..c4e1154 100644 --- a/Dockerfile +++ b/Dockerfile @@ -347,6 +347,40 @@ ENV CARGO_INCREMENTAL=0 \ RUSTUP_MAX_RETRIES=10 ENTRYPOINT ["/usr/local/bin/just-cargo"] +# Builds musl C headers that match the musl release this Rust toolchain links. +# +# Why: musl targets compile C (e.g. `aws-lc-sys`) with clang, which otherwise +# uses the host glibc headers. Since glibc 2.38 those redirect `sscanf`/`strtol` +# to `__isoc23_*` symbols that musl lacks, so links fail. Debian's `musl-dev` +# doesn't help: clang ignores its include dir and its release can differ from +# the musl Rust links (trixie 1.2.5 vs Rust 1.90's 1.2.3). +# +# Caveats: +# - The version is read with `strings` from the toolchain's `libc.a`; if musl +# stops embedding it, `grep` finds no match and the build fails before the fetch. +# - Headers are fetched from musl.libc.org at build time, like other tools here. +# - Both musl targets are assumed to ship the same musl release. +FROM rust as rust-musl-patch +# Only needed to read the bundled musl version; both targets ship the same release. +RUN rustup target add x86_64-unknown-linux-musl +# musl embeds its release string (e.g. 1.2.3) in libc.a; there is no rustc flag for it. +# Saved to /musl/VERSION for the steps below and for CI to check against libc.a. +# grep exits non-zero if no version is found, so a format change fails here. +RUN musl_target_libdir=$(rustc --print target-libdir --target x86_64-unknown-linux-musl) && \ + mkdir -p /musl && \ + strings "$musl_target_libdir/self-contained/libc.a" | grep -m1 -xE '[0-9]+\.[0-9]+\.[0-9]+' > /musl/VERSION +# Fetch the musl source for that release. +RUN musl_version=$(cat /musl/VERSION) && \ + scurl -o /musl.tar.gz "https://musl.libc.org/releases/musl-${musl_version}.tar.gz" +RUN mkdir -p /src && \ + tar xzf /musl.tar.gz --strip-components=1 -C /src +# Installs only headers (copy plus sed-generated bits/*.h); nothing is compiled. +RUN make -C /src ARCH=x86_64 prefix=/musl/x86_64 install-headers +# bits/*.h are generated per arch under obj/; clear x86_64's so aarch64 regenerates its own. +RUN rm -rf /src/obj +# Installs the aarch64 headers. +RUN make -C /src ARCH=aarch64 prefix=/musl/aarch64 install-headers + FROM rust as rust-musl RUN rustup target add \ aarch64-unknown-linux-musl \ @@ -359,6 +393,11 @@ RUN --mount=type=cache,from=apt-base,source=/etc/apt,target=/etc/apt,sharing=loc g++-aarch64-linux-gnu \ gcc-aarch64-linux-gnu \ libc6-dev-arm64-cross +COPY --link --from=rust-musl-patch /musl /usr/local/musl +# cc-rs reads these per-target flags; `-isystem` puts musl's headers ahead of glibc's (see rust-musl-patch). +ENV CFLAGS_x86_64_unknown_linux_musl="-isystem /usr/local/musl/x86_64/include" +# Same for aarch64, with the aarch64 musl headers. +ENV CFLAGS_aarch64_unknown_linux_musl="-isystem /usr/local/musl/aarch64/include" ## ## Devcontainer