From 396b0f614e226e7568a10092f779d8e395d3da42 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 00:05:33 +0200 Subject: [PATCH 01/45] Migrate Linux Docker builds to Azure Linux 3.0 + sysroot architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace Debian 11/13 and Alpine host containers with Azure Linux 3.0 base images using a host/sysroot-decoupled architecture. This eliminates ~100 Component Governance (CG) security alerts from stale OS packages while maintaining backward compatibility (glibc 2.31 for most targets). Key changes: - New azurelinux/glibc image: debootstrap creates Debian sysroots (Bullseye for glibc 2.31, Trixie for loongarch64's glibc 2.38) - New azurelinux/alpine image: apk.static with Alpine 3.21 (was 3.17 EOL) - New azurelinux/bionic image: Azure Linux host + NDK r26b (unchanged) - build.cake reads SYSROOT_PATH env var for cross-compilation paths - Trimmed Alpine packages: musl-dev+linux-headers+fontconfig-dev replaces build-base (eliminates busybox/binutils CVEs) - All images use --foreign debootstrap (no second-stage needed for sysroots) Architecture (same as .NET runtime): Azure Linux 3.0 host → clang-18 → --sysroot=/sysroot → libSkiaSharp.so Security impact: - Alpine 3.17 EOL alerts: ~94 → 0 (bumped to 3.21) - Debian host package alerts: 9 → 0 (Azure Linux replaces Debian) - build-base CVEs: ~50 → 0 (trimmed unnecessary packages) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .agents/skills/issue-fix/SKILL.md | 4 +- documentation/dev/building-linux.md | 6 +- documentation/dev/debugging-methodology.md | 2 +- native/linux-clang-cross/build.cake | 4 +- .../azure-templates-stages-native-linux.yml | 19 +- .../linux/docker/_clang-cross-common.sh | 54 ----- .../native/linux/docker/alpine/.dockerignore | 1 - .../native/linux/docker/alpine/build-local.sh | 11 - .../docker/{ => azurelinux}/alpine/Dockerfile | 110 +++++----- .../docker/azurelinux/alpine/build-local.sh | 32 +++ .../docker/{ => azurelinux}/alpine/startup.sh | 0 .../docker/{ => azurelinux}/bionic/Dockerfile | 60 +++--- .../{ => azurelinux}/bionic/build-local.sh | 7 +- .../docker/{ => azurelinux}/bionic/startup.sh | 0 .../linux/docker/azurelinux/glibc/Dockerfile | 191 ++++++++++++++++++ .../docker/azurelinux/glibc/build-local.sh | 46 +++++ .../11 => azurelinux/glibc}/startup.sh | 0 .../native/linux/docker/debian/11/Dockerfile | 137 ------------- .../linux/docker/debian/11/build-local.sh | 14 -- .../linux/docker/debian/13/.dockerignore | 1 - .../native/linux/docker/debian/13/Dockerfile | 111 ---------- .../linux/docker/debian/13/build-local.sh | 14 -- .../native/linux/docker/debian/13/startup.sh | 6 - 23 files changed, 386 insertions(+), 444 deletions(-) delete mode 100755 scripts/infra/native/linux/docker/_clang-cross-common.sh delete mode 100644 scripts/infra/native/linux/docker/alpine/.dockerignore delete mode 100755 scripts/infra/native/linux/docker/alpine/build-local.sh rename scripts/infra/native/linux/docker/{ => azurelinux}/alpine/Dockerfile (54%) create mode 100755 scripts/infra/native/linux/docker/azurelinux/alpine/build-local.sh rename scripts/infra/native/linux/docker/{ => azurelinux}/alpine/startup.sh (100%) mode change 100644 => 100755 rename scripts/infra/native/linux/docker/{ => azurelinux}/bionic/Dockerfile (59%) rename scripts/infra/native/linux/docker/{ => azurelinux}/bionic/build-local.sh (88%) mode change 100644 => 100755 rename scripts/infra/native/linux/docker/{ => azurelinux}/bionic/startup.sh (100%) mode change 100644 => 100755 create mode 100644 scripts/infra/native/linux/docker/azurelinux/glibc/Dockerfile create mode 100755 scripts/infra/native/linux/docker/azurelinux/glibc/build-local.sh rename scripts/infra/native/linux/docker/{debian/11 => azurelinux/glibc}/startup.sh (100%) delete mode 100644 scripts/infra/native/linux/docker/debian/11/Dockerfile delete mode 100755 scripts/infra/native/linux/docker/debian/11/build-local.sh delete mode 100644 scripts/infra/native/linux/docker/debian/13/.dockerignore delete mode 100644 scripts/infra/native/linux/docker/debian/13/Dockerfile delete mode 100644 scripts/infra/native/linux/docker/debian/13/build-local.sh delete mode 100644 scripts/infra/native/linux/docker/debian/13/startup.sh diff --git a/.agents/skills/issue-fix/SKILL.md b/.agents/skills/issue-fix/SKILL.md index 0f8ad233226..700fce1e952 100644 --- a/.agents/skills/issue-fix/SKILL.md +++ b/.agents/skills/issue-fix/SKILL.md @@ -282,10 +282,10 @@ When a bug affects one platform but not another, **build both platforms locally* ```bash # Build x64 native (in Docker) -bash ./scripts/infra/native/linux/docker/debian/11/build-local.sh +bash ./scripts/infra/native/linux/docker/azurelinux/glibc/build-local.sh x64 # Build ARM64 cross-compile (in Docker) -bash ./scripts/infra/native/linux/docker/debian/11/build-local.sh arm64 10 +bash ./scripts/infra/native/linux/docker/azurelinux/glibc/build-local.sh arm64 # Compare DT_NEEDED entries (the linked libraries) docker run --rm -v $(pwd):/work debian:bookworm-slim bash -c \ diff --git a/documentation/dev/building-linux.md b/documentation/dev/building-linux.md index ec0b3f0ed56..7260fcee7d4 100644 --- a/documentation/dev/building-linux.md +++ b/documentation/dev/building-linux.md @@ -82,12 +82,12 @@ Building for different Linux distributions is easier with Docker. The repository ```bash # Build the Docker image -cd scripts/infra/native/linux/docker/alpine/amd64 -docker build --tag skiasharp-alpine . +cd scripts/infra/native/linux/docker/azurelinux/alpine +docker build --tag skiasharp-alpine --build-arg BUILD_ARCH=x64 . # Run the build docker run --rm --name skiasharp-alpine --volume $(pwd):/work skiasharp-alpine \ - /bin/bash ./bootstrapper.sh -t externals-linux --variant=alpine --buildarch=x64 + /bin/bash -c "dotnet tool restore && dotnet cake --target=externals-linux-clang-cross --buildarch=x64 --variant=alpine" # Fix file ownership if needed chown -R $(id -u):$(id -g) . diff --git a/documentation/dev/debugging-methodology.md b/documentation/dev/debugging-methodology.md index f04ba48a789..d0eadb1149c 100644 --- a/documentation/dev/debugging-methodology.md +++ b/documentation/dev/debugging-methodology.md @@ -199,7 +199,7 @@ but the actual `.so.1.2.3` file is in the runtime package (`libfoo1`), not the d | Root Cause | Fix Location | |------------|--------------| | Library missing from linker flags | `native/linux/build.cake` or `externals/skia/third_party/BUILD.gn` | -| Library missing from cross-compile sysroot | `scripts/infra/native/linux/docker/debian/*/Dockerfile` | +| Library missing from cross-compile sysroot | `scripts/infra/native/linux/docker/azurelinux/glibc/Dockerfile` | | Indirect dependency (A→B→C missing) | Fix B's linkage or add C explicitly | ### Real Example: ARM64 fontconfig issue (#3369) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index 5f614d7145d..274bb85bc84 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -5,10 +5,12 @@ DirectoryPath ROOT_PATH = MakeAbsolute(Directory("../..")); string TOOLCHAIN_ARCH = Argument("toolchainArch", EnvironmentVariable("TOOLCHAIN_ARCH")); string TOOLCHAIN_ARCH_SHORT = Argument("toolchainArchShort", EnvironmentVariable("TOOLCHAIN_ARCH_SHORT")); string TOOLCHAIN_ARCH_TARGET = Argument("toolchainArchTarget", EnvironmentVariable("TOOLCHAIN_ARCH_TARGET")); +string SYSROOT_PATH = Argument("sysrootPath", EnvironmentVariable("SYSROOT_PATH")); Information("Toolchain:"); Information($" Arch: {TOOLCHAIN_ARCH} ({TOOLCHAIN_ARCH_SHORT})"); Information($" Target {TOOLCHAIN_ARCH_TARGET}"); +Information($" Sysroot: {SYSROOT_PATH}"); if (BUILD_ARCH.Length == 0) BUILD_ARCH = new [] { "arm" }; @@ -21,7 +23,7 @@ string GetGnArgs(string arch) _ => ("", ""), }; - var sysroot = $"/usr/{TOOLCHAIN_ARCH}"; + var sysroot = SYSROOT_PATH; var init = $"{sysrootArg} '--target={TOOLCHAIN_ARCH_TARGET}'"; var bin = $"'-B{sysroot}/bin/' "; var libs = $"'-L{sysroot}/lib/' "; diff --git a/scripts/azure-templates-stages-native-linux.yml b/scripts/azure-templates-stages-native-linux.yml index ce97932a617..f916d873909 100644 --- a/scripts/azure-templates-stages-native-linux.yml +++ b/scripts/azure-templates-stages-native-linux.yml @@ -27,30 +27,27 @@ stages: additionalArgs: --verifyExcluded=fontconfig gnArgs: skia_use_fontconfig=false matrix: - # Debian 13 / Trixie (gcc-14, glibc 2.38) - for archs that + # Azure Linux + Trixie sysroot (gcc-14, glibc 2.38) - for archs that # never had release-arch packages in Bullseye OR Bookworm: # - loongarch64 (LoongArch port; not in Bullseye/Bookworm main, # first release arch in Trixie) - ${{ each arch in split('loongarch64', ',') }}: - arch: ${{ arch }} - docker: scripts/infra/native/linux/docker/debian/13 + docker: scripts/infra/native/linux/docker/azurelinux/glibc target: externals-linux-clang-cross additionalArgs: --verifyGlibcMax=2.38 - # Debian 11 / Bullseye (gcc-10, glibc 2.31). riscv64 is a - # carve-out: Bullseye has no riscv64 binary archive but ships - # gcc-10-cross packages that contain a riscv64 sysroot at - # glibc 2.31. The image installs those instead of LLVM - # multiarch packages and the build links libstdc++/libgcc - # statically - keeps glibc parity with the other archs. + dockerArgs: --build-arg SYSROOT_RELEASE=trixie + # Azure Linux + Bullseye sysroot (gcc-10, glibc 2.31). - ${{ each arch in split('arm,arm64,x86,x64,riscv64', ',') }}: - arch: ${{ arch }} - docker: scripts/infra/native/linux/docker/debian/11 + docker: scripts/infra/native/linux/docker/azurelinux/glibc target: externals-linux-clang-cross additionalArgs: --verifyGlibcMax=2.31 + dockerArgs: --build-arg SYSROOT_RELEASE=bullseye - ${{ each arch in split('arm,arm64,x64,riscv64,loongarch64', ',') }}: # removed x86 as it is having issues building - arch: ${{ arch }} variant: alpine - docker: scripts/infra/native/linux/docker/alpine + docker: scripts/infra/native/linux/docker/azurelinux/alpine target: externals-linux-clang-cross - template: /scripts/azure-templates-jobs-linux-matrix.yml@self # Build Native Linux (Bionic) @@ -65,5 +62,5 @@ stages: - ${{ each arch in split('arm64,x64', ',') }}: - arch: ${{ arch }} variant: bionic - docker: scripts/infra/native/linux/docker/bionic + docker: scripts/infra/native/linux/docker/azurelinux/bionic diff --git a/scripts/infra/native/linux/docker/_clang-cross-common.sh b/scripts/infra/native/linux/docker/_clang-cross-common.sh deleted file mode 100755 index d96261b6191..00000000000 --- a/scripts/infra/native/linux/docker/_clang-cross-common.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -set -ex - -# Parameters: -# $1 - The directory containing the Dockerfile [ clang-cross/10 | clang-cross ] -# $2 - The target architecture to build for [ arm | arm64 | riscv64 | x86 | x64 | loongarch64 ] -# $3 - The ABI [ gnu | musl ] -# $4 - The variant [ "" | alpine ] -# $5 - Extra arguments to pass to dotnet cake - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -# the directory containing the Dockerfile -DOCKER_DIR="$1" - -# the target architecture to build for -ARCH="$2" - -# the docker platform to use -MACHINE_ARCH="$(uname -m)" -case $MACHINE_ARCH in - arm64) PLATFORM=linux/arm64 ; MACHINE_ARCH=aarch64 ;; - *) PLATFORM=linux/amd64 ;; -esac - -# the ABI -ABI=$3 - -# the variant -VARIANT=$4 - -# extra arguments passed through -EXTRA_ARGS=$5 - -# GLIBC verification based on Docker image (matching CI configuration) -ADDITIONAL_ARGS="" -case "$DOCKER_DIR" in - *debian/11*) ADDITIONAL_ARGS="--verifyGlibcMax=2.31" ;; - *debian/13*) ADDITIONAL_ARGS="--verifyGlibcMax=2.38" ;; -esac - -(cd $DIR && - docker build --tag skiasharp-linux-$ABI-cross-$ARCH \ - --platform=$PLATFORM \ - --build-arg BUILD_ARCH=$ARCH \ - --build-arg MACHINE_ARCH=$MACHINE_ARCH \ - $DOCKER_DIR) - -[ -n "$VARIANT" ] && VARIANT="--variant=$VARIANT" - -(cd $DIR/../../../../.. && - docker run --rm --name skiasharp-linux-$ABI-cross-$ARCH --volume $(pwd):/work skiasharp-linux-$ABI-cross-$ARCH /bin/bash -c " \ - dotnet tool restore ; \ - dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=$ARCH $VARIANT $ADDITIONAL_ARGS $EXTRA_ARGS ") diff --git a/scripts/infra/native/linux/docker/alpine/.dockerignore b/scripts/infra/native/linux/docker/alpine/.dockerignore deleted file mode 100644 index 3a09fc21d8d..00000000000 --- a/scripts/infra/native/linux/docker/alpine/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -build-local.sh diff --git a/scripts/infra/native/linux/docker/alpine/build-local.sh b/scripts/infra/native/linux/docker/alpine/build-local.sh deleted file mode 100755 index 11fb297eb3f..00000000000 --- a/scripts/infra/native/linux/docker/alpine/build-local.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -set -ex - -# Parameters: -# $1 - The target architecture to build for [ arm | arm64 | riscv64 | x86 | x64 | loongarch64 ] - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -ARCH="${1:-arm}" - -$DIR/../_clang-cross-common.sh "$DIR" "$ARCH" "musl" "alpine" diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/azurelinux/alpine/Dockerfile similarity index 54% rename from scripts/infra/native/linux/docker/alpine/Dockerfile rename to scripts/infra/native/linux/docker/azurelinux/alpine/Dockerfile index 7ae10de1000..c71e1f97a27 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/azurelinux/alpine/Dockerfile @@ -1,52 +1,71 @@ -# Arguments: -# DOTNET_SDK_VERSION - the version of dotnet for the Cake script [ 10.0 | * ] -# LLVM_VERSION - the version of the LLVM compiler [ 19 | * ] -# TOOLCHAIN_ARCH - the architecture of the GCC toolchain [ arm-alpine-linux-musleabihf | aarch64-alpine-linux-musl | riscv64-alpine-linux-musl | loongarch64-alpine-linux-musl ] +# musl (Alpine) cross-compilation image. +# +# Uses apk.static to create an Alpine sysroot, same concept as glibc image. +# Host is Azure Linux 3.0; sysroot is Alpine 3.21 for musl target. +# +# musl is forward-compatible: binaries built against 3.21 run on 3.22+. +# musl is NOT backward-compatible: cannot target 3.17 from 3.21 headers. +# +# Alpine 3.21 (musl 1.2.5) is the minimum supported — 3.17-3.20 are all EOL. +# .NET 10 officially supports only Alpine 3.21+. -# Use --platform flag when building to select architecture -FROM mcr.microsoft.com/mirror/docker/library/debian:12 +FROM mcr.microsoft.com/azurelinux/base/core:3.0 + +# Install host build tools from Azure Linux packages. +RUN tdnf install -y \ + clang \ + lld \ + llvm \ + compiler-rt \ + ninja-build \ + python3 \ + git \ + curl \ + tar \ + xz \ + && tdnf clean all + +# Install the .NET SDK (needed for Cake build scripts) +ARG DOTNET_SDK_VERSION=10.0.1xx +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +RUN curl https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -L -o dotnet-install.sh \ + && bash dotnet-install.sh --channel ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ + && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ + && rm dotnet-install.sh \ + && dotnet help \ + && dotnet --info + +ENV CC=clang CXX=clang++ -# Set the architecture-specific variables based on the value of the BUILD_ARCH argument ARG BUILD_ARCH=arm64 + +# Map BUILD_ARCH to Alpine triple and apk architecture RUN case ${BUILD_ARCH} in \ - arm) TOOLCHAIN_ARCH=armv7-alpine-linux-musleabihf ; TOOLCHAIN_ARCH_TARGET=armv7-alpine-linux-musleabihf ;; \ - arm64) TOOLCHAIN_ARCH=aarch64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=aarch64-alpine-linux-musl ;; \ - loongarch64) TOOLCHAIN_ARCH=loongarch64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=loongarch64-alpine-linux-musl ;; \ - riscv64) TOOLCHAIN_ARCH=riscv64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=riscv64-alpine-linux-musl ;; \ - x86) TOOLCHAIN_ARCH=i586-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=i586-alpine-linux-musl ;; \ - x64) TOOLCHAIN_ARCH=x86_64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=x86_64-alpine-linux-musl ;; \ + arm) TOOLCHAIN_ARCH=armv7-alpine-linux-musleabihf ; TOOLCHAIN_ARCH_TARGET=armv7-alpine-linux-musleabihf ; APK_ARCH=armv7 ;; \ + arm64) TOOLCHAIN_ARCH=aarch64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=aarch64-alpine-linux-musl ; APK_ARCH=aarch64 ;; \ + loongarch64) TOOLCHAIN_ARCH=loongarch64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=loongarch64-alpine-linux-musl ; APK_ARCH=loongarch64 ;; \ + riscv64) TOOLCHAIN_ARCH=riscv64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=riscv64-alpine-linux-musl ; APK_ARCH=riscv64 ;; \ + x86) TOOLCHAIN_ARCH=i586-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=i586-alpine-linux-musl ; APK_ARCH=x86 ;; \ + x64) TOOLCHAIN_ARCH=x86_64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=x86_64-alpine-linux-musl ; APK_ARCH=x86_64 ;; \ *) echo "Unsupported architecture: ${BUILD_ARCH}" && exit 1 ;; \ esac \ && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env + && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env \ + && echo "export SYSROOT_PATH=/alpine/usr" >> /etc/skia-env -# Install the required packages -ARG LLVM_VERSION=19 -RUN apt-get update \ - && apt-get install -y \ - curl python3 git clang-${LLVM_VERSION} lld-${LLVM_VERSION} ninja-build xz-utils \ - && rm -rf /var/lib/apt/lists/* - -# Install the cross-compilation musl toolchain -# First, obtain apk.static from https://gitlab.alpinelinux.org/alpine/apk-tools/-/releases/v2.12.14 +# Create Alpine sysroot using apk.static. +# Only install the minimal packages needed for cross-compilation: +# musl-dev: C library headers and runtime +# linux-headers: kernel headers +# fontconfig-dev: font configuration headers and library +# Avoids build-base which pulls in busybox (~35 CVEs) and binutils (~15 CVEs). +ARG DISTRO_VERSION=3.21 ARG MACHINE_ARCH=x86_64 RUN . /etc/skia-env \ - && case "${BUILD_ARCH}" in \ - arm) APK_ARCH=armv7 ;; \ - arm64) APK_ARCH=aarch64 ;; \ - loongarch64) APK_ARCH=loongarch64 ;; \ - riscv64) APK_ARCH=riscv64 ;; \ - x86) APK_ARCH=x86 ;; \ - x64) APK_ARCH=x86_64 ;; \ - *) echo "Unsupported architecture: ${BUILD_ARCH}" && exit 1 ;; \ - esac \ - && case "${BUILD_ARCH}" in \ - loongarch64) DISTRO_VERSION=3.21 ;; \ - riscv64) DISTRO_VERSION=3.20 ;; \ - *) DISTRO_VERSION=3.17 ;; \ - esac \ && APK_DIR="$(mktemp -d)" \ - && curl -SLO --fail --retry 5 --retry-delay 10 --retry-all-errors --create-dirs --output-dir "$APK_DIR" "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.12.14/$MACHINE_ARCH/apk.static" \ + && curl -SLO --fail --retry 5 --retry-delay 10 --retry-all-errors \ + --create-dirs --output-dir "$APK_DIR" \ + "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.12.14/$MACHINE_ARCH/apk.static" \ && chmod +x "$APK_DIR/apk.static" \ && "$APK_DIR/apk.static" \ -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ @@ -56,19 +75,8 @@ RUN . /etc/skia-env \ -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ -U --allow-untrusted --root /alpine --arch "$APK_ARCH" --no-scripts \ - add fontconfig-dev build-base linux-headers - -# Install the .NET SDK -ARG DOTNET_SDK_VERSION=10.0.1xx -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -RUN curl https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -L -o dotnet-install.sh \ - && bash dotnet-install.sh --channel ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ - && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ - && rm dotnet-install.sh \ - && dotnet help \ - && dotnet --info - -ENV CC=clang-${LLVM_VERSION} CXX=clang++-${LLVM_VERSION} + add musl-dev linux-headers fontconfig-dev \ + && rm -rf "$APK_DIR" WORKDIR /work diff --git a/scripts/infra/native/linux/docker/azurelinux/alpine/build-local.sh b/scripts/infra/native/linux/docker/azurelinux/alpine/build-local.sh new file mode 100755 index 00000000000..a3efb14bc41 --- /dev/null +++ b/scripts/infra/native/linux/docker/azurelinux/alpine/build-local.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -ex + +# Parameters: +# $1 - The target architecture to build for [ arm | arm64 | riscv64 | x64 | loongarch64 ] +# $2+ - Additional arguments to pass to the cake script + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +ARCH="${1:-arm64}" +shift 1 || true +EXTRA_ARGS="$@" + +# the docker platform to use +MACHINE_ARCH="$(uname -m)" +case $MACHINE_ARCH in + arm64) PLATFORM=linux/arm64 ; MACHINE_ARCH=aarch64 ;; + *) PLATFORM=linux/amd64 ;; +esac + +# Build the alpine image +(cd "$DIR" && + docker build --tag skiasharp-linux-musl-cross-$ARCH \ + --platform=$PLATFORM \ + --build-arg BUILD_ARCH=$ARCH \ + --build-arg MACHINE_ARCH=$MACHINE_ARCH \ + .) + +(cd "$DIR/../../../../../.." && + docker run --rm --name skiasharp-linux-musl-cross-$ARCH --volume $(pwd):/work skiasharp-linux-musl-cross-$ARCH /bin/bash -c " \ + dotnet tool restore ; \ + dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=$ARCH --variant=alpine $EXTRA_ARGS ") diff --git a/scripts/infra/native/linux/docker/alpine/startup.sh b/scripts/infra/native/linux/docker/azurelinux/alpine/startup.sh old mode 100644 new mode 100755 similarity index 100% rename from scripts/infra/native/linux/docker/alpine/startup.sh rename to scripts/infra/native/linux/docker/azurelinux/alpine/startup.sh diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/azurelinux/bionic/Dockerfile similarity index 59% rename from scripts/infra/native/linux/docker/bionic/Dockerfile rename to scripts/infra/native/linux/docker/azurelinux/bionic/Dockerfile index c0ae19777bb..93e08fdbe02 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/azurelinux/bionic/Dockerfile @@ -1,11 +1,39 @@ -# Arguments: -# DOTNET_SDK_VERSION - the version of dotnet for the Cake script [ 10.0 | * ] -# BUILD_ARCH - the target architecture to build for [ x64 | arm64 ] +# Android NDK cross-compilation image. +# +# The NDK provides its own Bionic sysroot and clang toolchain — no debootstrap needed. +# Host is Azure Linux 3.0 for consistent CG surface across all Linux build images. +# +# The NDK's own clang is used for compilation (not the host clang-18), since the +# NDK bundles Android-specific runtime support (compiler-rt, libc++, etc.). -FROM mcr.microsoft.com/mirror/docker/library/debian:12 +FROM mcr.microsoft.com/azurelinux/base/core:3.0 + +# Install host build tools from Azure Linux packages. +# Bionic doesn't need clang/lld from host — NDK provides its own. +# But we need python3, git, ninja for Skia's GN build system and unzip for NDK. +RUN tdnf install -y \ + ninja-build \ + python3 \ + git \ + curl \ + tar \ + xz \ + unzip \ + && tdnf clean all + +# Install the .NET SDK (needed for Cake build scripts) +ARG DOTNET_SDK_VERSION=10.0.1xx +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +RUN curl https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -L -o dotnet-install.sh \ + && bash dotnet-install.sh --channel ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ + && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ + && rm dotnet-install.sh \ + && dotnet help \ + && dotnet --info -# Set the architecture-specific variables based on the value of the BUILD_ARCH argument ARG BUILD_ARCH=x64 + +# Map BUILD_ARCH to Android triple and sysroot lib directory RUN case ${BUILD_ARCH} in \ x64) ANDROID_TRIPLE=x86_64-linux-android29 ; ANDROID_SYSROOT_LIB=x86_64-linux-android ;; \ arm64) ANDROID_TRIPLE=aarch64-linux-android29 ; ANDROID_SYSROOT_LIB=aarch64-linux-android ;; \ @@ -14,14 +42,10 @@ RUN case ${BUILD_ARCH} in \ && echo "export ANDROID_TRIPLE=${ANDROID_TRIPLE}" > /etc/skia-env \ && echo "export ANDROID_SYSROOT_LIB=${ANDROID_SYSROOT_LIB}" >> /etc/skia-env -# Install the required packages -RUN apt-get update \ - && apt-get install -y \ - binutils curl python3 git ninja-build unzip \ - && rm -rf /var/lib/apt/lists/* - -# Configure Android NDK r26b -RUN curl -SL https://dl.google.com/android/repository/android-ndk-r26b-linux.zip -o /tmp/ndk.zip \ +# Install Android NDK r26b +# tdnf provides unzip; curl is already in base image. +RUN tdnf install -y unzip && tdnf clean all \ + && curl -SL https://dl.google.com/android/repository/android-ndk-r26b-linux.zip -o /tmp/ndk.zip \ && unzip -q /tmp/ndk.zip -d /opt \ && rm /tmp/ndk.zip ENV ANDROID_NDK_HOME=/opt/android-ndk-r26b @@ -39,16 +63,6 @@ RUN . /etc/skia-env \ && echo "export AR=llvm-ar" >> /etc/skia-env \ && echo "export LD=lld" >> /etc/skia-env -# Install the .NET SDK -ARG DOTNET_SDK_VERSION=10.0 -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -RUN curl https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -L -o dotnet-install.sh \ - && bash dotnet-install.sh --channel ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ - && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ - && rm dotnet-install.sh \ - && dotnet help \ - && dotnet --info - WORKDIR /work COPY ./startup.sh / diff --git a/scripts/infra/native/linux/docker/bionic/build-local.sh b/scripts/infra/native/linux/docker/azurelinux/bionic/build-local.sh old mode 100644 new mode 100755 similarity index 88% rename from scripts/infra/native/linux/docker/bionic/build-local.sh rename to scripts/infra/native/linux/docker/azurelinux/bionic/build-local.sh index b0c0b6a4ed2..b0c60578768 --- a/scripts/infra/native/linux/docker/bionic/build-local.sh +++ b/scripts/infra/native/linux/docker/azurelinux/bionic/build-local.sh @@ -15,12 +15,13 @@ if [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then PLATFORM_ARGS="--platform linux/amd64" fi -(cd "$DIR" && +# Build the bionic image +(cd "$DIR" && docker build $PLATFORM_ARGS --tag "skiasharp-bionic-$ARCH" \ - --build-arg BUILD_ARCH=$ARCH \ + --build-arg BUILD_ARCH=$ARCH \ .) -(cd "$DIR/../../../../../.." && +(cd "$DIR/../../../../../.." && docker run --rm $PLATFORM_ARGS --name "skiasharp-bionic-$ARCH" --volume "$(pwd)":/work "skiasharp-bionic-$ARCH" /bin/bash -c " \ dotnet tool restore ; \ dotnet cake --target=externals-linux --configuration=Release --buildarch=$ARCH --variant=bionic --verifyExcluded=fontconfig ") diff --git a/scripts/infra/native/linux/docker/bionic/startup.sh b/scripts/infra/native/linux/docker/azurelinux/bionic/startup.sh old mode 100644 new mode 100755 similarity index 100% rename from scripts/infra/native/linux/docker/bionic/startup.sh rename to scripts/infra/native/linux/docker/azurelinux/bionic/startup.sh diff --git a/scripts/infra/native/linux/docker/azurelinux/glibc/Dockerfile b/scripts/infra/native/linux/docker/azurelinux/glibc/Dockerfile new file mode 100644 index 00000000000..dbd0ff23b66 --- /dev/null +++ b/scripts/infra/native/linux/docker/azurelinux/glibc/Dockerfile @@ -0,0 +1,191 @@ +# glibc cross-compilation image. +# +# Uses debootstrap to create a Debian sysroot, then compiles with --sysroot. +# The sysroot's glibc version determines the minimum target distro. +# The host's glibc version is IRRELEVANT — only the sysroot matters. +# +# Architecture: +# Azure Linux 3.0 (host) → clang-18 → --sysroot=/sysroot → libSkiaSharp.so +# +# Replaces: debian/11/Dockerfile (glibc 2.31) and debian/13/Dockerfile (glibc 2.38) +# +# | SYSROOT_RELEASE | glibc | libstdc++ | Supported distros | +# |-----------------|-------|-----------|--------------------------------------| +# | bullseye (11) | 2.31 | GCC 10 | Debian 11+, Ubuntu 20.04+, RHEL 9+ | +# | trixie (13) | 2.38 | GCC 14 | Debian 13+, Ubuntu 24.04+ | +# +# Bullseye is the oldest with GCC 10 libstdc++ (C++20: std::span, std::countl_zero). +# loongarch64 requires Trixie (no Bullseye/Bookworm binary archive). + +FROM mcr.microsoft.com/azurelinux/base/core:3.0 + +# Install host build tools from Azure Linux packages. +RUN tdnf install -y \ + clang \ + lld \ + llvm \ + compiler-rt \ + ninja-build \ + python3 \ + git \ + curl \ + tar \ + xz \ + debootstrap \ + dpkg \ + && tdnf clean all + +# Install the .NET SDK (needed for Cake build scripts) +ARG DOTNET_SDK_VERSION=10.0.1xx +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +RUN curl https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -L -o dotnet-install.sh \ + && bash dotnet-install.sh --channel ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ + && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ + && rm dotnet-install.sh \ + && dotnet help \ + && dotnet --info + +ENV CC=clang CXX=clang++ + +ARG BUILD_ARCH=arm64 + +# Map BUILD_ARCH to Debian triple, dpkg short arch, and clang target triple. +RUN case ${BUILD_ARCH} in \ + arm) TOOLCHAIN_ARCH=arm-linux-gnueabihf ; TOOLCHAIN_ARCH_SHORT=armhf ; TOOLCHAIN_ARCH_TARGET=armv7a-linux-gnueabihf ;; \ + arm64) TOOLCHAIN_ARCH=aarch64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=arm64 ; TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu ;; \ + riscv64) TOOLCHAIN_ARCH=riscv64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=riscv64 ; TOOLCHAIN_ARCH_TARGET=riscv64-linux-gnu ;; \ + x86) TOOLCHAIN_ARCH=i686-linux-gnu ; TOOLCHAIN_ARCH_SHORT=i386 ; TOOLCHAIN_ARCH_TARGET=i686-linux-gnu ;; \ + x64) TOOLCHAIN_ARCH=x86_64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=amd64 ; TOOLCHAIN_ARCH_TARGET=x86_64-linux-gnu ;; \ + loongarch64) TOOLCHAIN_ARCH=loongarch64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=loong64 ; TOOLCHAIN_ARCH_TARGET=loongarch64-linux-gnu ;; \ + *) echo "Unsupported architecture: ${BUILD_ARCH}" && exit 1 ;; \ + esac \ + && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ + && echo "export TOOLCHAIN_ARCH_SHORT=${TOOLCHAIN_ARCH_SHORT}" >> /etc/skia-env \ + && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env + +# Choose the Debian release for the sysroot. +# Bullseye (glibc 2.31) is default — oldest with C++20 libstdc++. +# loongarch64 needs Trixie — no binary archive exists in Bullseye/Bookworm. +ARG SYSROOT_RELEASE=bullseye + +# Determine GCC/libstdc++ version from the release. +RUN case ${SYSROOT_RELEASE} in \ + bullseye) TOOLCHAIN_VERSION=10 ;; \ + trixie) TOOLCHAIN_VERSION=14 ;; \ + *) echo "Unsupported release: ${SYSROOT_RELEASE}" && exit 1 ;; \ + esac \ + && echo "export TOOLCHAIN_VERSION=${TOOLCHAIN_VERSION}" >> /etc/skia-env + +# Create the Debian sysroot using debootstrap. +# +# --foreign: Do not run second stage (we're creating a foreign-arch sysroot +# on an amd64/arm64 host — postinst scripts can't execute). We only need +# the unpacked headers + libraries for cross-compilation, not a bootable rootfs. +# --variant=minbase: Only essential packages (dpkg, apt, coreutils). +# --include: Add the gcc cross-compilation development packages we need. +# +# NOTE: riscv64 is not a release arch in Bullseye — it's available from the +# debian-ports archive. For riscv64+bullseye, we fall back to extracting +# gcc-10-cross .deb packages (same approach as the old debian/11 Dockerfile). +ARG SYSROOT_DIR=/sysroot +RUN . /etc/skia-env \ + && case "${BUILD_ARCH}" in \ + riscv64) \ + # riscv64 has no Bullseye binary archive — use gcc-cross package extraction. + # Download the cross packages from Debian's pool and extract into sysroot. + mkdir -p ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH} \ + && TOOLCHAIN_VERSION=$(grep TOOLCHAIN_VERSION /etc/skia-env | cut -d= -f2) \ + && APT_REPO=http://deb.debian.org/debian \ + && POOL="${APT_REPO}/pool/main" \ + && mkdir -p /tmp/riscv-pkgs && cd /tmp/riscv-pkgs \ + && for pkg in \ + "g/gcc-${TOOLCHAIN_VERSION}-cross/libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "g/gcc-${TOOLCHAIN_VERSION}-cross/libgcc-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "l/linux/linux-libc-dev-${TOOLCHAIN_ARCH_SHORT}-cross"; do \ + # Find the latest version of the package + dir=$(echo $pkg | rev | cut -d/ -f1 | rev) ; \ + base=$(echo $pkg | rev | cut -d/ -f2- | rev) ; \ + url="${POOL}/${base}/" ; \ + # Download and extract — we only need data.tar + deb_url=$(curl -sL "$url" | grep -oP "href=\"\K${dir}_[^\"]+_all\.deb" | sort -V | tail -1) ; \ + if [ -n "$deb_url" ]; then \ + curl -LO "${url}${deb_url}" ; \ + dpkg-deb -x "${deb_url}" extract/ ; \ + fi ; \ + done \ + && if [ -d extract/usr/riscv64-linux-gnu ]; then \ + cp -a extract/usr/riscv64-linux-gnu/* ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/ ; \ + fi \ + && if [ -d extract/usr/lib/gcc-cross ]; then \ + mkdir -p ${SYSROOT_DIR}/usr/lib/gcc-cross ; \ + cp -a extract/usr/lib/gcc-cross/* ${SYSROOT_DIR}/usr/lib/gcc-cross/ ; \ + fi \ + && rm -rf /tmp/riscv-pkgs ;; \ + *) \ + # Standard debootstrap for architectures with Debian binary archives + debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ + --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev \ + ${SYSROOT_RELEASE} ${SYSROOT_DIR} http://deb.debian.org/debian ;; \ + esac \ + && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" + +# Create the c++/current symlink so build.cake can reference libstdc++ headers +# without baking the GCC version number into the build script. +RUN . /etc/skia-env \ + && TOOLCHAIN_VERSION=$(grep TOOLCHAIN_VERSION /etc/skia-env | cut -d= -f2) \ + && INCLUDE_DIR=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/include/c++ \ + && if [ -d "${INCLUDE_DIR}/${TOOLCHAIN_VERSION}" ]; then \ + ln -sf ${INCLUDE_DIR}/${TOOLCHAIN_VERSION} ${INCLUDE_DIR}/current ; \ + elif [ -d "${INCLUDE_DIR}" ]; then \ + GCC_VERSION=$(ls ${INCLUDE_DIR}/ | head -1) ; \ + [ -n "$GCC_VERSION" ] && ln -sf ${INCLUDE_DIR}/${GCC_VERSION} ${INCLUDE_DIR}/current ; \ + fi + +# Fix libc/libpthread linker scripts to use relative paths. +# Debian's linker scripts contain absolute paths like: +# GROUP ( /usr/lib/x86_64-linux-gnu/libc.so.6 ... ) +# These must be relative for cross-compilation with --sysroot to work. +RUN . /etc/skia-env \ + && for script in \ + ${SYSROOT_DIR}/usr/lib/${TOOLCHAIN_ARCH}/libc.so \ + ${SYSROOT_DIR}/usr/lib/${TOOLCHAIN_ARCH}/libpthread.so \ + ${SYSROOT_DIR}/usr/lib/${TOOLCHAIN_ARCH}/libpthread.so.0 \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libc.so \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so.0; do \ + if [ -f "$script" ]; then \ + sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib/||g; s|/usr/lib/${TOOLCHAIN_ARCH}/||g; s|/lib/${TOOLCHAIN_ARCH}/||g" "$script" ; \ + fi ; \ + done + +# Install fontconfig into the sysroot. +# Download .deb packages from Debian's pool and extract headers + libraries. +RUN . /etc/skia-env \ + && mkdir -p /tmp/fontconfig \ + && cd /tmp/fontconfig \ + && case "${SYSROOT_RELEASE}" in \ + bullseye) FC_VERSION=2.13.1-4.2 ;; \ + trixie) FC_VERSION=2.17.1-5 ;; \ + esac \ + && case "${TOOLCHAIN_ARCH_SHORT}" in \ + riscv64) FC_VERSION=2.15.0-2.3 ;; \ + esac \ + && APT_REPO=http://deb.debian.org/debian \ + && for pkg in libfontconfig-dev libfontconfig1; do \ + url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb"; \ + echo "Downloading $url"; \ + curl -L -o "${pkg}.deb" "$url"; \ + dpkg-deb -x "${pkg}.deb" ${SYSROOT_DIR}/ ; \ + done \ + && rm -rf /tmp/fontconfig + +# Set the SYSROOT_PATH environment variable for build.cake to use. +# The build script reads this to know where headers and libraries are. +RUN . /etc/skia-env \ + && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}" >> /etc/skia-env + +WORKDIR /work + +COPY ./startup.sh / +RUN chmod +x /startup.sh +ENTRYPOINT [ "/startup.sh" ] diff --git a/scripts/infra/native/linux/docker/azurelinux/glibc/build-local.sh b/scripts/infra/native/linux/docker/azurelinux/glibc/build-local.sh new file mode 100755 index 00000000000..b5265dd550a --- /dev/null +++ b/scripts/infra/native/linux/docker/azurelinux/glibc/build-local.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -ex + +# Parameters: +# $1 - The target architecture to build for [ arm | arm64 | riscv64 | x86 | x64 | loongarch64 ] +# $2+ - Additional arguments to pass to the cake script + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +ARCH="${1:-arm64}" +shift 1 || true +EXTRA_ARGS="$@" + +# Determine sysroot release based on architecture +case "$ARCH" in + loongarch64) SYSROOT_RELEASE=trixie ;; + *) SYSROOT_RELEASE=bullseye ;; +esac + +# the docker platform to use +MACHINE_ARCH="$(uname -m)" +case $MACHINE_ARCH in + arm64) PLATFORM=linux/arm64 ; MACHINE_ARCH=aarch64 ;; + *) PLATFORM=linux/amd64 ;; +esac + +# Build the glibc image +(cd "$DIR" && + docker build --tag skiasharp-linux-gnu-cross-$ARCH \ + --platform=$PLATFORM \ + --build-arg BUILD_ARCH=$ARCH \ + --build-arg MACHINE_ARCH=$MACHINE_ARCH \ + --build-arg SYSROOT_RELEASE=$SYSROOT_RELEASE \ + .) + +# GLIBC verification based on sysroot release +ADDITIONAL_ARGS="" +case "$SYSROOT_RELEASE" in + bullseye) ADDITIONAL_ARGS="--verifyGlibcMax=2.31" ;; + trixie) ADDITIONAL_ARGS="--verifyGlibcMax=2.38" ;; +esac + +(cd "$DIR/../../../../../.." && + docker run --rm --name skiasharp-linux-gnu-cross-$ARCH --volume $(pwd):/work skiasharp-linux-gnu-cross-$ARCH /bin/bash -c " \ + dotnet tool restore ; \ + dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=$ARCH $ADDITIONAL_ARGS $EXTRA_ARGS ") diff --git a/scripts/infra/native/linux/docker/debian/11/startup.sh b/scripts/infra/native/linux/docker/azurelinux/glibc/startup.sh similarity index 100% rename from scripts/infra/native/linux/docker/debian/11/startup.sh rename to scripts/infra/native/linux/docker/azurelinux/glibc/startup.sh diff --git a/scripts/infra/native/linux/docker/debian/11/Dockerfile b/scripts/infra/native/linux/docker/debian/11/Dockerfile deleted file mode 100644 index 95557e6e37a..00000000000 --- a/scripts/infra/native/linux/docker/debian/11/Dockerfile +++ /dev/null @@ -1,137 +0,0 @@ -# Arguments: -# DOTNET_SDK_VERSION - the version of dotnet for the Cake script [ 10.0 | * ] -# LLVM_VERSION - the version of the LLVM compiler [ 13 | * ] -# TOOLCHAIN_VERSION - the version of the GCC toolchain [ 10 | * ] -# TOOLCHAIN_ARCH - the cross triple [ arm-linux-gnueabihf | aarch64-linux-gnu | i686-linux-gnu | riscv64-linux-gnu | x86_64-linux-gnu ] -# TOOLCHAIN_ARCH_SHORT - the dpkg/Debian short architecture [ armhf | arm64 | i386 | riscv64 | amd64 ] -# TOOLCHAIN_ARCH_TARGET - the clang target triple [ armv7a-linux-gnueabihf | aarch64-linux-gnu | i686-linux-gnu | riscv64-linux-gnu | x86_64-linux-gnu ] -# -# Toolchain: clang as the front-end, gcc-cross packages for the per-arch -# sysroot. Each gcc-N-cross .deb is shipped as an amd64 package that -# unpacks a target-arch sysroot under /usr//. libSkiaSharp links -# against libstdc++ + libgcc statically. Trying to use Debian's -# `libclang-rt-N-dev`/`libc++-N-dev`/`libunwind-N-dev` multiarch -# packages instead doesn't work on Bullseye: libclang-rt-13-dev wasn't -# split out from libclang-common-13-dev until LLVM 14, riscv64 has no -# Bullseye binary archive so multiarch fails outright there, and the -# carve-outs end up using gcc-cross anyway. - -# Use --platform flag when building to select architecture -FROM mcr.microsoft.com/mirror/docker/library/debian:11 - -# Set the architecture-specific variables based on the value of the BUILD_ARCH argument -# NOTE: x64 keeps `x86-64-linux-gnu` (with hyphen) in TOOLCHAIN_ARCH so -# the apt package name `binutils-${TOOLCHAIN_ARCH}` matches Debian's -# binutils-x86-64-linux-gnu (Debian uses the dpkg/gcc alias spelling -# for cross binutils, not the canonical `x86_64-linux-gnu` triple). The -# follow-up sed in the symlink step normalizes `x86-64` -> `x86_64` -# for the `/usr//` filesystem paths, which use the canonical -# spelling. -ARG BUILD_ARCH=arm64 -RUN case ${BUILD_ARCH} in \ - arm) TOOLCHAIN_ARCH=arm-linux-gnueabihf ; TOOLCHAIN_ARCH_SHORT=armhf ; TOOLCHAIN_ARCH_TARGET=armv7a-linux-gnueabihf ;; \ - arm64) TOOLCHAIN_ARCH=aarch64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=arm64 ; TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu ;; \ - riscv64) TOOLCHAIN_ARCH=riscv64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=riscv64 ; TOOLCHAIN_ARCH_TARGET=riscv64-linux-gnu ;; \ - x86) TOOLCHAIN_ARCH=i686-linux-gnu ; TOOLCHAIN_ARCH_SHORT=i386 ; TOOLCHAIN_ARCH_TARGET=i686-linux-gnu ;; \ - x64) TOOLCHAIN_ARCH=x86-64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=amd64 ; TOOLCHAIN_ARCH_TARGET=x86_64-linux-gnu ;; \ - *) echo "Unsupported architecture: ${BUILD_ARCH}" && exit 1 ;; \ - esac \ - && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_SHORT=${TOOLCHAIN_ARCH_SHORT}" >> /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env - -# Install the host build tools. -# clang-13 (rather than 11) — clang-11's bundled mm_malloc.h declares -# posix_memalign() without `throw()` while the cross-target glibc -# stdlib.h declares it with `throw()`. C++ requires matching exception -# specs across redeclarations, so any TU that pulls in -# (e.g. modules/skcms/skcms.cc on x86/x64 with SSE) fails to compile -# with clang-11. Fixed upstream in clang-12+ which added the throw() -# spec to the intrinsics header. -ARG LLVM_VERSION=13 -RUN apt-get update \ - && apt-get install -y \ - curl python3 git ninja-build xz-utils \ - clang-${LLVM_VERSION} lld-${LLVM_VERSION} \ - && rm -rf /var/lib/apt/lists/* - -# Install the gcc-cross sysroot for the target arch. -# GCC 10 (Bullseye default) ships and in libstdc++ — -# required by Skia post-m143 ("Build everything with c++20", commit -# 50a7aaced1) which uses std::countl_zero / std::popcount / std::span. -# Older Buster (gcc-8) cannot compile modern Skia. -ARG TOOLCHAIN_VERSION=10 -RUN . /etc/skia-env \ - && apt-get update \ - && apt-get install -y \ - libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross \ - libgcc-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross \ - binutils-${TOOLCHAIN_ARCH} \ - && rm -rf /var/lib/apt/lists/* - -# Normalize TOOLCHAIN_ARCH for filesystem path use — the apt package -# name `binutils-x86-64-linux-gnu` uses hyphens, but the cross sysroot -# lives under `/usr/x86_64-linux-gnu/` (underscore). Rewrite from this -# point on so the rest of the Dockerfile (and the build) sees the -# canonical underscored triple. -# -# native/linux-clang-cross/build.cake adds -# `-I/usr//include/c++/current` to the GN cflags so the -# version-specific libstdc++ headers can be referenced without baking -# the toolchain version into the build script. Create that symlink and -# strip absolute `/usr//lib/` prefixes from the libc / -# libpthread linker scripts so they resolve relative to the cross -# sysroot at link time. (Bullseye glibc 2.31 keeps the libpthread -# linker script under libpthread.so, not libpthread.so.0.) -RUN . /etc/skia-env \ - && TOOLCHAIN_ARCH=$(echo ${TOOLCHAIN_ARCH} | sed 's/x86-64/x86_64/g') \ - && ln -s /usr/${TOOLCHAIN_ARCH}/include/c++/${TOOLCHAIN_VERSION} \ - /usr/${TOOLCHAIN_ARCH}/include/c++/current \ - && sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib/||g" /usr/${TOOLCHAIN_ARCH}/lib/libpthread.so \ - && sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib/||g" /usr/${TOOLCHAIN_ARCH}/lib/libc.so - -# Install fontconfig for the target arch. We need BOTH libfontconfig1 -# (runtime .so) and libfontconfig1-dev (headers + .a). The pool/ tree -# is suite-agnostic, so for riscv64 (which has no Bullseye binary -# archive) we reach into Trixie's pool — the resulting libfontconfig -# .so.1 / headers drop into the cross sysroot cleanly because the -# fontconfig ABI is stable and runtime resolution happens against -# whatever fontconfig the user's system ships. Other archs use -# Bullseye's own .debs. -RUN . /etc/skia-env \ - && mkdir -p /skia-utils/libfontconfig-dev \ - && cd /skia-utils/libfontconfig-dev \ - && case "${TOOLCHAIN_ARCH_SHORT}" in \ - riscv64) FC_VERSION=2.15.0-2.3 ;; \ - *) FC_VERSION=2.13.1-4.2 ;; \ - esac \ - && APT_REPO=http://deb.debian.org/debian \ - && TOOLCHAIN_ARCH=$(echo ${TOOLCHAIN_ARCH} | sed 's/x86-64/x86_64/g') \ - && for pkg in libfontconfig-dev libfontconfig1; do \ - url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb"; \ - echo "Downloading $url"; \ - curl -L -o "${pkg}.deb" "$url"; \ - ar x "${pkg}.deb" data.tar.xz \ - && tar -xf data.tar.xz \ - && cp -R usr/lib/*/* /usr/${TOOLCHAIN_ARCH}/lib/ \ - && { [ -d usr/include ] && cp -R usr/include/* /usr/${TOOLCHAIN_ARCH}/include/ || true; } \ - && rm -rf *.deb *.tar* debian-binary usr; \ - done - -# Install the .NET SDK -ARG DOTNET_SDK_VERSION=10.0.1xx -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -RUN curl https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -L -o dotnet-install.sh \ - && bash dotnet-install.sh --channel ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ - && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ - && rm dotnet-install.sh \ - && dotnet help \ - && dotnet --info - -ENV CC=clang-${LLVM_VERSION} CXX=clang++-${LLVM_VERSION} - -WORKDIR /work - -COPY ./startup.sh / -RUN chmod +x /startup.sh -ENTRYPOINT [ "/startup.sh" ] diff --git a/scripts/infra/native/linux/docker/debian/11/build-local.sh b/scripts/infra/native/linux/docker/debian/11/build-local.sh deleted file mode 100755 index e6778b637bf..00000000000 --- a/scripts/infra/native/linux/docker/debian/11/build-local.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -ex - -# Parameters: -# $1 - The target architecture to build for [ arm | arm64 | riscv64 | x86 | x64 | loongarch64 ] -# $2+ - Additional arguments to pass to the cake script - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -ARCH="${1:-arm}" -shift 1 || true -EXTRA_ARGS="$@" - -$DIR/../../_clang-cross-common.sh "$DIR" "$ARCH" "gnu" "" "$EXTRA_ARGS" diff --git a/scripts/infra/native/linux/docker/debian/13/.dockerignore b/scripts/infra/native/linux/docker/debian/13/.dockerignore deleted file mode 100644 index 3a09fc21d8d..00000000000 --- a/scripts/infra/native/linux/docker/debian/13/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -build-local.sh diff --git a/scripts/infra/native/linux/docker/debian/13/Dockerfile b/scripts/infra/native/linux/docker/debian/13/Dockerfile deleted file mode 100644 index 45eecb56e20..00000000000 --- a/scripts/infra/native/linux/docker/debian/13/Dockerfile +++ /dev/null @@ -1,111 +0,0 @@ -# Arguments: -# DOTNET_SDK_VERSION - the version of dotnet for the Cake script [ 10.0 | * ] -# LLVM_VERSION - the version of the LLVM compiler [ 19 | * ] -# TOOLCHAIN_VERSION - the version of the GCC toolchain [ 14 | * ] -# TOOLCHAIN_ARCH - the multiarch triple [ loongarch64-linux-gnu ] -# TOOLCHAIN_ARCH_SHORT - the dpkg/Debian short architecture [ loong64 ] -# TOOLCHAIN_ARCH_TARGET - the LLVM target triple passed to clang [ loongarch64-linux-gnu ] -# -# This image only services loongarch64. Other archs are routed to -# scripts/Docker/debian/11 (Bullseye + glibc 2.31), which has the -# matching gcc-N-cross packages for those targets. -# -# Trixie main has no loong64 binary archive (loong64 is still a -# debian-ports arch, not yet a release arch), but Trixie does ship -# gcc-14-cross packages built from cross-toolchain-base — packaged -# as amd64 .debs containing a loong64 sysroot under -# /usr/loongarch64-linux-gnu/. The build links libstdc++/libgcc -# statically against that sysroot. Same shape as the Bullseye image, -# just on Trixie's gcc-14 / glibc 2.38. - -# Use --platform flag when building to select architecture -# NOTE: Using devcontainers base image because mcr.microsoft.com/mirror/docker/library/debian:13 -# is not yet available. This is a Debian 13 (Trixie) image hosted on MCR. -FROM mcr.microsoft.com/devcontainers/base:debian13 - -USER root - -# Set the architecture-specific variables based on the value of the BUILD_ARCH argument -ARG BUILD_ARCH=loongarch64 -RUN case ${BUILD_ARCH} in \ - loongarch64) TOOLCHAIN_ARCH=loongarch64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=loong64 ; TOOLCHAIN_ARCH_TARGET=loongarch64-linux-gnu ;; \ - *) echo "debian/13 image only supports loongarch64; got: ${BUILD_ARCH}" && exit 1 ;; \ - esac \ - && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_SHORT=${TOOLCHAIN_ARCH_SHORT}" >> /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env - -# Install the host build tools. -ARG LLVM_VERSION=19 -RUN apt-get update \ - && apt-get install -y \ - curl python3 git ninja-build xz-utils \ - clang-${LLVM_VERSION} lld-${LLVM_VERSION} \ - && rm -rf /var/lib/apt/lists/* - -# Install the loong64 gcc-cross sysroot. cross-toolchain-base ships -# loong64 cross packages on Trixie even though loong64 isn't a release -# arch — they install under /usr/loongarch64-linux-gnu/ and bring along -# glibc + libstdc++ + libgcc. -ARG TOOLCHAIN_VERSION=14 -RUN . /etc/skia-env \ - && apt-get update \ - && apt-get install -y \ - libc6-dev-${TOOLCHAIN_ARCH_SHORT}-cross \ - libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross \ - libgcc-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross \ - binutils-${TOOLCHAIN_ARCH} \ - && rm -rf /var/lib/apt/lists/* - -# native/linux-clang-cross/build.cake adds -# `-I/usr//include/c++/current` to the GN cflags so the -# version-specific libstdc++ headers can be referenced without baking -# the toolchain version into the build script. Create that symlink and -# strip absolute `/usr//lib/` prefixes from the libc / -# libpthread linker scripts so they resolve relative to the cross -# sysroot at link time. (libpthread.so.0 here, not libpthread.so — -# Trixie's glibc 2.41 keeps the linker script under the .0 name.) -RUN . /etc/skia-env \ - && ln -s /usr/${TOOLCHAIN_ARCH}/include/c++/${TOOLCHAIN_VERSION} \ - /usr/${TOOLCHAIN_ARCH}/include/c++/current \ - && sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib/||g" /usr/${TOOLCHAIN_ARCH}/lib/libpthread.so.0 \ - && sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib/||g" /usr/${TOOLCHAIN_ARCH}/lib/libc.so - -# loong64 fontconfig: pull from the Trixie pool — the pool/ tree is -# suite-agnostic and serves loong64 .debs even though Trixie's main -# binary lists don't include them. ABI-stable so headers + libfont- -# config.so.1 from the Trixie build drop into the cross sysroot -# cleanly. -RUN . /etc/skia-env \ - && mkdir -p /skia-utils/libfontconfig-dev \ - && cd /skia-utils/libfontconfig-dev \ - && FC_VERSION=2.17.1-5 \ - && APT_REPO=http://deb.debian.org/debian \ - && for pkg in libfontconfig-dev libfontconfig1; do \ - url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb"; \ - echo "Downloading $url"; \ - curl -L -o "${pkg}.deb" "$url"; \ - ar x "${pkg}.deb" data.tar.xz \ - && tar -xf data.tar.xz \ - && cp -R usr/lib/*/* /usr/${TOOLCHAIN_ARCH}/lib/ \ - && { [ -d usr/include ] && cp -R usr/include/* /usr/${TOOLCHAIN_ARCH}/include/ || true; } \ - && rm -rf *.deb *.tar* debian-binary usr; \ - done - -# Install the .NET SDK -ARG DOTNET_SDK_VERSION=10.0.1xx -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -RUN curl https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -L -o dotnet-install.sh \ - && bash dotnet-install.sh --channel ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ - && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ - && rm dotnet-install.sh \ - && dotnet help \ - && dotnet --info - -ENV CC=clang-${LLVM_VERSION} CXX=clang++-${LLVM_VERSION} - -WORKDIR /work - -COPY ./startup.sh / -RUN chmod +x /startup.sh -ENTRYPOINT [ "/startup.sh" ] diff --git a/scripts/infra/native/linux/docker/debian/13/build-local.sh b/scripts/infra/native/linux/docker/debian/13/build-local.sh deleted file mode 100644 index c5cbdf36d0e..00000000000 --- a/scripts/infra/native/linux/docker/debian/13/build-local.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -ex - -# Parameters: -# $1 - The target architecture to build for [ arm | arm64 | riscv64 | x86 | x64 | loongarch64 ] -# $2+ - Additional arguments to pass to the cake script - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -ARCH="${1:-loongarch64}" -shift 1 || true -EXTRA_ARGS="$@" - -$DIR/../../_clang-cross-common.sh "$DIR" "$ARCH" "gnu" "" "$EXTRA_ARGS" diff --git a/scripts/infra/native/linux/docker/debian/13/startup.sh b/scripts/infra/native/linux/docker/debian/13/startup.sh deleted file mode 100644 index 512bb431bf7..00000000000 --- a/scripts/infra/native/linux/docker/debian/13/startup.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -e - -source /etc/skia-env - -exec "$@" From dc57c7b19655f81433bd4cbcdfefa3e8cba5db07 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 00:16:29 +0200 Subject: [PATCH 02/45] =?UTF-8?q?Remove=20azurelinux=20subfolder=20?= =?UTF-8?q?=E2=80=94=20flatten=20Docker=20directory=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All images are Azure Linux now, so the extra nesting is redundant. New paths: scripts/infra/native/linux/docker/{glibc,alpine,bionic}/ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .agents/skills/issue-fix/SKILL.md | 4 ++-- documentation/dev/building-linux.md | 2 +- documentation/dev/debugging-methodology.md | 2 +- scripts/azure-templates-stages-native-linux.yml | 8 ++++---- .../linux/docker/{azurelinux => }/alpine/Dockerfile | 0 .../linux/docker/{azurelinux => }/alpine/build-local.sh | 0 .../linux/docker/{azurelinux => }/alpine/startup.sh | 0 .../linux/docker/{azurelinux => }/bionic/Dockerfile | 0 .../linux/docker/{azurelinux => }/bionic/build-local.sh | 0 .../linux/docker/{azurelinux => }/bionic/startup.sh | 0 .../native/linux/docker/{azurelinux => }/glibc/Dockerfile | 0 .../linux/docker/{azurelinux => }/glibc/build-local.sh | 0 .../native/linux/docker/{azurelinux => }/glibc/startup.sh | 0 13 files changed, 8 insertions(+), 8 deletions(-) rename scripts/infra/native/linux/docker/{azurelinux => }/alpine/Dockerfile (100%) rename scripts/infra/native/linux/docker/{azurelinux => }/alpine/build-local.sh (100%) rename scripts/infra/native/linux/docker/{azurelinux => }/alpine/startup.sh (100%) rename scripts/infra/native/linux/docker/{azurelinux => }/bionic/Dockerfile (100%) rename scripts/infra/native/linux/docker/{azurelinux => }/bionic/build-local.sh (100%) rename scripts/infra/native/linux/docker/{azurelinux => }/bionic/startup.sh (100%) rename scripts/infra/native/linux/docker/{azurelinux => }/glibc/Dockerfile (100%) rename scripts/infra/native/linux/docker/{azurelinux => }/glibc/build-local.sh (100%) rename scripts/infra/native/linux/docker/{azurelinux => }/glibc/startup.sh (100%) diff --git a/.agents/skills/issue-fix/SKILL.md b/.agents/skills/issue-fix/SKILL.md index 700fce1e952..1e42ff48982 100644 --- a/.agents/skills/issue-fix/SKILL.md +++ b/.agents/skills/issue-fix/SKILL.md @@ -282,10 +282,10 @@ When a bug affects one platform but not another, **build both platforms locally* ```bash # Build x64 native (in Docker) -bash ./scripts/infra/native/linux/docker/azurelinux/glibc/build-local.sh x64 +bash ./scripts/infra/native/linux/docker/glibc/build-local.sh x64 # Build ARM64 cross-compile (in Docker) -bash ./scripts/infra/native/linux/docker/azurelinux/glibc/build-local.sh arm64 +bash ./scripts/infra/native/linux/docker/glibc/build-local.sh arm64 # Compare DT_NEEDED entries (the linked libraries) docker run --rm -v $(pwd):/work debian:bookworm-slim bash -c \ diff --git a/documentation/dev/building-linux.md b/documentation/dev/building-linux.md index 7260fcee7d4..c48b9511a70 100644 --- a/documentation/dev/building-linux.md +++ b/documentation/dev/building-linux.md @@ -82,7 +82,7 @@ Building for different Linux distributions is easier with Docker. The repository ```bash # Build the Docker image -cd scripts/infra/native/linux/docker/azurelinux/alpine +cd scripts/infra/native/linux/docker/alpine docker build --tag skiasharp-alpine --build-arg BUILD_ARCH=x64 . # Run the build diff --git a/documentation/dev/debugging-methodology.md b/documentation/dev/debugging-methodology.md index d0eadb1149c..e082c7d9d5f 100644 --- a/documentation/dev/debugging-methodology.md +++ b/documentation/dev/debugging-methodology.md @@ -199,7 +199,7 @@ but the actual `.so.1.2.3` file is in the runtime package (`libfoo1`), not the d | Root Cause | Fix Location | |------------|--------------| | Library missing from linker flags | `native/linux/build.cake` or `externals/skia/third_party/BUILD.gn` | -| Library missing from cross-compile sysroot | `scripts/infra/native/linux/docker/azurelinux/glibc/Dockerfile` | +| Library missing from cross-compile sysroot | `scripts/infra/native/linux/docker/glibc/Dockerfile` | | Indirect dependency (A→B→C missing) | Fix B's linkage or add C explicitly | ### Real Example: ARM64 fontconfig issue (#3369) diff --git a/scripts/azure-templates-stages-native-linux.yml b/scripts/azure-templates-stages-native-linux.yml index f916d873909..ff8d6734a5c 100644 --- a/scripts/azure-templates-stages-native-linux.yml +++ b/scripts/azure-templates-stages-native-linux.yml @@ -33,21 +33,21 @@ stages: # first release arch in Trixie) - ${{ each arch in split('loongarch64', ',') }}: - arch: ${{ arch }} - docker: scripts/infra/native/linux/docker/azurelinux/glibc + docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross additionalArgs: --verifyGlibcMax=2.38 dockerArgs: --build-arg SYSROOT_RELEASE=trixie # Azure Linux + Bullseye sysroot (gcc-10, glibc 2.31). - ${{ each arch in split('arm,arm64,x86,x64,riscv64', ',') }}: - arch: ${{ arch }} - docker: scripts/infra/native/linux/docker/azurelinux/glibc + docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross additionalArgs: --verifyGlibcMax=2.31 dockerArgs: --build-arg SYSROOT_RELEASE=bullseye - ${{ each arch in split('arm,arm64,x64,riscv64,loongarch64', ',') }}: # removed x86 as it is having issues building - arch: ${{ arch }} variant: alpine - docker: scripts/infra/native/linux/docker/azurelinux/alpine + docker: scripts/infra/native/linux/docker/alpine target: externals-linux-clang-cross - template: /scripts/azure-templates-jobs-linux-matrix.yml@self # Build Native Linux (Bionic) @@ -62,5 +62,5 @@ stages: - ${{ each arch in split('arm64,x64', ',') }}: - arch: ${{ arch }} variant: bionic - docker: scripts/infra/native/linux/docker/azurelinux/bionic + docker: scripts/infra/native/linux/docker/bionic diff --git a/scripts/infra/native/linux/docker/azurelinux/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile similarity index 100% rename from scripts/infra/native/linux/docker/azurelinux/alpine/Dockerfile rename to scripts/infra/native/linux/docker/alpine/Dockerfile diff --git a/scripts/infra/native/linux/docker/azurelinux/alpine/build-local.sh b/scripts/infra/native/linux/docker/alpine/build-local.sh similarity index 100% rename from scripts/infra/native/linux/docker/azurelinux/alpine/build-local.sh rename to scripts/infra/native/linux/docker/alpine/build-local.sh diff --git a/scripts/infra/native/linux/docker/azurelinux/alpine/startup.sh b/scripts/infra/native/linux/docker/alpine/startup.sh similarity index 100% rename from scripts/infra/native/linux/docker/azurelinux/alpine/startup.sh rename to scripts/infra/native/linux/docker/alpine/startup.sh diff --git a/scripts/infra/native/linux/docker/azurelinux/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile similarity index 100% rename from scripts/infra/native/linux/docker/azurelinux/bionic/Dockerfile rename to scripts/infra/native/linux/docker/bionic/Dockerfile diff --git a/scripts/infra/native/linux/docker/azurelinux/bionic/build-local.sh b/scripts/infra/native/linux/docker/bionic/build-local.sh similarity index 100% rename from scripts/infra/native/linux/docker/azurelinux/bionic/build-local.sh rename to scripts/infra/native/linux/docker/bionic/build-local.sh diff --git a/scripts/infra/native/linux/docker/azurelinux/bionic/startup.sh b/scripts/infra/native/linux/docker/bionic/startup.sh similarity index 100% rename from scripts/infra/native/linux/docker/azurelinux/bionic/startup.sh rename to scripts/infra/native/linux/docker/bionic/startup.sh diff --git a/scripts/infra/native/linux/docker/azurelinux/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile similarity index 100% rename from scripts/infra/native/linux/docker/azurelinux/glibc/Dockerfile rename to scripts/infra/native/linux/docker/glibc/Dockerfile diff --git a/scripts/infra/native/linux/docker/azurelinux/glibc/build-local.sh b/scripts/infra/native/linux/docker/glibc/build-local.sh similarity index 100% rename from scripts/infra/native/linux/docker/azurelinux/glibc/build-local.sh rename to scripts/infra/native/linux/docker/glibc/build-local.sh diff --git a/scripts/infra/native/linux/docker/azurelinux/glibc/startup.sh b/scripts/infra/native/linux/docker/glibc/startup.sh similarity index 100% rename from scripts/infra/native/linux/docker/azurelinux/glibc/startup.sh rename to scripts/infra/native/linux/docker/glibc/startup.sh From db6ceef5dea3f21150146bacc9937f062bc89078 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 01:41:51 +0200 Subject: [PATCH 03/45] Fix Docker build issues found during local testing - Add gawk and ca-certificates to all tdnf installs (needed by dotnet-install.sh and HTTPS downloads respectively) - Remove dpkg from glibc tdnf install (not available in Azure Linux; debootstrap brings binutils which provides ar) - Replace dpkg-deb extraction with ar+tar (works with binutils from Azure Linux) - Fix Alpine apk.static: write repositories file + use -p for package cache + --update-cache (apk needs persistent repo config) - Write APK_ARCH to /etc/skia-env so it persists across RUN layers All three images (alpine, glibc, bionic) now build successfully on Docker Desktop arm64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../infra/native/linux/docker/alpine/Dockerfile | 17 +++++++++-------- .../infra/native/linux/docker/bionic/Dockerfile | 2 ++ .../infra/native/linux/docker/glibc/Dockerfile | 13 +++++++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index c71e1f97a27..54adcb64c25 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -23,6 +23,8 @@ RUN tdnf install -y \ curl \ tar \ xz \ + gawk \ + ca-certificates \ && tdnf clean all # Install the .NET SDK (needed for Cake build scripts) @@ -51,6 +53,7 @@ RUN case ${BUILD_ARCH} in \ esac \ && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env \ + && echo "export APK_ARCH=${APK_ARCH}" >> /etc/skia-env \ && echo "export SYSROOT_PATH=/alpine/usr" >> /etc/skia-env # Create Alpine sysroot using apk.static. @@ -67,16 +70,14 @@ RUN . /etc/skia-env \ --create-dirs --output-dir "$APK_DIR" \ "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.12.14/$MACHINE_ARCH/apk.static" \ && chmod +x "$APK_DIR/apk.static" \ + && mkdir -p /alpine/etc/apk \ + && echo "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" > /alpine/etc/apk/repositories \ + && echo "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" >> /alpine/etc/apk/repositories \ && "$APK_DIR/apk.static" \ - -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ - -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ - -U --allow-untrusted --root /alpine --arch "$APK_ARCH" --initdb add \ - && "$APK_DIR/apk.static" \ - -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ - -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ - -U --allow-untrusted --root /alpine --arch "$APK_ARCH" --no-scripts \ + -p /alpine/packages --root /alpine --arch "$APK_ARCH" \ + --update-cache --allow-untrusted --initdb \ add musl-dev linux-headers fontconfig-dev \ - && rm -rf "$APK_DIR" + && rm -rf "$APK_DIR" /alpine/packages WORKDIR /work diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index 93e08fdbe02..a56f3583151 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -19,6 +19,8 @@ RUN tdnf install -y \ tar \ xz \ unzip \ + gawk \ + ca-certificates \ && tdnf clean all # Install the .NET SDK (needed for Cake build scripts) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index dbd0ff23b66..e587681029f 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -31,8 +31,9 @@ RUN tdnf install -y \ curl \ tar \ xz \ + gawk \ + ca-certificates \ debootstrap \ - dpkg \ && tdnf clean all # Install the .NET SDK (needed for Cake build scripts) @@ -106,11 +107,13 @@ RUN . /etc/skia-env \ dir=$(echo $pkg | rev | cut -d/ -f1 | rev) ; \ base=$(echo $pkg | rev | cut -d/ -f2- | rev) ; \ url="${POOL}/${base}/" ; \ - # Download and extract — we only need data.tar + # Download and extract — ar x + tar (no dpkg-deb needed) deb_url=$(curl -sL "$url" | grep -oP "href=\"\K${dir}_[^\"]+_all\.deb" | sort -V | tail -1) ; \ if [ -n "$deb_url" ]; then \ curl -LO "${url}${deb_url}" ; \ - dpkg-deb -x "${deb_url}" extract/ ; \ + ar x "${deb_url}" ; \ + mkdir -p extract && tar xf data.tar.* -C extract/ ; \ + rm -f control.tar.* data.tar.* debian-binary ; \ fi ; \ done \ && if [ -d extract/usr/riscv64-linux-gnu ]; then \ @@ -175,7 +178,9 @@ RUN . /etc/skia-env \ url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb"; \ echo "Downloading $url"; \ curl -L -o "${pkg}.deb" "$url"; \ - dpkg-deb -x "${pkg}.deb" ${SYSROOT_DIR}/ ; \ + ar x "${pkg}.deb" ; \ + tar xf data.tar.* -C ${SYSROOT_DIR}/ ; \ + rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ done \ && rm -rf /tmp/fontconfig From fe7a44fbbf859cb86ae5eb041a42c403cdcf638e Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 06:09:36 +0200 Subject: [PATCH 04/45] Fix CI failures: sysroot paths, missing packages, and security improvements - glibc: Fix SYSROOT_PATH to differ per layout (debootstrap vs cross-packages) - glibc: Add libc6-dev cross package for riscv64/loongarch64 (required for linking) - glibc: Use -ports pool suffix for loongarch64 packages (gcc-14-cross-ports) - glibc: Add binutils (for readelf post-build check and ar for .deb extraction) - glibc: Fix c++ symlink to use SYSROOT_PATH-relative path - glibc: Relocate fontconfig files for cross-package arch layout - glibc: Switch to HTTPS for Debian package downloads - alpine: Revert to two-step apk.static invocation with -X flags (fixes 'Permission denied') - alpine: Add libstdc++-dev to sysroot (required for C++ compilation) - alpine: Add binutils to host (for readelf) - bionic: Add binutils to host (for readelf) - build.cake: Add -fuse-ld=lld for all variants (no cross-linker available) - build.cake: Add arch-specific include/lib paths for cross-compilation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux-clang-cross/build.cake | 5 +- .../native/linux/docker/alpine/Dockerfile | 18 +++-- .../native/linux/docker/bionic/Dockerfile | 1 + .../native/linux/docker/glibc/Dockerfile | 73 ++++++++++++------- 4 files changed, 62 insertions(+), 35 deletions(-) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index 274bb85bc84..e836f6e9cce 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -20,15 +20,16 @@ string GetGnArgs(string arch) var (sysrootArg, linker) = BUILD_VARIANT switch { "alpine" or "alpinenodeps" => ("'--sysroot=/alpine', ", "'-fuse-ld=lld'"), - _ => ("", ""), + _ => ("", "'-fuse-ld=lld'"), }; var sysroot = SYSROOT_PATH; var init = $"{sysrootArg} '--target={TOOLCHAIN_ARCH_TARGET}'"; var bin = $"'-B{sysroot}/bin/' "; - var libs = $"'-L{sysroot}/lib/' "; + var libs = $"'-L{sysroot}/lib/', '-L{sysroot}/lib/{TOOLCHAIN_ARCH}/' "; var includes = $"'-I{sysroot}/include', " + + $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}', " + $"'-I{sysroot}/include/c++/current', " + $"'-I{sysroot}/include/c++/current/{TOOLCHAIN_ARCH}' "; diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 54adcb64c25..3f566cddb38 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -25,6 +25,7 @@ RUN tdnf install -y \ xz \ gawk \ ca-certificates \ + binutils \ && tdnf clean all # Install the .NET SDK (needed for Cake build scripts) @@ -61,6 +62,7 @@ RUN case ${BUILD_ARCH} in \ # musl-dev: C library headers and runtime # linux-headers: kernel headers # fontconfig-dev: font configuration headers and library +# libstdc++-dev: C++ standard library headers (needed by Skia) # Avoids build-base which pulls in busybox (~35 CVEs) and binutils (~15 CVEs). ARG DISTRO_VERSION=3.21 ARG MACHINE_ARCH=x86_64 @@ -70,14 +72,16 @@ RUN . /etc/skia-env \ --create-dirs --output-dir "$APK_DIR" \ "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.12.14/$MACHINE_ARCH/apk.static" \ && chmod +x "$APK_DIR/apk.static" \ - && mkdir -p /alpine/etc/apk \ - && echo "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" > /alpine/etc/apk/repositories \ - && echo "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" >> /alpine/etc/apk/repositories \ && "$APK_DIR/apk.static" \ - -p /alpine/packages --root /alpine --arch "$APK_ARCH" \ - --update-cache --allow-untrusted --initdb \ - add musl-dev linux-headers fontconfig-dev \ - && rm -rf "$APK_DIR" /alpine/packages + -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ + -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ + -U --allow-untrusted --root /alpine --arch "$APK_ARCH" --initdb add \ + && "$APK_DIR/apk.static" \ + -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ + -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ + -U --allow-untrusted --root /alpine --arch "$APK_ARCH" --no-scripts \ + add musl-dev linux-headers fontconfig-dev libstdc++-dev \ + && rm -rf "$APK_DIR" WORKDIR /work diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index a56f3583151..b90ee72b01c 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -21,6 +21,7 @@ RUN tdnf install -y \ unzip \ gawk \ ca-certificates \ + binutils \ && tdnf clean all # Install the .NET SDK (needed for Cake build scripts) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index e587681029f..e3b29be1a00 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -33,6 +33,7 @@ RUN tdnf install -y \ xz \ gawk \ ca-certificates \ + binutils \ debootstrap \ && tdnf clean all @@ -91,23 +92,27 @@ RUN case ${SYSROOT_RELEASE} in \ ARG SYSROOT_DIR=/sysroot RUN . /etc/skia-env \ && case "${BUILD_ARCH}" in \ - riscv64) \ - # riscv64 has no Bullseye binary archive — use gcc-cross package extraction. - # Download the cross packages from Debian's pool and extract into sysroot. + riscv64|loongarch64) \ + # riscv64/loongarch64 have no standard Debian binary archive — use gcc-cross package extraction. + # This produces the cross-package layout at /sysroot/usr/{TOOLCHAIN_ARCH}/ + # loong64 packages are in -ports variants; riscv64 is in the standard pool. mkdir -p ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH} \ && TOOLCHAIN_VERSION=$(grep TOOLCHAIN_VERSION /etc/skia-env | cut -d= -f2) \ - && APT_REPO=http://deb.debian.org/debian \ + && APT_REPO=https://deb.debian.org/debian \ && POOL="${APT_REPO}/pool/main" \ - && mkdir -p /tmp/riscv-pkgs && cd /tmp/riscv-pkgs \ + && case "${TOOLCHAIN_ARCH_SHORT}" in \ + loong64) PORTS_SUFFIX="-ports" ;; \ + *) PORTS_SUFFIX="" ;; \ + esac \ + && mkdir -p /tmp/cross-pkgs && cd /tmp/cross-pkgs \ && for pkg in \ - "g/gcc-${TOOLCHAIN_VERSION}-cross/libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "g/gcc-${TOOLCHAIN_VERSION}-cross/libgcc-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "l/linux/linux-libc-dev-${TOOLCHAIN_ARCH_SHORT}-cross"; do \ - # Find the latest version of the package + "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libgcc-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "c/cross-toolchain-base${PORTS_SUFFIX}/linux-libc-dev-${TOOLCHAIN_ARCH_SHORT}-cross"; do \ dir=$(echo $pkg | rev | cut -d/ -f1 | rev) ; \ base=$(echo $pkg | rev | cut -d/ -f2- | rev) ; \ url="${POOL}/${base}/" ; \ - # Download and extract — ar x + tar (no dpkg-deb needed) deb_url=$(curl -sL "$url" | grep -oP "href=\"\K${dir}_[^\"]+_all\.deb" | sort -V | tail -1) ; \ if [ -n "$deb_url" ]; then \ curl -LO "${url}${deb_url}" ; \ @@ -116,32 +121,38 @@ RUN . /etc/skia-env \ rm -f control.tar.* data.tar.* debian-binary ; \ fi ; \ done \ - && if [ -d extract/usr/riscv64-linux-gnu ]; then \ - cp -a extract/usr/riscv64-linux-gnu/* ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/ ; \ + && if [ -d extract/usr/${TOOLCHAIN_ARCH} ]; then \ + cp -a extract/usr/${TOOLCHAIN_ARCH}/* ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/ ; \ fi \ && if [ -d extract/usr/lib/gcc-cross ]; then \ mkdir -p ${SYSROOT_DIR}/usr/lib/gcc-cross ; \ cp -a extract/usr/lib/gcc-cross/* ${SYSROOT_DIR}/usr/lib/gcc-cross/ ; \ fi \ - && rm -rf /tmp/riscv-pkgs ;; \ + && rm -rf /tmp/cross-pkgs \ + && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}" >> /etc/skia-env ;; \ *) \ - # Standard debootstrap for architectures with Debian binary archives + # Standard debootstrap for architectures with Debian binary archives. + # Produces native layout: headers at /usr/include/, libs at /usr/lib/{triple}/ debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev \ - ${SYSROOT_RELEASE} ${SYSROOT_DIR} http://deb.debian.org/debian ;; \ + ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ + && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env ;; \ esac \ && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" # Create the c++/current symlink so build.cake can reference libstdc++ headers # without baking the GCC version number into the build script. +# Debootstrap: headers at /sysroot/usr/include/c++/VERSION/ +# Cross-packages (riscv64/loongarch64): headers at /sysroot/usr/{ARCH}/include/c++/VERSION/ RUN . /etc/skia-env \ && TOOLCHAIN_VERSION=$(grep TOOLCHAIN_VERSION /etc/skia-env | cut -d= -f2) \ - && INCLUDE_DIR=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/include/c++ \ + && SYSROOT_PATH_VAL=$(grep SYSROOT_PATH /etc/skia-env | cut -d= -f2) \ + && INCLUDE_DIR=${SYSROOT_PATH_VAL}/include/c++ \ && if [ -d "${INCLUDE_DIR}/${TOOLCHAIN_VERSION}" ]; then \ - ln -sf ${INCLUDE_DIR}/${TOOLCHAIN_VERSION} ${INCLUDE_DIR}/current ; \ + ln -sf ${TOOLCHAIN_VERSION} ${INCLUDE_DIR}/current ; \ elif [ -d "${INCLUDE_DIR}" ]; then \ GCC_VERSION=$(ls ${INCLUDE_DIR}/ | head -1) ; \ - [ -n "$GCC_VERSION" ] && ln -sf ${INCLUDE_DIR}/${GCC_VERSION} ${INCLUDE_DIR}/current ; \ + [ -n "$GCC_VERSION" ] && ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current ; \ fi # Fix libc/libpthread linker scripts to use relative paths. @@ -163,7 +174,9 @@ RUN . /etc/skia-env \ # Install fontconfig into the sysroot. # Download .deb packages from Debian's pool and extract headers + libraries. +# For cross-package arches (riscv64/loongarch64), relocate to match the SYSROOT_PATH layout. RUN . /etc/skia-env \ + && SYSROOT_PATH_VAL=$(grep "SYSROOT_PATH=" /etc/skia-env | cut -d= -f2) \ && mkdir -p /tmp/fontconfig \ && cd /tmp/fontconfig \ && case "${SYSROOT_RELEASE}" in \ @@ -171,24 +184,32 @@ RUN . /etc/skia-env \ trixie) FC_VERSION=2.17.1-5 ;; \ esac \ && case "${TOOLCHAIN_ARCH_SHORT}" in \ - riscv64) FC_VERSION=2.15.0-2.3 ;; \ + riscv64) FC_VERSION=2.15.0-2.3 ;; \ esac \ - && APT_REPO=http://deb.debian.org/debian \ + && APT_REPO=https://deb.debian.org/debian \ + && mkdir -p /tmp/fontconfig/extract \ && for pkg in libfontconfig-dev libfontconfig1; do \ url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb"; \ echo "Downloading $url"; \ curl -L -o "${pkg}.deb" "$url"; \ ar x "${pkg}.deb" ; \ - tar xf data.tar.* -C ${SYSROOT_DIR}/ ; \ + tar xf data.tar.* -C /tmp/fontconfig/extract/ ; \ rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ done \ + && case "${BUILD_ARCH}" in \ + riscv64|loongarch64) \ + # Cross-package layout: put headers and libs under SYSROOT_PATH + mkdir -p ${SYSROOT_PATH_VAL}/include ${SYSROOT_PATH_VAL}/lib ; \ + cp -a /tmp/fontconfig/extract/usr/include/* ${SYSROOT_PATH_VAL}/include/ ; \ + if [ -d "/tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}" ]; then \ + cp -a /tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}/* ${SYSROOT_PATH_VAL}/lib/ ; \ + fi ;; \ + *) \ + # Debootstrap layout: extract directly to sysroot root + cp -a /tmp/fontconfig/extract/* ${SYSROOT_DIR}/ ;; \ + esac \ && rm -rf /tmp/fontconfig -# Set the SYSROOT_PATH environment variable for build.cake to use. -# The build script reads this to know where headers and libraries are. -RUN . /etc/skia-env \ - && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}" >> /etc/skia-env - WORKDIR /work COPY ./startup.sh / From 739ff3323f96ccf324fa3bbc0a9011728fac1d7e Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 06:54:45 +0200 Subject: [PATCH 05/45] Fix cstddef not found: extract --include debs after debootstrap --foreign Root cause: debootstrap --foreign only extracts essential/base packages. The --include packages (libstdc++-dev, libgcc-dev, linux-libc-dev) are downloaded to var/cache/apt/archives/ but NOT extracted during first stage. The second stage (which we skip with --foreign) would normally unpack them. Fix: After debootstrap completes, manually extract the --include dev packages from the apt archives cache into the sysroot. Also adds the missing c++/current symlink to the Alpine Dockerfile and improves diagnostic output in the glibc symlink step so future failures are immediately visible in the Docker build log. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/alpine/Dockerfile | 13 +++++ .../native/linux/docker/glibc/Dockerfile | 47 +++++++++++++++++-- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 3f566cddb38..24f5ee6c962 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -83,6 +83,19 @@ RUN . /etc/skia-env \ add musl-dev linux-headers fontconfig-dev libstdc++-dev \ && rm -rf "$APK_DIR" +# Create c++/current symlink so build.cake can find libstdc++ headers +# without hardcoding the GCC version number. Alpine installs headers at +# /alpine/usr/include/c++// (e.g. 14.2.0). +RUN . /etc/skia-env \ + && INCLUDE_DIR=/alpine/usr/include/c++ \ + && if [ -d "${INCLUDE_DIR}" ]; then \ + GCC_VERSION=$(ls ${INCLUDE_DIR}/ | grep -E '^[0-9]' | head -1) ; \ + if [ -n "$GCC_VERSION" ]; then \ + ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current ; \ + echo "Created symlink: ${INCLUDE_DIR}/current -> ${GCC_VERSION}" ; \ + fi ; \ + fi + WORKDIR /work COPY ./startup.sh / diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index e3b29be1a00..570d3e3e8c0 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -101,7 +101,7 @@ RUN . /etc/skia-env \ && APT_REPO=https://deb.debian.org/debian \ && POOL="${APT_REPO}/pool/main" \ && case "${TOOLCHAIN_ARCH_SHORT}" in \ - loong64) PORTS_SUFFIX="-ports" ;; \ + loong64|riscv64) PORTS_SUFFIX="-ports" ;; \ *) PORTS_SUFFIX="" ;; \ esac \ && mkdir -p /tmp/cross-pkgs && cd /tmp/cross-pkgs \ @@ -133,9 +133,23 @@ RUN . /etc/skia-env \ *) \ # Standard debootstrap for architectures with Debian binary archives. # Produces native layout: headers at /usr/include/, libs at /usr/lib/{triple}/ + # NOTE: --foreign only extracts essential/base packages; --include packages + # are downloaded to var/cache/apt/archives/ but NOT extracted. We must + # manually extract them after debootstrap completes. debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ - --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev \ + --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev,libc6-dev \ ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ + && echo "Extracting --include dev packages from cache..." \ + && for deb in \ + ${SYSROOT_DIR}/var/cache/apt/archives/libstdc++-${TOOLCHAIN_VERSION}-dev*.deb \ + ${SYSROOT_DIR}/var/cache/apt/archives/libgcc-${TOOLCHAIN_VERSION}-dev*.deb \ + ${SYSROOT_DIR}/var/cache/apt/archives/linux-libc-dev*.deb \ + ${SYSROOT_DIR}/var/cache/apt/archives/libc6-dev*.deb; do \ + if [ -f "$deb" ]; then \ + echo " Extracting: $(basename $deb)" ; \ + cd /tmp && ar x "$deb" && tar xf data.tar.* -C ${SYSROOT_DIR}/ && rm -f control.tar.* data.tar.* debian-binary ; \ + fi ; \ + done \ && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env ;; \ esac \ && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" @@ -144,15 +158,38 @@ RUN . /etc/skia-env \ # without baking the GCC version number into the build script. # Debootstrap: headers at /sysroot/usr/include/c++/VERSION/ # Cross-packages (riscv64/loongarch64): headers at /sysroot/usr/{ARCH}/include/c++/VERSION/ +# +# Additionally, Debian native packages place arch-specific c++ headers at: +# /usr/include/{TOOLCHAIN_ARCH}/c++/VERSION/ (e.g. /usr/include/arm-linux-gnueabihf/c++/10/) +# But build.cake expects them at: +# /usr/include/c++/VERSION/{TOOLCHAIN_ARCH}/ (the cross-toolchain layout) +# We create a symlink to bridge this gap. RUN . /etc/skia-env \ && TOOLCHAIN_VERSION=$(grep TOOLCHAIN_VERSION /etc/skia-env | cut -d= -f2) \ - && SYSROOT_PATH_VAL=$(grep SYSROOT_PATH /etc/skia-env | cut -d= -f2) \ + && SYSROOT_PATH_VAL=$(grep "SYSROOT_PATH=" /etc/skia-env | cut -d= -f2) \ && INCLUDE_DIR=${SYSROOT_PATH_VAL}/include/c++ \ + && echo "Looking for c++ headers in: ${INCLUDE_DIR}" \ + && echo "TOOLCHAIN_VERSION=${TOOLCHAIN_VERSION}, TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" \ + && if [ -d "${INCLUDE_DIR}" ]; then ls ${INCLUDE_DIR}/ ; fi \ && if [ -d "${INCLUDE_DIR}/${TOOLCHAIN_VERSION}" ]; then \ ln -sf ${TOOLCHAIN_VERSION} ${INCLUDE_DIR}/current ; \ + echo "Created symlink: ${INCLUDE_DIR}/current -> ${TOOLCHAIN_VERSION}" ; \ elif [ -d "${INCLUDE_DIR}" ]; then \ - GCC_VERSION=$(ls ${INCLUDE_DIR}/ | head -1) ; \ - [ -n "$GCC_VERSION" ] && ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current ; \ + GCC_VERSION=$(ls ${INCLUDE_DIR}/ | grep -E '^[0-9]' | head -1) ; \ + if [ -n "$GCC_VERSION" ]; then \ + ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current ; \ + echo "Created symlink: ${INCLUDE_DIR}/current -> ${GCC_VERSION}" ; \ + TOOLCHAIN_VERSION=${GCC_VERSION} ; \ + else \ + echo "ERROR: No version directory found in ${INCLUDE_DIR}" && exit 1 ; \ + fi ; \ + else \ + echo "ERROR: ${INCLUDE_DIR} does not exist!" && exit 1 ; \ + fi \ + && ARCH_INCLUDE=${SYSROOT_PATH_VAL}/include/${TOOLCHAIN_ARCH}/c++/${TOOLCHAIN_VERSION} \ + && if [ -d "${ARCH_INCLUDE}" ] && [ ! -e "${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH}" ]; then \ + ln -sf ${ARCH_INCLUDE} ${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH} ; \ + echo "Created arch symlink: ${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH} -> ${ARCH_INCLUDE}" ; \ fi # Fix libc/libpthread linker scripts to use relative paths. From 7a23655ffd9da7336f64848579d2a57a795b7ad6 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 07:21:06 +0200 Subject: [PATCH 06/45] Fix include path order and cross-package diagnostics Two issues fixed: 1. C++ headers must come before C headers in -I paths for #include_next to work. When does #include_next , clang searches paths AFTER the current file's location. If C headers are listed first, they're skipped by #include_next. 2. Replace grep -oP (Perl regex with \K) with portable grep -o + sed for cross-package URL parsing. Add diagnostic output to identify which packages are found/missing during extraction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux-clang-cross/build.cake | 10 +++++++--- scripts/infra/native/linux/docker/glibc/Dockerfile | 11 ++++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index e836f6e9cce..a85d0f20e4c 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -27,11 +27,15 @@ string GetGnArgs(string arch) var init = $"{sysrootArg} '--target={TOOLCHAIN_ARCH_TARGET}'"; var bin = $"'-B{sysroot}/bin/' "; var libs = $"'-L{sysroot}/lib/', '-L{sysroot}/lib/{TOOLCHAIN_ARCH}/' "; + // C++ headers MUST come before C headers for #include_next to work correctly. + // When does #include_next , the search continues from paths + // AFTER the directory where cmath was found. If C headers are listed first, + // they won't be searched by #include_next from C++ headers. var includes = - $"'-I{sysroot}/include', " + - $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}', " + $"'-I{sysroot}/include/c++/current', " + - $"'-I{sysroot}/include/c++/current/{TOOLCHAIN_ARCH}' "; + $"'-I{sysroot}/include/c++/current/{TOOLCHAIN_ARCH}', " + + $"'-I{sysroot}/include', " + + $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}' "; return $"extra_asmflags+=[ {init}, '-no-integrated-as', {bin}, {includes} ] " + diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 570d3e3e8c0..be48c884cd7 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -113,14 +113,19 @@ RUN . /etc/skia-env \ dir=$(echo $pkg | rev | cut -d/ -f1 | rev) ; \ base=$(echo $pkg | rev | cut -d/ -f2- | rev) ; \ url="${POOL}/${base}/" ; \ - deb_url=$(curl -sL "$url" | grep -oP "href=\"\K${dir}_[^\"]+_all\.deb" | sort -V | tail -1) ; \ + echo "Fetching index: $url (looking for ${dir}_*_all.deb)" ; \ + deb_url=$(curl -sL "$url" | grep -o "href=\"${dir}_[^\"]*_all\.deb\"" | sed 's/href="//;s/"$//' | sort -V | tail -1) ; \ if [ -n "$deb_url" ]; then \ - curl -LO "${url}${deb_url}" ; \ + echo " Downloading: ${url}${deb_url}" ; \ + curl -sL -O "${url}${deb_url}" ; \ ar x "${deb_url}" ; \ mkdir -p extract && tar xf data.tar.* -C extract/ ; \ - rm -f control.tar.* data.tar.* debian-binary ; \ + rm -f control.tar.* data.tar.* debian-binary "${deb_url}" ; \ + else \ + echo " WARNING: No package found for ${dir}" ; \ fi ; \ done \ + && echo "Extracted files:" && ls extract/usr/${TOOLCHAIN_ARCH}/ 2>/dev/null | head -5 || true \ && if [ -d extract/usr/${TOOLCHAIN_ARCH} ]; then \ cp -a extract/usr/${TOOLCHAIN_ARCH}/* ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/ ; \ fi \ From 9e2e57afea63f46ce5d455226c8977f40ae05451 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 07:27:22 +0200 Subject: [PATCH 07/45] Use official LLVM release instead of Azure Linux's stripped package Azure Linux's LLVM is built with only 'host;AMDGPU;BPF' targets, which means it cannot cross-compile to ARM, AArch64, RISC-V, or LoongArch. Replace tdnf clang/lld/llvm with the official LLVM 18.1.8 release from GitHub which includes all target backends. This fixes the 'No available targets are compatible with triple' errors for all non-x86_64 architectures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/alpine/Dockerfile | 19 +++++++++++++++---- .../native/linux/docker/glibc/Dockerfile | 19 +++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 24f5ee6c962..60559e8a58b 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -12,11 +12,10 @@ FROM mcr.microsoft.com/azurelinux/base/core:3.0 # Install host build tools from Azure Linux packages. +# NOTE: We do NOT install clang/lld/llvm from tdnf because the Azure Linux +# LLVM package only includes host+AMDGPU+BPF targets. We need ARM, AArch64, +# RISCV, LoongArch, and X86 backends for cross-compilation. RUN tdnf install -y \ - clang \ - lld \ - llvm \ - compiler-rt \ ninja-build \ python3 \ git \ @@ -26,8 +25,20 @@ RUN tdnf install -y \ gawk \ ca-certificates \ binutils \ + libxml2 \ + ncurses-libs \ + zlib \ + libffi \ && tdnf clean all +# Install official LLVM 18 release with all target backends. +# Azure Linux's LLVM only has "host;AMDGPU;BPF" — we need all cross targets. +ARG LLVM_VERSION=18.1.8 +RUN curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz" \ + | tar xJ --strip-components=1 -C /usr/local \ + && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ + && clang --version && lld --version + # Install the .NET SDK (needed for Cake build scripts) ARG DOTNET_SDK_VERSION=10.0.1xx ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index be48c884cd7..83e56dc2cb1 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -20,11 +20,10 @@ FROM mcr.microsoft.com/azurelinux/base/core:3.0 # Install host build tools from Azure Linux packages. +# NOTE: We do NOT install clang/lld/llvm from tdnf because the Azure Linux +# LLVM package only includes host+AMDGPU+BPF targets. We need ARM, AArch64, +# RISCV, LoongArch, and X86 backends for cross-compilation. RUN tdnf install -y \ - clang \ - lld \ - llvm \ - compiler-rt \ ninja-build \ python3 \ git \ @@ -35,8 +34,20 @@ RUN tdnf install -y \ ca-certificates \ binutils \ debootstrap \ + libxml2 \ + ncurses-libs \ + zlib \ + libffi \ && tdnf clean all +# Install official LLVM 18 release with all target backends. +# Azure Linux's LLVM only has "host;AMDGPU;BPF" — we need all cross targets. +ARG LLVM_VERSION=18.1.8 +RUN curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz" \ + | tar xJ --strip-components=1 -C /usr/local \ + && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ + && clang --version && lld --version + # Install the .NET SDK (needed for Cake build scripts) ARG DOTNET_SDK_VERSION=10.0.1xx ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 From 40d246021debd9e6c4fdff7f29cc4406e941deba Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 07:36:39 +0200 Subject: [PATCH 08/45] Add libtinfo.so.5 compat symlink for LLVM release binary The official LLVM 18.1.8 release (built on Ubuntu 18.04) links against libtinfo.so.5, but Azure Linux 3.0 only ships libtinfo.so.6. The ABI is compatible between ncurses 5 and 6 for our usage (basic terminal info), so a symlink suffices. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/infra/native/linux/docker/alpine/Dockerfile | 6 +++++- scripts/infra/native/linux/docker/glibc/Dockerfile | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 60559e8a58b..d5547443261 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -33,8 +33,12 @@ RUN tdnf install -y \ # Install official LLVM 18 release with all target backends. # Azure Linux's LLVM only has "host;AMDGPU;BPF" — we need all cross targets. +# The release binary needs libtinfo.so.5 (built on Ubuntu 18.04) but Azure Linux +# only has libtinfo.so.6 — create a compat symlink (ABI compatible for our use). ARG LLVM_VERSION=18.1.8 -RUN curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz" \ +RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ + && ln -sf /usr/lib64/libncursesw.so.6 /usr/lib64/libncurses.so.5 \ + && curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz" \ | tar xJ --strip-components=1 -C /usr/local \ && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ && clang --version && lld --version diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 83e56dc2cb1..ea5ebc6dba6 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -42,8 +42,12 @@ RUN tdnf install -y \ # Install official LLVM 18 release with all target backends. # Azure Linux's LLVM only has "host;AMDGPU;BPF" — we need all cross targets. +# The release binary needs libtinfo.so.5 (built on Ubuntu 18.04) but Azure Linux +# only has libtinfo.so.6 — create a compat symlink (ABI compatible for our use). ARG LLVM_VERSION=18.1.8 -RUN curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz" \ +RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ + && ln -sf /usr/lib64/libncursesw.so.6 /usr/lib64/libncurses.so.5 \ + && curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz" \ | tar xJ --strip-components=1 -C /usr/local \ && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ && clang --version && lld --version From 1b1300d31e5bc0fb693a2ed8e6e7b2ce5296f9c0 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 07:45:08 +0200 Subject: [PATCH 09/45] Fix lld version check: use ld.lld instead of lld 'lld' is a generic driver that exits with code 1 when invoked directly (it requires being called as ld.lld, ld64.lld, lld-link, or wasm-ld). Use 'ld.lld --version' for the verification step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/infra/native/linux/docker/alpine/Dockerfile | 2 +- scripts/infra/native/linux/docker/glibc/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index d5547443261..755f8745318 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -41,7 +41,7 @@ RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ && curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz" \ | tar xJ --strip-components=1 -C /usr/local \ && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ - && clang --version && lld --version + && clang --version && ld.lld --version # Install the .NET SDK (needed for Cake build scripts) ARG DOTNET_SDK_VERSION=10.0.1xx diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index ea5ebc6dba6..5e716418dc1 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -50,7 +50,7 @@ RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ && curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz" \ | tar xJ --strip-components=1 -C /usr/local \ && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ - && clang --version && lld --version + && clang --version && ld.lld --version # Install the .NET SDK (needed for Cake build scripts) ARG DOTNET_SDK_VERSION=10.0.1xx From 3fce9e1deab9ae59a677c6714a7fdfd6b302f7cd Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 08:01:26 +0200 Subject: [PATCH 10/45] Fix x86 multiarch: symlink i686-linux-gnu to i386-linux-gnu Debian's debootstrap creates multiarch dirs using 'i386-linux-gnu' but build.cake uses TOOLCHAIN_ARCH='i686-linux-gnu' for the clang target. Create a symlink bridge so include/lib paths resolve correctly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/infra/native/linux/docker/glibc/Dockerfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 5e716418dc1..db7571d0017 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -172,7 +172,14 @@ RUN . /etc/skia-env \ done \ && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env ;; \ esac \ - && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" + && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" \ + && if [ "${TOOLCHAIN_ARCH}" != "${TOOLCHAIN_ARCH_SHORT}-linux-gnu" ] \ + && [ -d "${SYSROOT_DIR}/usr/include/${TOOLCHAIN_ARCH_SHORT}-linux-gnu" ] \ + && [ ! -e "${SYSROOT_DIR}/usr/include/${TOOLCHAIN_ARCH}" ]; then \ + ln -sf ${TOOLCHAIN_ARCH_SHORT}-linux-gnu ${SYSROOT_DIR}/usr/include/${TOOLCHAIN_ARCH} ; \ + ln -sf ${TOOLCHAIN_ARCH_SHORT}-linux-gnu ${SYSROOT_DIR}/usr/lib/${TOOLCHAIN_ARCH} 2>/dev/null || true ; \ + echo "Created multiarch symlink: ${TOOLCHAIN_ARCH} -> ${TOOLCHAIN_ARCH_SHORT}-linux-gnu" ; \ + fi # Create the c++/current symlink so build.cake can reference libstdc++ headers # without baking the GCC version number into the build script. From 7dc70f6d2c7663c6e91c730033ac5ce9167d2f8c Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 08:45:26 +0200 Subject: [PATCH 11/45] fix: add --sysroot and GCC_LIB_DIR for linker CRT discovery The linker was failing with 'cannot open crti.o' and 'cannot open crtbeginS.o' because: 1. No --sysroot was passed for glibc builds, so clang couldn't find CRT files in the debootstrap sysroot 2. No explicit -B/-L path to the GCC lib dir where crtbeginS.o lives Changes: - build.cake: Add SYSROOT_ROOT env var support; when set, passes --sysroot to clang. Add GCC_LIB_DIR env var support; when set, adds -B and -L flags for CRT object and libgcc discovery. - glibc Dockerfile: Export SYSROOT_ROOT=/sysroot and GCC_LIB_DIR for the debootstrap path. Export only GCC_LIB_DIR for the cross-package path (no --sysroot needed since explicit -I/-L covers it). - alpine Dockerfile: Export GCC_LIB_DIR for crtbeginS.o discovery. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux-clang-cross/build.cake | 15 ++++++++++++--- .../infra/native/linux/docker/alpine/Dockerfile | 3 +++ .../infra/native/linux/docker/glibc/Dockerfile | 7 +++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index a85d0f20e4c..25eb1e68a91 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -6,27 +6,36 @@ string TOOLCHAIN_ARCH = Argument("toolchainArch", EnvironmentVariable("TOOLCHAIN string TOOLCHAIN_ARCH_SHORT = Argument("toolchainArchShort", EnvironmentVariable("TOOLCHAIN_ARCH_SHORT")); string TOOLCHAIN_ARCH_TARGET = Argument("toolchainArchTarget", EnvironmentVariable("TOOLCHAIN_ARCH_TARGET")); string SYSROOT_PATH = Argument("sysrootPath", EnvironmentVariable("SYSROOT_PATH")); +string SYSROOT_ROOT = Argument("sysrootRoot", EnvironmentVariable("SYSROOT_ROOT") ?? ""); +string GCC_LIB_DIR = Argument("gccLibDir", EnvironmentVariable("GCC_LIB_DIR") ?? ""); Information("Toolchain:"); Information($" Arch: {TOOLCHAIN_ARCH} ({TOOLCHAIN_ARCH_SHORT})"); Information($" Target {TOOLCHAIN_ARCH_TARGET}"); Information($" Sysroot: {SYSROOT_PATH}"); +Information($" Sysroot Root: {SYSROOT_ROOT}"); +Information($" GCC Lib Dir: {GCC_LIB_DIR}"); if (BUILD_ARCH.Length == 0) BUILD_ARCH = new [] { "arm" }; string GetGnArgs(string arch) { - var (sysrootArg, linker) = BUILD_VARIANT switch + var sysrootArg = BUILD_VARIANT switch { - "alpine" or "alpinenodeps" => ("'--sysroot=/alpine', ", "'-fuse-ld=lld'"), - _ => ("", "'-fuse-ld=lld'"), + "alpine" or "alpinenodeps" => "'--sysroot=/alpine', ", + _ when !string.IsNullOrEmpty(SYSROOT_ROOT) => $"'--sysroot={SYSROOT_ROOT}', ", + _ => "", }; + var linker = "'-fuse-ld=lld'"; var sysroot = SYSROOT_PATH; var init = $"{sysrootArg} '--target={TOOLCHAIN_ARCH_TARGET}'"; var bin = $"'-B{sysroot}/bin/' "; var libs = $"'-L{sysroot}/lib/', '-L{sysroot}/lib/{TOOLCHAIN_ARCH}/' "; + // Add GCC library directory for CRT files (crtbeginS.o) and libgcc + if (!string.IsNullOrEmpty(GCC_LIB_DIR)) + libs += $", '-L{GCC_LIB_DIR}/', '-B{GCC_LIB_DIR}/' "; // C++ headers MUST come before C headers for #include_next to work correctly. // When does #include_next , the search continues from paths // AFTER the directory where cmath was found. If C headers are listed first, diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 755f8745318..7b40ff8d71a 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -108,6 +108,9 @@ RUN . /etc/skia-env \ if [ -n "$GCC_VERSION" ]; then \ ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current ; \ echo "Created symlink: ${INCLUDE_DIR}/current -> ${GCC_VERSION}" ; \ + GCC_LIB_DIR="/alpine/usr/lib/gcc/${TOOLCHAIN_ARCH}/${GCC_VERSION}" ; \ + echo "export GCC_LIB_DIR=${GCC_LIB_DIR}" >> /etc/skia-env ; \ + echo "GCC lib dir: ${GCC_LIB_DIR}" ; \ fi ; \ fi diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index db7571d0017..fc7912c44a2 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -149,7 +149,8 @@ RUN . /etc/skia-env \ cp -a extract/usr/lib/gcc-cross/* ${SYSROOT_DIR}/usr/lib/gcc-cross/ ; \ fi \ && rm -rf /tmp/cross-pkgs \ - && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}" >> /etc/skia-env ;; \ + && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}" >> /etc/skia-env \ + && echo "export GCC_LIB_DIR=${SYSROOT_DIR}/usr/lib/gcc-cross/${TOOLCHAIN_ARCH}/${TOOLCHAIN_VERSION}" >> /etc/skia-env ;; \ *) \ # Standard debootstrap for architectures with Debian binary archives. # Produces native layout: headers at /usr/include/, libs at /usr/lib/{triple}/ @@ -170,7 +171,9 @@ RUN . /etc/skia-env \ cd /tmp && ar x "$deb" && tar xf data.tar.* -C ${SYSROOT_DIR}/ && rm -f control.tar.* data.tar.* debian-binary ; \ fi ; \ done \ - && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env ;; \ + && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env \ + && echo "export SYSROOT_ROOT=${SYSROOT_DIR}" >> /etc/skia-env \ + && echo "export GCC_LIB_DIR=${SYSROOT_DIR}/usr/lib/gcc/${TOOLCHAIN_ARCH}/${TOOLCHAIN_VERSION}" >> /etc/skia-env ;; \ esac \ && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" \ && if [ "${TOOLCHAIN_ARCH}" != "${TOOLCHAIN_ARCH_SHORT}-linux-gnu" ] \ From 6f13e8b3c3e31241cb71876bb37cadea4ab5ecd2 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 10:02:54 +0200 Subject: [PATCH 12/45] fix: resolve remaining linker errors for alpine and cross-packages Alpine: - Add 'gcc' package to apk install (provides crtbeginS.o and libgcc.a which are required for linking; libstdc++-dev only depends on the runtime libgcc package, not the development files) Cross-packages (riscv64/loongarch64): - Add libc6-*-cross package to extraction list (provides runtime libs like ld-linux-*.so.1 and libc.so.6 referenced by linker scripts) - Add -B{sysroot}/lib/ to build.cake (CRT objects like crti.o are found via -B paths, not -L paths; previously only -B for bin/ was set) - Extend linker script sed to also strip bare '/lib/' prefix (handles the dynamic linker path /lib/ld-linux-*.so.1 which lacks the multiarch subdir) - Relax .deb grep pattern to match any arch suffix (not just _all.deb) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux-clang-cross/build.cake | 2 +- scripts/infra/native/linux/docker/alpine/Dockerfile | 2 +- scripts/infra/native/linux/docker/glibc/Dockerfile | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index 25eb1e68a91..1b62ef5597d 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -31,7 +31,7 @@ string GetGnArgs(string arch) var sysroot = SYSROOT_PATH; var init = $"{sysrootArg} '--target={TOOLCHAIN_ARCH_TARGET}'"; - var bin = $"'-B{sysroot}/bin/' "; + var bin = $"'-B{sysroot}/bin/', '-B{sysroot}/lib/' "; var libs = $"'-L{sysroot}/lib/', '-L{sysroot}/lib/{TOOLCHAIN_ARCH}/' "; // Add GCC library directory for CRT files (crtbeginS.o) and libgcc if (!string.IsNullOrEmpty(GCC_LIB_DIR)) diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 7b40ff8d71a..6e74a49ce66 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -95,7 +95,7 @@ RUN . /etc/skia-env \ -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ -U --allow-untrusted --root /alpine --arch "$APK_ARCH" --no-scripts \ - add musl-dev linux-headers fontconfig-dev libstdc++-dev \ + add musl-dev linux-headers fontconfig-dev libstdc++-dev gcc \ && rm -rf "$APK_DIR" # Create c++/current symlink so build.cake can find libstdc++ headers diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index fc7912c44a2..0cdc923b528 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -124,12 +124,13 @@ RUN . /etc/skia-env \ "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libgcc-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-${TOOLCHAIN_ARCH_SHORT}-cross" \ "c/cross-toolchain-base${PORTS_SUFFIX}/linux-libc-dev-${TOOLCHAIN_ARCH_SHORT}-cross"; do \ dir=$(echo $pkg | rev | cut -d/ -f1 | rev) ; \ base=$(echo $pkg | rev | cut -d/ -f2- | rev) ; \ url="${POOL}/${base}/" ; \ - echo "Fetching index: $url (looking for ${dir}_*_all.deb)" ; \ - deb_url=$(curl -sL "$url" | grep -o "href=\"${dir}_[^\"]*_all\.deb\"" | sed 's/href="//;s/"$//' | sort -V | tail -1) ; \ + echo "Fetching index: $url (looking for ${dir}_*.deb)" ; \ + deb_url=$(curl -sL "$url" | grep -o "href=\"${dir}_[^\"]*\.deb\"" | sed 's/href="//;s/"$//' | sort -V | tail -1) ; \ if [ -n "$deb_url" ]; then \ echo " Downloading: ${url}${deb_url}" ; \ curl -sL -O "${url}${deb_url}" ; \ @@ -235,7 +236,7 @@ RUN . /etc/skia-env \ ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so \ ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so.0; do \ if [ -f "$script" ]; then \ - sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib/||g; s|/usr/lib/${TOOLCHAIN_ARCH}/||g; s|/lib/${TOOLCHAIN_ARCH}/||g" "$script" ; \ + sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib/||g; s|/usr/lib/${TOOLCHAIN_ARCH}/||g; s|/lib/${TOOLCHAIN_ARCH}/||g; s|/lib/||g" "$script" ; \ fi ; \ done From 179cf2082406f01a0200eecf8a4fc456d307ec15 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 10:07:44 +0200 Subject: [PATCH 13/45] fix: only apply linker script sed for cross-packages (not debootstrap) For debootstrap builds that pass --sysroot, lld automatically prepends the sysroot prefix to absolute paths in linker scripts. Converting them to relative paths would break resolution since the files aren't in the -L search paths. For cross-package builds (riscv64/loongarch64) that don't use --sysroot, the sed is still needed to make paths relative so lld can find them via -L flags. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/glibc/Dockerfile | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 0cdc923b528..6f62d893eca 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -224,21 +224,23 @@ RUN . /etc/skia-env \ fi # Fix libc/libpthread linker scripts to use relative paths. -# Debian's linker scripts contain absolute paths like: -# GROUP ( /usr/lib/x86_64-linux-gnu/libc.so.6 ... ) -# These must be relative for cross-compilation with --sysroot to work. +# Only needed for cross-package builds (riscv64/loongarch64) which don't use --sysroot. +# For debootstrap builds, --sysroot causes lld to automatically prepend the sysroot +# to absolute paths in linker scripts, so no modification is needed. RUN . /etc/skia-env \ - && for script in \ - ${SYSROOT_DIR}/usr/lib/${TOOLCHAIN_ARCH}/libc.so \ - ${SYSROOT_DIR}/usr/lib/${TOOLCHAIN_ARCH}/libpthread.so \ - ${SYSROOT_DIR}/usr/lib/${TOOLCHAIN_ARCH}/libpthread.so.0 \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libc.so \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so.0; do \ - if [ -f "$script" ]; then \ - sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib/||g; s|/usr/lib/${TOOLCHAIN_ARCH}/||g; s|/lib/${TOOLCHAIN_ARCH}/||g; s|/lib/||g" "$script" ; \ - fi ; \ - done + && if [ -z "$(grep 'SYSROOT_ROOT=' /etc/skia-env | cut -d= -f2)" ]; then \ + for script in \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libc.so \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so.0; do \ + if [ -f "$script" ]; then \ + sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib/||g; s|/lib/${TOOLCHAIN_ARCH}/||g; s|/lib/||g" "$script" ; \ + echo "Fixed linker script: $script" ; \ + fi ; \ + done ; \ + else \ + echo "Skipping linker script fix (--sysroot handles absolute paths)" ; \ + fi # Install fontconfig into the sysroot. # Download .deb packages from Debian's pool and extract headers + libraries. From 82a9c2d4a65ebb02dc35088f25041ca6eee3069b Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 11:13:42 +0200 Subject: [PATCH 14/45] fix: add lib64 paths and MACHINE_ARCH for local testing - Add lib64/ to -B and -L paths in build.cake for loongarch64 support - Add lib64/ linker script paths to sed fix in glibc Dockerfile - Add MACHINE_ARCH build arg to glibc/alpine Dockerfiles for ARM Mac local testing - LLVM binary download now selects correct arch (x86_64 vs aarch64) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux-clang-cross/build.cake | 4 ++-- .../infra/native/linux/docker/alpine/Dockerfile | 8 +++++++- scripts/infra/native/linux/docker/glibc/Dockerfile | 14 +++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index 1b62ef5597d..728585b9bb8 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -31,8 +31,8 @@ string GetGnArgs(string arch) var sysroot = SYSROOT_PATH; var init = $"{sysrootArg} '--target={TOOLCHAIN_ARCH_TARGET}'"; - var bin = $"'-B{sysroot}/bin/', '-B{sysroot}/lib/' "; - var libs = $"'-L{sysroot}/lib/', '-L{sysroot}/lib/{TOOLCHAIN_ARCH}/' "; + var bin = $"'-B{sysroot}/bin/', '-B{sysroot}/lib/', '-B{sysroot}/lib64/' "; + var libs = $"'-L{sysroot}/lib/', '-L{sysroot}/lib64/', '-L{sysroot}/lib/{TOOLCHAIN_ARCH}/' "; // Add GCC library directory for CRT files (crtbeginS.o) and libgcc if (!string.IsNullOrEmpty(GCC_LIB_DIR)) libs += $", '-L{GCC_LIB_DIR}/', '-B{GCC_LIB_DIR}/' "; diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 6e74a49ce66..86a21c35d4d 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -36,9 +36,15 @@ RUN tdnf install -y \ # The release binary needs libtinfo.so.5 (built on Ubuntu 18.04) but Azure Linux # only has libtinfo.so.6 — create a compat symlink (ABI compatible for our use). ARG LLVM_VERSION=18.1.8 +ARG MACHINE_ARCH=x86_64 RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ && ln -sf /usr/lib64/libncursesw.so.6 /usr/lib64/libncurses.so.5 \ - && curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz" \ + && case "${MACHINE_ARCH}" in \ + x86_64) LLVM_SUFFIX="x86_64-linux-gnu-ubuntu-18.04" ;; \ + aarch64) LLVM_SUFFIX="aarch64-linux-gnu" ;; \ + *) echo "Unsupported MACHINE_ARCH: ${MACHINE_ARCH}" && exit 1 ;; \ + esac \ + && curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${LLVM_SUFFIX}.tar.xz" \ | tar xJ --strip-components=1 -C /usr/local \ && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ && clang --version && ld.lld --version diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 6f62d893eca..b51821df010 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -45,9 +45,15 @@ RUN tdnf install -y \ # The release binary needs libtinfo.so.5 (built on Ubuntu 18.04) but Azure Linux # only has libtinfo.so.6 — create a compat symlink (ABI compatible for our use). ARG LLVM_VERSION=18.1.8 +ARG MACHINE_ARCH=x86_64 RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ && ln -sf /usr/lib64/libncursesw.so.6 /usr/lib64/libncurses.so.5 \ - && curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz" \ + && case "${MACHINE_ARCH}" in \ + x86_64) LLVM_SUFFIX="x86_64-linux-gnu-ubuntu-18.04" ;; \ + aarch64) LLVM_SUFFIX="aarch64-linux-gnu" ;; \ + *) echo "Unsupported MACHINE_ARCH: ${MACHINE_ARCH}" && exit 1 ;; \ + esac \ + && curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${LLVM_SUFFIX}.tar.xz" \ | tar xJ --strip-components=1 -C /usr/local \ && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ && clang --version && ld.lld --version @@ -232,9 +238,11 @@ RUN . /etc/skia-env \ for script in \ ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libc.so \ ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so.0; do \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so.0 \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib64/libc.so \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib64/libpthread.so; do \ if [ -f "$script" ]; then \ - sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib/||g; s|/lib/${TOOLCHAIN_ARCH}/||g; s|/lib/||g" "$script" ; \ + sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib64/||g; s|/usr/${TOOLCHAIN_ARCH}/lib/||g; s|/lib/${TOOLCHAIN_ARCH}/||g; s|/lib64/||g; s|/lib/||g" "$script" ; \ echo "Fixed linker script: $script" ; \ fi ; \ done ; \ From 52999f8353e6f4c78fedbd04060d464c1cfa996a Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 14:15:49 +0200 Subject: [PATCH 15/45] fix: add shared runtime libs and clean up x86 triple handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add libstdc++6 and libgcc-s1 runtime packages for cross-package builds (riscv64/loongarch64). Without these, lld falls back to static libstdc++.a which has incompatible .eh_frame sections with lld 18. - Change x86 TOOLCHAIN_ARCH from i686-linux-gnu to i386-linux-gnu to match Debian's multiarch directory naming. This eliminates the need for the i686→i386 symlink workaround entirely. - Use find-based GCC_LIB_DIR discovery (maxdepth 3) for debootstrap builds, since GCC uses i686-linux-gnu internally while multiarch uses i386-linux-gnu. - Remove dead multiarch symlink logic (no longer needed). - Add arch compat symlink for C++ headers when GCC's arch dir name differs from TOOLCHAIN_ARCH. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/glibc/Dockerfile | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index b51821df010..59c4d5441a4 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -77,7 +77,7 @@ RUN case ${BUILD_ARCH} in \ arm) TOOLCHAIN_ARCH=arm-linux-gnueabihf ; TOOLCHAIN_ARCH_SHORT=armhf ; TOOLCHAIN_ARCH_TARGET=armv7a-linux-gnueabihf ;; \ arm64) TOOLCHAIN_ARCH=aarch64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=arm64 ; TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu ;; \ riscv64) TOOLCHAIN_ARCH=riscv64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=riscv64 ; TOOLCHAIN_ARCH_TARGET=riscv64-linux-gnu ;; \ - x86) TOOLCHAIN_ARCH=i686-linux-gnu ; TOOLCHAIN_ARCH_SHORT=i386 ; TOOLCHAIN_ARCH_TARGET=i686-linux-gnu ;; \ + x86) TOOLCHAIN_ARCH=i386-linux-gnu ; TOOLCHAIN_ARCH_SHORT=i386 ; TOOLCHAIN_ARCH_TARGET=i686-linux-gnu ;; \ x64) TOOLCHAIN_ARCH=x86_64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=amd64 ; TOOLCHAIN_ARCH_TARGET=x86_64-linux-gnu ;; \ loongarch64) TOOLCHAIN_ARCH=loongarch64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=loong64 ; TOOLCHAIN_ARCH_TARGET=loongarch64-linux-gnu ;; \ *) echo "Unsupported architecture: ${BUILD_ARCH}" && exit 1 ;; \ @@ -128,7 +128,9 @@ RUN . /etc/skia-env \ && mkdir -p /tmp/cross-pkgs && cd /tmp/cross-pkgs \ && for pkg in \ "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++6-${TOOLCHAIN_ARCH_SHORT}-cross" \ "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libgcc-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libgcc-s1-${TOOLCHAIN_ARCH_SHORT}-cross" \ "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-${TOOLCHAIN_ARCH_SHORT}-cross" \ "c/cross-toolchain-base${PORTS_SUFFIX}/linux-libc-dev-${TOOLCHAIN_ARCH_SHORT}-cross"; do \ @@ -180,16 +182,10 @@ RUN . /etc/skia-env \ done \ && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env \ && echo "export SYSROOT_ROOT=${SYSROOT_DIR}" >> /etc/skia-env \ - && echo "export GCC_LIB_DIR=${SYSROOT_DIR}/usr/lib/gcc/${TOOLCHAIN_ARCH}/${TOOLCHAIN_VERSION}" >> /etc/skia-env ;; \ + && GCC_DIR=$(find ${SYSROOT_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ + && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env ;; \ esac \ - && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" \ - && if [ "${TOOLCHAIN_ARCH}" != "${TOOLCHAIN_ARCH_SHORT}-linux-gnu" ] \ - && [ -d "${SYSROOT_DIR}/usr/include/${TOOLCHAIN_ARCH_SHORT}-linux-gnu" ] \ - && [ ! -e "${SYSROOT_DIR}/usr/include/${TOOLCHAIN_ARCH}" ]; then \ - ln -sf ${TOOLCHAIN_ARCH_SHORT}-linux-gnu ${SYSROOT_DIR}/usr/include/${TOOLCHAIN_ARCH} ; \ - ln -sf ${TOOLCHAIN_ARCH_SHORT}-linux-gnu ${SYSROOT_DIR}/usr/lib/${TOOLCHAIN_ARCH} 2>/dev/null || true ; \ - echo "Created multiarch symlink: ${TOOLCHAIN_ARCH} -> ${TOOLCHAIN_ARCH_SHORT}-linux-gnu" ; \ - fi + && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" # Create the c++/current symlink so build.cake can reference libstdc++ headers # without baking the GCC version number into the build script. @@ -227,6 +223,13 @@ RUN . /etc/skia-env \ && if [ -d "${ARCH_INCLUDE}" ] && [ ! -e "${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH}" ]; then \ ln -sf ${ARCH_INCLUDE} ${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH} ; \ echo "Created arch symlink: ${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH} -> ${ARCH_INCLUDE}" ; \ + fi \ + && if [ ! -e "${INCLUDE_DIR}/current/${TOOLCHAIN_ARCH}" ] && [ -d "${INCLUDE_DIR}/current/" ]; then \ + EXISTING_ARCH=$(ls -d ${INCLUDE_DIR}/current/*-linux-gnu* 2>/dev/null | head -1 | xargs basename 2>/dev/null) ; \ + if [ -n "${EXISTING_ARCH}" ] && [ "${EXISTING_ARCH}" != "${TOOLCHAIN_ARCH}" ]; then \ + ln -sf ${EXISTING_ARCH} ${INCLUDE_DIR}/current/${TOOLCHAIN_ARCH} ; \ + echo "Created arch compat symlink: ${TOOLCHAIN_ARCH} -> ${EXISTING_ARCH}" ; \ + fi ; \ fi # Fix libc/libpthread linker scripts to use relative paths. From 6c9ae4d04521961267382e795cb2ab8f790d2ec0 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 15:37:53 +0200 Subject: [PATCH 16/45] fix(riscv64): move to Trixie sysroot to resolve lld 18 incompatibility GCC 10's static libstdc++.a (Bullseye) has .eh_frame sections that lld 18 rejects with 'relocation refers to a symbol in a discarded section' errors. GCC 14 (Trixie) produces lld-compatible archives. Changes: - Move riscv64 from Bullseye to Trixie in CI matrix - Remove riscv64 from cross-package extraction path (now uses debootstrap) - Add unconditional merged-usr symlink pre-creation for debootstrap (required for Trixie, harmless for Bullseye since tar follows symlinks) - Simplify PORTS_SUFFIX (only loongarch64 uses cross-packages now) - Remove riscv64-specific fontconfig version override Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../azure-templates-stages-native-linux.yml | 10 ++--- .../native/linux/docker/glibc/Dockerfile | 37 ++++++++++--------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/scripts/azure-templates-stages-native-linux.yml b/scripts/azure-templates-stages-native-linux.yml index ff8d6734a5c..e77a2e18774 100644 --- a/scripts/azure-templates-stages-native-linux.yml +++ b/scripts/azure-templates-stages-native-linux.yml @@ -28,17 +28,17 @@ stages: gnArgs: skia_use_fontconfig=false matrix: # Azure Linux + Trixie sysroot (gcc-14, glibc 2.38) - for archs that - # never had release-arch packages in Bullseye OR Bookworm: - # - loongarch64 (LoongArch port; not in Bullseye/Bookworm main, - # first release arch in Trixie) - - ${{ each arch in split('loongarch64', ',') }}: + # need GCC 14 (lld 18 is incompatible with GCC 10's static libstdc++.a + # on riscv64 due to .eh_frame discarded-section errors), or never had + # release-arch packages in Bullseye/Bookworm (loongarch64). + - ${{ each arch in split('loongarch64,riscv64', ',') }}: - arch: ${{ arch }} docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross additionalArgs: --verifyGlibcMax=2.38 dockerArgs: --build-arg SYSROOT_RELEASE=trixie # Azure Linux + Bullseye sysroot (gcc-10, glibc 2.31). - - ${{ each arch in split('arm,arm64,x86,x64,riscv64', ',') }}: + - ${{ each arch in split('arm,arm64,x86,x64', ',') }}: - arch: ${{ arch }} docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 59c4d5441a4..50141b34a19 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -107,24 +107,21 @@ RUN case ${SYSROOT_RELEASE} in \ # --variant=minbase: Only essential packages (dpkg, apt, coreutils). # --include: Add the gcc cross-compilation development packages we need. # -# NOTE: riscv64 is not a release arch in Bullseye — it's available from the -# debian-ports archive. For riscv64+bullseye, we fall back to extracting -# gcc-10-cross .deb packages (same approach as the old debian/11 Dockerfile). +# NOTE: loongarch64 is not a release arch in any current Debian version — use +# gcc-cross-ports package extraction. riscv64 became a release arch in Trixie +# so it uses debootstrap (below) when built with Trixie. ARG SYSROOT_DIR=/sysroot RUN . /etc/skia-env \ && case "${BUILD_ARCH}" in \ - riscv64|loongarch64) \ - # riscv64/loongarch64 have no standard Debian binary archive — use gcc-cross package extraction. + loongarch64) \ + # loongarch64 has no standard Debian binary archive (even in Trixie) — + # use gcc-cross package extraction. # This produces the cross-package layout at /sysroot/usr/{TOOLCHAIN_ARCH}/ - # loong64 packages are in -ports variants; riscv64 is in the standard pool. mkdir -p ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH} \ && TOOLCHAIN_VERSION=$(grep TOOLCHAIN_VERSION /etc/skia-env | cut -d= -f2) \ && APT_REPO=https://deb.debian.org/debian \ && POOL="${APT_REPO}/pool/main" \ - && case "${TOOLCHAIN_ARCH_SHORT}" in \ - loong64|riscv64) PORTS_SUFFIX="-ports" ;; \ - *) PORTS_SUFFIX="" ;; \ - esac \ + && PORTS_SUFFIX="-ports" \ && mkdir -p /tmp/cross-pkgs && cd /tmp/cross-pkgs \ && for pkg in \ "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ @@ -166,7 +163,14 @@ RUN . /etc/skia-env \ # NOTE: --foreign only extracts essential/base packages; --include packages # are downloaded to var/cache/apt/archives/ but NOT extracted. We must # manually extract them after debootstrap completes. - debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ + # Pre-create merged-usr layout (/bin→usr/bin etc). Required for Trixie + # (base-files expects symlinks). Harmless for Bullseye (tar follows symlinks, + # so extractions to /lib/ end up in /usr/lib/ which is the same location). + mkdir -p ${SYSROOT_DIR}/usr/bin ${SYSROOT_DIR}/usr/lib ${SYSROOT_DIR}/usr/sbin \ + && ln -sf usr/bin ${SYSROOT_DIR}/bin \ + && ln -sf usr/lib ${SYSROOT_DIR}/lib \ + && ln -sf usr/sbin ${SYSROOT_DIR}/sbin \ + && debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev,libc6-dev \ ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ && echo "Extracting --include dev packages from cache..." \ @@ -190,7 +194,7 @@ RUN . /etc/skia-env \ # Create the c++/current symlink so build.cake can reference libstdc++ headers # without baking the GCC version number into the build script. # Debootstrap: headers at /sysroot/usr/include/c++/VERSION/ -# Cross-packages (riscv64/loongarch64): headers at /sysroot/usr/{ARCH}/include/c++/VERSION/ +# Cross-packages (loongarch64): headers at /sysroot/usr/{ARCH}/include/c++/VERSION/ # # Additionally, Debian native packages place arch-specific c++ headers at: # /usr/include/{TOOLCHAIN_ARCH}/c++/VERSION/ (e.g. /usr/include/arm-linux-gnueabihf/c++/10/) @@ -233,7 +237,7 @@ RUN . /etc/skia-env \ fi # Fix libc/libpthread linker scripts to use relative paths. -# Only needed for cross-package builds (riscv64/loongarch64) which don't use --sysroot. +# Only needed for cross-package builds (loongarch64) which don't use --sysroot. # For debootstrap builds, --sysroot causes lld to automatically prepend the sysroot # to absolute paths in linker scripts, so no modification is needed. RUN . /etc/skia-env \ @@ -255,7 +259,7 @@ RUN . /etc/skia-env \ # Install fontconfig into the sysroot. # Download .deb packages from Debian's pool and extract headers + libraries. -# For cross-package arches (riscv64/loongarch64), relocate to match the SYSROOT_PATH layout. +# For cross-package arches (loongarch64), relocate to match the SYSROOT_PATH layout. RUN . /etc/skia-env \ && SYSROOT_PATH_VAL=$(grep "SYSROOT_PATH=" /etc/skia-env | cut -d= -f2) \ && mkdir -p /tmp/fontconfig \ @@ -264,9 +268,6 @@ RUN . /etc/skia-env \ bullseye) FC_VERSION=2.13.1-4.2 ;; \ trixie) FC_VERSION=2.17.1-5 ;; \ esac \ - && case "${TOOLCHAIN_ARCH_SHORT}" in \ - riscv64) FC_VERSION=2.15.0-2.3 ;; \ - esac \ && APT_REPO=https://deb.debian.org/debian \ && mkdir -p /tmp/fontconfig/extract \ && for pkg in libfontconfig-dev libfontconfig1; do \ @@ -278,7 +279,7 @@ RUN . /etc/skia-env \ rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ done \ && case "${BUILD_ARCH}" in \ - riscv64|loongarch64) \ + loongarch64) \ # Cross-package layout: put headers and libs under SYSROOT_PATH mkdir -p ${SYSROOT_PATH_VAL}/include ${SYSROOT_PATH_VAL}/lib ; \ cp -a /tmp/fontconfig/extract/usr/include/* ${SYSROOT_PATH_VAL}/include/ ; \ From d1908146a938303487826912882646ac8b6f4146 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 17:26:30 +0200 Subject: [PATCH 17/45] fix(glibc): handle Trixie debootstrap merged-usr exit code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit debootstrap returns exit 2 on Trixie because base-files tries to create merged-usr symlinks (bin→usr/bin) but debootstrap's setup already created /bin/ as a directory. This is harmless — all packages are still downloaded to the cache. Changes: - Tolerate debootstrap non-zero exit with cache verification - Extract ALL cached .debs (not just -dev packages) so libc6, libgcc-s1 etc. are available for the linker (needed for shared lib references in linker scripts like libc.so and libgcc_s.so) - Suppress tar stderr during extraction (benign symlink conflicts) Verified locally: - riscv64+Trixie: Docker build ✅, C++ static-libstdc++ link ✅ - arm64+Bullseye: Docker build ✅, C++ static-libstdc++ link ✅ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/glibc/Dockerfile | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 50141b34a19..0552d36722c 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -163,25 +163,22 @@ RUN . /etc/skia-env \ # NOTE: --foreign only extracts essential/base packages; --include packages # are downloaded to var/cache/apt/archives/ but NOT extracted. We must # manually extract them after debootstrap completes. - # Pre-create merged-usr layout (/bin→usr/bin etc). Required for Trixie - # (base-files expects symlinks). Harmless for Bullseye (tar follows symlinks, - # so extractions to /lib/ end up in /usr/lib/ which is the same location). - mkdir -p ${SYSROOT_DIR}/usr/bin ${SYSROOT_DIR}/usr/lib ${SYSROOT_DIR}/usr/sbin \ - && ln -sf usr/bin ${SYSROOT_DIR}/bin \ - && ln -sf usr/lib ${SYSROOT_DIR}/lib \ - && ln -sf usr/sbin ${SYSROOT_DIR}/sbin \ - && debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ + # NOTE: Trixie's base-files creates merged-usr symlinks (bin→usr/bin etc.) + # but debootstrap pre-creates /bin/ as a directory, causing tar to fail with + # "Cannot create symlink: File exists" (exit 2). This is harmless — debootstrap + # still downloads all packages to the cache and creates the symlinks afterward. + # We tolerate this known exit code and verify the cache was populated. + debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev,libc6-dev \ ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ - && echo "Extracting --include dev packages from cache..." \ - && for deb in \ - ${SYSROOT_DIR}/var/cache/apt/archives/libstdc++-${TOOLCHAIN_VERSION}-dev*.deb \ - ${SYSROOT_DIR}/var/cache/apt/archives/libgcc-${TOOLCHAIN_VERSION}-dev*.deb \ - ${SYSROOT_DIR}/var/cache/apt/archives/linux-libc-dev*.deb \ - ${SYSROOT_DIR}/var/cache/apt/archives/libc6-dev*.deb; do \ + || { echo "debootstrap exited $? (known issue with Trixie merged-usr; checking cache...)" ; \ + ls ${SYSROOT_DIR}/var/cache/apt/archives/libstdc++-${TOOLCHAIN_VERSION}-dev*.deb > /dev/null 2>&1 \ + || { echo "FATAL: debootstrap cache not populated" && exit 1 ; } ; } \ + && echo "Extracting all cached packages into sysroot..." \ + && for deb in ${SYSROOT_DIR}/var/cache/apt/archives/*.deb; do \ if [ -f "$deb" ]; then \ echo " Extracting: $(basename $deb)" ; \ - cd /tmp && ar x "$deb" && tar xf data.tar.* -C ${SYSROOT_DIR}/ && rm -f control.tar.* data.tar.* debian-binary ; \ + cd /tmp && ar x "$deb" && tar xf data.tar.* -C ${SYSROOT_DIR}/ 2>/dev/null ; rm -f control.tar.* data.tar.* debian-binary ; \ fi ; \ done \ && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env \ From 7648372c2b321259521a4778bf2a8ff0e5b5fc79 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 18:54:49 +0200 Subject: [PATCH 18/45] security: harden all Dockerfiles with SHA256 verification and strict error handling Addresses all 15 findings from deep security review: CRITICAL: - Add SHA-256 verification for LLVM 18.1.8 (x86_64 + aarch64) - Add SHA-256 verification for NDK r26b - Add SHA-256 verification for apk.static v2.12.14 (both arches) - Remove --allow-untrusted from Alpine (use proper signing keys) - Tighten debootstrap validation (check ALL required packages) - Make loongarch64 fail hard on missing packages HIGH: - Add SHELL [/bin/bash, -euo, pipefail, -c] to all Dockerfiles - Support aarch64 build agents in bionic (MACHINE_ARCH arg) - Pin apk.static to exact version with checksum - Replace 2>/dev/null with filtered stderr for tar extraction MEDIUM: - Pin .NET SDK to exact version 10.0.100 (matches global.json) - Replace fragile grep|cut with '. /etc/skia-env' for env reading - Fix GCC version sort (sort -V | tail -1 instead of head -1) - Add llvm-ar --version smoke test after LLVM install LOW: - Add libpthread.a guard for NDK (fail if unexpectedly present) - Change Alpine repos from http to https Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/alpine/Dockerfile | 66 +++++++++----- .../native/linux/docker/bionic/Dockerfile | 39 ++++++--- .../native/linux/docker/glibc/Dockerfile | 85 ++++++++++++------- 3 files changed, 128 insertions(+), 62 deletions(-) diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 86a21c35d4d..120a3f31e8e 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -11,6 +11,9 @@ FROM mcr.microsoft.com/azurelinux/base/core:3.0 +# Use bash with strict error handling for all RUN commands. +SHELL ["/bin/bash", "-euo", "pipefail", "-c"] + # Install host build tools from Azure Linux packages. # NOTE: We do NOT install clang/lld/llvm from tdnf because the Azure Linux # LLVM package only includes host+AMDGPU+BPF targets. We need ARM, AArch64, @@ -32,28 +35,31 @@ RUN tdnf install -y \ && tdnf clean all # Install official LLVM 18 release with all target backends. -# Azure Linux's LLVM only has "host;AMDGPU;BPF" — we need all cross targets. -# The release binary needs libtinfo.so.5 (built on Ubuntu 18.04) but Azure Linux -# only has libtinfo.so.6 — create a compat symlink (ABI compatible for our use). +# SHA-256 checksums computed from official GitHub releases (llvm/llvm-project). ARG LLVM_VERSION=18.1.8 ARG MACHINE_ARCH=x86_64 +ARG LLVM_SHA256_X86_64=54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36 +ARG LLVM_SHA256_AARCH64=dcaa1bebbfbb86953fdfbdc7f938800229f75ad26c5c9375ef242edad737d999 RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ && ln -sf /usr/lib64/libncursesw.so.6 /usr/lib64/libncurses.so.5 \ && case "${MACHINE_ARCH}" in \ - x86_64) LLVM_SUFFIX="x86_64-linux-gnu-ubuntu-18.04" ;; \ - aarch64) LLVM_SUFFIX="aarch64-linux-gnu" ;; \ + x86_64) LLVM_SUFFIX="x86_64-linux-gnu-ubuntu-18.04" ; LLVM_SHA256="${LLVM_SHA256_X86_64}" ;; \ + aarch64) LLVM_SUFFIX="aarch64-linux-gnu" ; LLVM_SHA256="${LLVM_SHA256_AARCH64}" ;; \ *) echo "Unsupported MACHINE_ARCH: ${MACHINE_ARCH}" && exit 1 ;; \ esac \ - && curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${LLVM_SUFFIX}.tar.xz" \ - | tar xJ --strip-components=1 -C /usr/local \ + && curl -fSL -o /tmp/llvm.tar.xz \ + "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${LLVM_SUFFIX}.tar.xz" \ + && echo "${LLVM_SHA256} /tmp/llvm.tar.xz" | sha256sum -c - \ + && tar xJf /tmp/llvm.tar.xz --strip-components=1 -C /usr/local \ + && rm -f /tmp/llvm.tar.xz \ && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ - && clang --version && ld.lld --version + && clang --version && ld.lld --version && llvm-ar --version -# Install the .NET SDK (needed for Cake build scripts) -ARG DOTNET_SDK_VERSION=10.0.1xx +# Install the .NET SDK (needed for Cake build scripts). +ARG DOTNET_SDK_VERSION=10.0.100 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -RUN curl https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -L -o dotnet-install.sh \ - && bash dotnet-install.sh --channel ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ +RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ + && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ && rm dotnet-install.sh \ && dotnet help \ @@ -85,22 +91,34 @@ RUN case ${BUILD_ARCH} in \ # fontconfig-dev: font configuration headers and library # libstdc++-dev: C++ standard library headers (needed by Skia) # Avoids build-base which pulls in busybox (~35 CVEs) and binutils (~15 CVEs). +# SHA-256 of apk.static pinned for supply-chain integrity. ARG DISTRO_VERSION=3.21 -ARG MACHINE_ARCH=x86_64 +ARG APK_STATIC_SHA256_X86_64=cd282c693059c39b68ce0575fbdc4e0c162af85ed633a7834f9cfd01b8b1d58b +ARG APK_STATIC_SHA256_AARCH64=77ebdbb193aaa605e10610586d443909331872799ac78648dff1216822d76299 RUN . /etc/skia-env \ && APK_DIR="$(mktemp -d)" \ - && curl -SLO --fail --retry 5 --retry-delay 10 --retry-all-errors \ - --create-dirs --output-dir "$APK_DIR" \ + && case "${MACHINE_ARCH}" in \ + x86_64) APK_STATIC_SHA256="${APK_STATIC_SHA256_X86_64}" ;; \ + aarch64) APK_STATIC_SHA256="${APK_STATIC_SHA256_AARCH64}" ;; \ + esac \ + && curl -fSL --retry 5 --retry-delay 10 --retry-all-errors \ + -o "$APK_DIR/apk.static" \ "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.12.14/$MACHINE_ARCH/apk.static" \ + && echo "${APK_STATIC_SHA256} $APK_DIR/apk.static" | sha256sum -c - \ && chmod +x "$APK_DIR/apk.static" \ + && mkdir -p /alpine/etc/apk/keys \ + && curl -fsSL -o /alpine/etc/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub \ + "https://alpinelinux.org/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub" \ + && curl -fsSL -o /alpine/etc/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub \ + "https://alpinelinux.org/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub" \ && "$APK_DIR/apk.static" \ - -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ - -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ - -U --allow-untrusted --root /alpine --arch "$APK_ARCH" --initdb add \ + -X "https://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ + -X "https://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ + -U --keys-dir /alpine/etc/apk/keys --root /alpine --arch "$APK_ARCH" --initdb add \ && "$APK_DIR/apk.static" \ - -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ - -X "http://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ - -U --allow-untrusted --root /alpine --arch "$APK_ARCH" --no-scripts \ + -X "https://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ + -X "https://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ + -U --keys-dir /alpine/etc/apk/keys --root /alpine --arch "$APK_ARCH" --no-scripts \ add musl-dev linux-headers fontconfig-dev libstdc++-dev gcc \ && rm -rf "$APK_DIR" @@ -110,14 +128,18 @@ RUN . /etc/skia-env \ RUN . /etc/skia-env \ && INCLUDE_DIR=/alpine/usr/include/c++ \ && if [ -d "${INCLUDE_DIR}" ]; then \ - GCC_VERSION=$(ls ${INCLUDE_DIR}/ | grep -E '^[0-9]' | head -1) ; \ + GCC_VERSION=$(ls ${INCLUDE_DIR}/ | grep -E '^[0-9]' | sort -V | tail -1) ; \ if [ -n "$GCC_VERSION" ]; then \ ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current ; \ echo "Created symlink: ${INCLUDE_DIR}/current -> ${GCC_VERSION}" ; \ GCC_LIB_DIR="/alpine/usr/lib/gcc/${TOOLCHAIN_ARCH}/${GCC_VERSION}" ; \ echo "export GCC_LIB_DIR=${GCC_LIB_DIR}" >> /etc/skia-env ; \ echo "GCC lib dir: ${GCC_LIB_DIR}" ; \ + else \ + echo "ERROR: No GCC version directory found in ${INCLUDE_DIR}" && exit 1 ; \ fi ; \ + else \ + echo "ERROR: ${INCLUDE_DIR} does not exist!" && exit 1 ; \ fi WORKDIR /work diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index b90ee72b01c..9f23bb06c99 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -8,6 +8,9 @@ FROM mcr.microsoft.com/azurelinux/base/core:3.0 +# Use bash with strict error handling for all RUN commands. +SHELL ["/bin/bash", "-euo", "pipefail", "-c"] + # Install host build tools from Azure Linux packages. # Bionic doesn't need clang/lld from host — NDK provides its own. # But we need python3, git, ninja for Skia's GN build system and unzip for NDK. @@ -24,11 +27,11 @@ RUN tdnf install -y \ binutils \ && tdnf clean all -# Install the .NET SDK (needed for Cake build scripts) -ARG DOTNET_SDK_VERSION=10.0.1xx +# Install the .NET SDK (needed for Cake build scripts). +ARG DOTNET_SDK_VERSION=10.0.100 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -RUN curl https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -L -o dotnet-install.sh \ - && bash dotnet-install.sh --channel ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ +RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ + && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ && rm dotnet-install.sh \ && dotnet help \ @@ -45,19 +48,35 @@ RUN case ${BUILD_ARCH} in \ && echo "export ANDROID_TRIPLE=${ANDROID_TRIPLE}" > /etc/skia-env \ && echo "export ANDROID_SYSROOT_LIB=${ANDROID_SYSROOT_LIB}" >> /etc/skia-env -# Install Android NDK r26b -# tdnf provides unzip; curl is already in base image. -RUN tdnf install -y unzip && tdnf clean all \ - && curl -SL https://dl.google.com/android/repository/android-ndk-r26b-linux.zip -o /tmp/ndk.zip \ +# Install Android NDK r26b with SHA-256 verification. +ARG NDK_VERSION=r26b +ARG NDK_SHA256=ad73c0370f0b0a87d1671ed2fd5a9ac9acfd1eb5c43a7fbfbd330f85d19dd632 +ARG MACHINE_ARCH=x86_64 +RUN curl -fSL -o /tmp/ndk.zip \ + "https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux.zip" \ + && echo "${NDK_SHA256} /tmp/ndk.zip" | sha256sum -c - \ && unzip -q /tmp/ndk.zip -d /opt \ && rm /tmp/ndk.zip ENV ANDROID_NDK_HOME=/opt/android-ndk-r26b -ENV PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin + +# Determine NDK host platform (supports x86_64 and aarch64 build agents). +RUN case "${MACHINE_ARCH}" in \ + x86_64) NDK_HOST_TAG="linux-x86_64" ;; \ + aarch64) NDK_HOST_TAG="linux-aarch64" ;; \ + *) echo "Unsupported MACHINE_ARCH for NDK: ${MACHINE_ARCH}" && exit 1 ;; \ + esac \ + && echo "export NDK_HOST_TAG=${NDK_HOST_TAG}" >> /etc/skia-env +ENV PATH=$PATH:/opt/android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin # Create empty libpthread stub - Bionic bundles pthreads into libc, but # Skia's GN build adds -lpthread for target_os='linux' builds. RUN . /etc/skia-env \ - && printf '!\n' > $ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/$ANDROID_SYSROOT_LIB/libpthread.a + && NDK_SYSROOT="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${NDK_HOST_TAG}/sysroot" \ + && PTHREAD_TARGET="$NDK_SYSROOT/usr/lib/$ANDROID_SYSROOT_LIB/libpthread.a" \ + && if [ -e "$PTHREAD_TARGET" ]; then \ + echo "WARNING: NDK already ships libpthread.a — review needed" && exit 1 ; \ + fi \ + && printf '!\n' > "$PTHREAD_TARGET" # Set cross-compiler to NDK clang RUN . /etc/skia-env \ diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 0552d36722c..d90b9605507 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -19,6 +19,10 @@ FROM mcr.microsoft.com/azurelinux/base/core:3.0 +# Use bash with strict error handling for all RUN commands. +# -e: exit on error, -u: error on undefined vars, -o pipefail: pipe failures propagate. +SHELL ["/bin/bash", "-euo", "pipefail", "-c"] + # Install host build tools from Azure Linux packages. # NOTE: We do NOT install clang/lld/llvm from tdnf because the Azure Linux # LLVM package only includes host+AMDGPU+BPF targets. We need ARM, AArch64, @@ -44,25 +48,32 @@ RUN tdnf install -y \ # Azure Linux's LLVM only has "host;AMDGPU;BPF" — we need all cross targets. # The release binary needs libtinfo.so.5 (built on Ubuntu 18.04) but Azure Linux # only has libtinfo.so.6 — create a compat symlink (ABI compatible for our use). +# SHA-256 checksums computed from official GitHub releases (llvm/llvm-project). ARG LLVM_VERSION=18.1.8 ARG MACHINE_ARCH=x86_64 +ARG LLVM_SHA256_X86_64=54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36 +ARG LLVM_SHA256_AARCH64=dcaa1bebbfbb86953fdfbdc7f938800229f75ad26c5c9375ef242edad737d999 RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ && ln -sf /usr/lib64/libncursesw.so.6 /usr/lib64/libncurses.so.5 \ && case "${MACHINE_ARCH}" in \ - x86_64) LLVM_SUFFIX="x86_64-linux-gnu-ubuntu-18.04" ;; \ - aarch64) LLVM_SUFFIX="aarch64-linux-gnu" ;; \ + x86_64) LLVM_SUFFIX="x86_64-linux-gnu-ubuntu-18.04" ; LLVM_SHA256="${LLVM_SHA256_X86_64}" ;; \ + aarch64) LLVM_SUFFIX="aarch64-linux-gnu" ; LLVM_SHA256="${LLVM_SHA256_AARCH64}" ;; \ *) echo "Unsupported MACHINE_ARCH: ${MACHINE_ARCH}" && exit 1 ;; \ esac \ - && curl -sL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${LLVM_SUFFIX}.tar.xz" \ - | tar xJ --strip-components=1 -C /usr/local \ + && curl -fSL -o /tmp/llvm.tar.xz \ + "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${LLVM_SUFFIX}.tar.xz" \ + && echo "${LLVM_SHA256} /tmp/llvm.tar.xz" | sha256sum -c - \ + && tar xJf /tmp/llvm.tar.xz --strip-components=1 -C /usr/local \ + && rm -f /tmp/llvm.tar.xz \ && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ - && clang --version && ld.lld --version + && clang --version && ld.lld --version && llvm-ar --version -# Install the .NET SDK (needed for Cake build scripts) -ARG DOTNET_SDK_VERSION=10.0.1xx +# Install the .NET SDK (needed for Cake build scripts). +# Pin to exact version matching global.json for reproducible builds. +ARG DOTNET_SDK_VERSION=10.0.100 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -RUN curl https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -L -o dotnet-install.sh \ - && bash dotnet-install.sh --channel ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ +RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ + && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ && rm dotnet-install.sh \ && dotnet help \ @@ -118,10 +129,10 @@ RUN . /etc/skia-env \ # use gcc-cross package extraction. # This produces the cross-package layout at /sysroot/usr/{TOOLCHAIN_ARCH}/ mkdir -p ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH} \ - && TOOLCHAIN_VERSION=$(grep TOOLCHAIN_VERSION /etc/skia-env | cut -d= -f2) \ && APT_REPO=https://deb.debian.org/debian \ && POOL="${APT_REPO}/pool/main" \ && PORTS_SUFFIX="-ports" \ + && FETCH_FAILURES=0 \ && mkdir -p /tmp/cross-pkgs && cd /tmp/cross-pkgs \ && for pkg in \ "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ @@ -135,18 +146,22 @@ RUN . /etc/skia-env \ base=$(echo $pkg | rev | cut -d/ -f2- | rev) ; \ url="${POOL}/${base}/" ; \ echo "Fetching index: $url (looking for ${dir}_*.deb)" ; \ - deb_url=$(curl -sL "$url" | grep -o "href=\"${dir}_[^\"]*\.deb\"" | sed 's/href="//;s/"$//' | sort -V | tail -1) ; \ + deb_url=$(curl -fsSL "$url" | grep -o "href=\"${dir}_[^\"]*\.deb\"" | sed 's/href="//;s/"$//' | sort -V | tail -1) ; \ if [ -n "$deb_url" ]; then \ echo " Downloading: ${url}${deb_url}" ; \ - curl -sL -O "${url}${deb_url}" ; \ + curl -fsSL -O "${url}${deb_url}" ; \ ar x "${deb_url}" ; \ mkdir -p extract && tar xf data.tar.* -C extract/ ; \ rm -f control.tar.* data.tar.* debian-binary "${deb_url}" ; \ else \ - echo " WARNING: No package found for ${dir}" ; \ + echo " FATAL: No package found for ${dir}" ; \ + FETCH_FAILURES=$((FETCH_FAILURES + 1)) ; \ fi ; \ done \ - && echo "Extracted files:" && ls extract/usr/${TOOLCHAIN_ARCH}/ 2>/dev/null | head -5 || true \ + && if [ "${FETCH_FAILURES}" -gt 0 ]; then \ + echo "ERROR: ${FETCH_FAILURES} package(s) could not be fetched" && exit 1 ; \ + fi \ + && echo "Extracted files:" && ls extract/usr/${TOOLCHAIN_ARCH}/ | head -5 \ && if [ -d extract/usr/${TOOLCHAIN_ARCH} ]; then \ cp -a extract/usr/${TOOLCHAIN_ARCH}/* ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/ ; \ fi \ @@ -167,18 +182,31 @@ RUN . /etc/skia-env \ # but debootstrap pre-creates /bin/ as a directory, causing tar to fail with # "Cannot create symlink: File exists" (exit 2). This is harmless — debootstrap # still downloads all packages to the cache and creates the symlinks afterward. - # We tolerate this known exit code and verify the cache was populated. + # We tolerate ONLY this specific scenario and verify ALL required packages cached. debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev,libc6-dev \ ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ - || { echo "debootstrap exited $? (known issue with Trixie merged-usr; checking cache...)" ; \ - ls ${SYSROOT_DIR}/var/cache/apt/archives/libstdc++-${TOOLCHAIN_VERSION}-dev*.deb > /dev/null 2>&1 \ - || { echo "FATAL: debootstrap cache not populated" && exit 1 ; } ; } \ + || { DEBOOTSTRAP_EXIT=$? ; \ + echo "debootstrap exited ${DEBOOTSTRAP_EXIT} — verifying cache..." ; \ + for required_pkg in \ + "libstdc++-${TOOLCHAIN_VERSION}-dev" \ + "libgcc-${TOOLCHAIN_VERSION}-dev" \ + "linux-libc-dev" \ + "libc6-dev" \ + "libc6" \ + "libgcc-s1"; do \ + ls ${SYSROOT_DIR}/var/cache/apt/archives/${required_pkg}_*.deb > /dev/null 2>&1 \ + || { echo "FATAL: required package '${required_pkg}' not in cache (debootstrap exit ${DEBOOTSTRAP_EXIT})" && exit 1 ; } ; \ + done ; \ + echo "All required packages found in cache." ; } \ && echo "Extracting all cached packages into sysroot..." \ && for deb in ${SYSROOT_DIR}/var/cache/apt/archives/*.deb; do \ if [ -f "$deb" ]; then \ echo " Extracting: $(basename $deb)" ; \ - cd /tmp && ar x "$deb" && tar xf data.tar.* -C ${SYSROOT_DIR}/ 2>/dev/null ; rm -f control.tar.* data.tar.* debian-binary ; \ + cd /tmp && ar x "$deb" \ + && tar xf data.tar.* -C ${SYSROOT_DIR}/ 2>&1 \ + | { grep -v "Cannot create symlink" || true ; } \ + && rm -f control.tar.* data.tar.* debian-binary ; \ fi ; \ done \ && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env \ @@ -199,9 +227,7 @@ RUN . /etc/skia-env \ # /usr/include/c++/VERSION/{TOOLCHAIN_ARCH}/ (the cross-toolchain layout) # We create a symlink to bridge this gap. RUN . /etc/skia-env \ - && TOOLCHAIN_VERSION=$(grep TOOLCHAIN_VERSION /etc/skia-env | cut -d= -f2) \ - && SYSROOT_PATH_VAL=$(grep "SYSROOT_PATH=" /etc/skia-env | cut -d= -f2) \ - && INCLUDE_DIR=${SYSROOT_PATH_VAL}/include/c++ \ + && INCLUDE_DIR=${SYSROOT_PATH}/include/c++ \ && echo "Looking for c++ headers in: ${INCLUDE_DIR}" \ && echo "TOOLCHAIN_VERSION=${TOOLCHAIN_VERSION}, TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" \ && if [ -d "${INCLUDE_DIR}" ]; then ls ${INCLUDE_DIR}/ ; fi \ @@ -209,7 +235,7 @@ RUN . /etc/skia-env \ ln -sf ${TOOLCHAIN_VERSION} ${INCLUDE_DIR}/current ; \ echo "Created symlink: ${INCLUDE_DIR}/current -> ${TOOLCHAIN_VERSION}" ; \ elif [ -d "${INCLUDE_DIR}" ]; then \ - GCC_VERSION=$(ls ${INCLUDE_DIR}/ | grep -E '^[0-9]' | head -1) ; \ + GCC_VERSION=$(ls ${INCLUDE_DIR}/ | grep -E '^[0-9]' | sort -V | tail -1) ; \ if [ -n "$GCC_VERSION" ]; then \ ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current ; \ echo "Created symlink: ${INCLUDE_DIR}/current -> ${GCC_VERSION}" ; \ @@ -220,7 +246,7 @@ RUN . /etc/skia-env \ else \ echo "ERROR: ${INCLUDE_DIR} does not exist!" && exit 1 ; \ fi \ - && ARCH_INCLUDE=${SYSROOT_PATH_VAL}/include/${TOOLCHAIN_ARCH}/c++/${TOOLCHAIN_VERSION} \ + && ARCH_INCLUDE=${SYSROOT_PATH}/include/${TOOLCHAIN_ARCH}/c++/${TOOLCHAIN_VERSION} \ && if [ -d "${ARCH_INCLUDE}" ] && [ ! -e "${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH}" ]; then \ ln -sf ${ARCH_INCLUDE} ${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH} ; \ echo "Created arch symlink: ${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH} -> ${ARCH_INCLUDE}" ; \ @@ -238,7 +264,7 @@ RUN . /etc/skia-env \ # For debootstrap builds, --sysroot causes lld to automatically prepend the sysroot # to absolute paths in linker scripts, so no modification is needed. RUN . /etc/skia-env \ - && if [ -z "$(grep 'SYSROOT_ROOT=' /etc/skia-env | cut -d= -f2)" ]; then \ + && if [ -z "${SYSROOT_ROOT:-}" ]; then \ for script in \ ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libc.so \ ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so \ @@ -258,7 +284,6 @@ RUN . /etc/skia-env \ # Download .deb packages from Debian's pool and extract headers + libraries. # For cross-package arches (loongarch64), relocate to match the SYSROOT_PATH layout. RUN . /etc/skia-env \ - && SYSROOT_PATH_VAL=$(grep "SYSROOT_PATH=" /etc/skia-env | cut -d= -f2) \ && mkdir -p /tmp/fontconfig \ && cd /tmp/fontconfig \ && case "${SYSROOT_RELEASE}" in \ @@ -270,7 +295,7 @@ RUN . /etc/skia-env \ && for pkg in libfontconfig-dev libfontconfig1; do \ url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb"; \ echo "Downloading $url"; \ - curl -L -o "${pkg}.deb" "$url"; \ + curl -fSL -o "${pkg}.deb" "$url"; \ ar x "${pkg}.deb" ; \ tar xf data.tar.* -C /tmp/fontconfig/extract/ ; \ rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ @@ -278,10 +303,10 @@ RUN . /etc/skia-env \ && case "${BUILD_ARCH}" in \ loongarch64) \ # Cross-package layout: put headers and libs under SYSROOT_PATH - mkdir -p ${SYSROOT_PATH_VAL}/include ${SYSROOT_PATH_VAL}/lib ; \ - cp -a /tmp/fontconfig/extract/usr/include/* ${SYSROOT_PATH_VAL}/include/ ; \ + mkdir -p ${SYSROOT_PATH}/include ${SYSROOT_PATH}/lib ; \ + cp -a /tmp/fontconfig/extract/usr/include/* ${SYSROOT_PATH}/include/ ; \ if [ -d "/tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}" ]; then \ - cp -a /tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}/* ${SYSROOT_PATH_VAL}/lib/ ; \ + cp -a /tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}/* ${SYSROOT_PATH}/lib/ ; \ fi ;; \ *) \ # Debootstrap layout: extract directly to sysroot root From 3df78a0b0f8ce196a21a91fcc4dc874c83ba3542 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 19:12:37 +0200 Subject: [PATCH 19/45] chore: bump .NET SDK to 10.0.108 (latest 10.0.1xx) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/infra/native/linux/docker/alpine/Dockerfile | 2 +- scripts/infra/native/linux/docker/bionic/Dockerfile | 2 +- scripts/infra/native/linux/docker/glibc/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 120a3f31e8e..bd7c553e541 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -56,7 +56,7 @@ RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ && clang --version && ld.lld --version && llvm-ar --version # Install the .NET SDK (needed for Cake build scripts). -ARG DOTNET_SDK_VERSION=10.0.100 +ARG DOTNET_SDK_VERSION=10.0.108 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index 9f23bb06c99..db38b9af941 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -28,7 +28,7 @@ RUN tdnf install -y \ && tdnf clean all # Install the .NET SDK (needed for Cake build scripts). -ARG DOTNET_SDK_VERSION=10.0.100 +ARG DOTNET_SDK_VERSION=10.0.108 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index d90b9605507..419e9bf3dda 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -70,7 +70,7 @@ RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ # Install the .NET SDK (needed for Cake build scripts). # Pin to exact version matching global.json for reproducible builds. -ARG DOTNET_SDK_VERSION=10.0.100 +ARG DOTNET_SDK_VERSION=10.0.108 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ From 371dbb2cf27fa813e069e948d1e48d95fa887f93 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 19:30:08 +0200 Subject: [PATCH 20/45] refactor: split loongarch64 into dedicated Dockerfile and simplify include paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract loongarch64 cross-package extraction into its own Dockerfile (scripts/infra/native/linux/docker/loongarch64/) since it uses a fundamentally different mechanism than debootstrap - Remove all loongarch64 case branches and linker-script fixups from glibc/Dockerfile — now purely debootstrap-based (196 lines vs 322) - Simplify c++ header symlinks: just 'current -> VERSION' (2 lines) instead of the 30-line detection/bridging block - Update build.cake to include both Debian arch-header layouts (non-existent -I paths are harmless to clang) - Update CI YAML to route loongarch64 to new Docker directory - Simplify alpine c++ symlink creation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux-clang-cross/build.cake | 7 + .../azure-templates-stages-native-linux.yml | 21 +- .../native/linux/docker/alpine/Dockerfile | 29 +-- .../native/linux/docker/glibc/Dockerfile | 229 ++++-------------- .../linux/docker/loongarch64/Dockerfile | 184 ++++++++++++++ .../linux/docker/loongarch64/startup.sh | 6 + 6 files changed, 272 insertions(+), 204 deletions(-) create mode 100644 scripts/infra/native/linux/docker/loongarch64/Dockerfile create mode 100644 scripts/infra/native/linux/docker/loongarch64/startup.sh diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index 728585b9bb8..85b230aa353 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -40,9 +40,16 @@ string GetGnArgs(string arch) // When does #include_next , the search continues from paths // AFTER the directory where cmath was found. If C headers are listed first, // they won't be searched by #include_next from C++ headers. + // + // The Dockerfile creates: include/c++/current -> VERSION + // Arch-specific headers vary by layout: + // Debootstrap: {sysroot}/include/{ARCH}/c++/current/ + // Cross-packages: {sysroot}/include/c++/current/{ARCH}/ + // We include both — non-existent paths are harmless to clang. var includes = $"'-I{sysroot}/include/c++/current', " + $"'-I{sysroot}/include/c++/current/{TOOLCHAIN_ARCH}', " + + $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}/c++/current', " + $"'-I{sysroot}/include', " + $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}' "; diff --git a/scripts/azure-templates-stages-native-linux.yml b/scripts/azure-templates-stages-native-linux.yml index e77a2e18774..b6500d2448f 100644 --- a/scripts/azure-templates-stages-native-linux.yml +++ b/scripts/azure-templates-stages-native-linux.yml @@ -27,16 +27,21 @@ stages: additionalArgs: --verifyExcluded=fontconfig gnArgs: skia_use_fontconfig=false matrix: + # Azure Linux + loongarch64 cross-package extraction (gcc-14, glibc 2.38). + # loongarch64 has no Debian binary archive — uses its own Dockerfile. + - arch: loongarch64 + docker: scripts/infra/native/linux/docker/loongarch64 + target: externals-linux-clang-cross + additionalArgs: --verifyGlibcMax=2.38 + dockerArgs: --build-arg SYSROOT_RELEASE=trixie # Azure Linux + Trixie sysroot (gcc-14, glibc 2.38) - for archs that # need GCC 14 (lld 18 is incompatible with GCC 10's static libstdc++.a - # on riscv64 due to .eh_frame discarded-section errors), or never had - # release-arch packages in Bullseye/Bookworm (loongarch64). - - ${{ each arch in split('loongarch64,riscv64', ',') }}: - - arch: ${{ arch }} - docker: scripts/infra/native/linux/docker/glibc - target: externals-linux-clang-cross - additionalArgs: --verifyGlibcMax=2.38 - dockerArgs: --build-arg SYSROOT_RELEASE=trixie + # on riscv64 due to .eh_frame discarded-section errors). + - arch: riscv64 + docker: scripts/infra/native/linux/docker/glibc + target: externals-linux-clang-cross + additionalArgs: --verifyGlibcMax=2.38 + dockerArgs: --build-arg SYSROOT_RELEASE=trixie # Azure Linux + Bullseye sysroot (gcc-10, glibc 2.31). - ${{ each arch in split('arm,arm64,x86,x64', ',') }}: - arch: ${{ arch }} diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index bd7c553e541..bd38cb6f54b 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -122,25 +122,16 @@ RUN . /etc/skia-env \ add musl-dev linux-headers fontconfig-dev libstdc++-dev gcc \ && rm -rf "$APK_DIR" -# Create c++/current symlink so build.cake can find libstdc++ headers -# without hardcoding the GCC version number. Alpine installs headers at -# /alpine/usr/include/c++// (e.g. 14.2.0). -RUN . /etc/skia-env \ - && INCLUDE_DIR=/alpine/usr/include/c++ \ - && if [ -d "${INCLUDE_DIR}" ]; then \ - GCC_VERSION=$(ls ${INCLUDE_DIR}/ | grep -E '^[0-9]' | sort -V | tail -1) ; \ - if [ -n "$GCC_VERSION" ]; then \ - ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current ; \ - echo "Created symlink: ${INCLUDE_DIR}/current -> ${GCC_VERSION}" ; \ - GCC_LIB_DIR="/alpine/usr/lib/gcc/${TOOLCHAIN_ARCH}/${GCC_VERSION}" ; \ - echo "export GCC_LIB_DIR=${GCC_LIB_DIR}" >> /etc/skia-env ; \ - echo "GCC lib dir: ${GCC_LIB_DIR}" ; \ - else \ - echo "ERROR: No GCC version directory found in ${INCLUDE_DIR}" && exit 1 ; \ - fi ; \ - else \ - echo "ERROR: ${INCLUDE_DIR} does not exist!" && exit 1 ; \ - fi +# Create c++/current symlink and discover GCC lib dir. +# Alpine installs headers at /alpine/usr/include/c++// (e.g. 14.2.0). +# We need the version to locate the GCC lib directory for CRT files. +RUN INCLUDE_DIR=/alpine/usr/include/c++ \ + && GCC_VERSION=$(ls ${INCLUDE_DIR}/ | grep -E '^[0-9]' | sort -V | tail -1) \ + && if [ -z "$GCC_VERSION" ]; then echo "ERROR: No GCC version in ${INCLUDE_DIR}" && exit 1 ; fi \ + && ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current \ + && . /etc/skia-env \ + && echo "export GCC_LIB_DIR=/alpine/usr/lib/gcc/${TOOLCHAIN_ARCH}/${GCC_VERSION}" >> /etc/skia-env \ + && echo "Created: ${INCLUDE_DIR}/current -> ${GCC_VERSION}" WORKDIR /work diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 419e9bf3dda..fe3d6ef55c6 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -15,7 +15,9 @@ # | trixie (13) | 2.38 | GCC 14 | Debian 13+, Ubuntu 24.04+ | # # Bullseye is the oldest with GCC 10 libstdc++ (C++20: std::span, std::countl_zero). -# loongarch64 requires Trixie (no Bullseye/Bookworm binary archive). +# +# Supported architectures: arm, arm64, x86, x64, riscv64 +# For loongarch64, see ../loongarch64/Dockerfile (cross-package extraction). FROM mcr.microsoft.com/azurelinux/base/core:3.0 @@ -85,13 +87,12 @@ ARG BUILD_ARCH=arm64 # Map BUILD_ARCH to Debian triple, dpkg short arch, and clang target triple. RUN case ${BUILD_ARCH} in \ - arm) TOOLCHAIN_ARCH=arm-linux-gnueabihf ; TOOLCHAIN_ARCH_SHORT=armhf ; TOOLCHAIN_ARCH_TARGET=armv7a-linux-gnueabihf ;; \ - arm64) TOOLCHAIN_ARCH=aarch64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=arm64 ; TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu ;; \ - riscv64) TOOLCHAIN_ARCH=riscv64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=riscv64 ; TOOLCHAIN_ARCH_TARGET=riscv64-linux-gnu ;; \ - x86) TOOLCHAIN_ARCH=i386-linux-gnu ; TOOLCHAIN_ARCH_SHORT=i386 ; TOOLCHAIN_ARCH_TARGET=i686-linux-gnu ;; \ - x64) TOOLCHAIN_ARCH=x86_64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=amd64 ; TOOLCHAIN_ARCH_TARGET=x86_64-linux-gnu ;; \ - loongarch64) TOOLCHAIN_ARCH=loongarch64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=loong64 ; TOOLCHAIN_ARCH_TARGET=loongarch64-linux-gnu ;; \ - *) echo "Unsupported architecture: ${BUILD_ARCH}" && exit 1 ;; \ + arm) TOOLCHAIN_ARCH=arm-linux-gnueabihf ; TOOLCHAIN_ARCH_SHORT=armhf ; TOOLCHAIN_ARCH_TARGET=armv7a-linux-gnueabihf ;; \ + arm64) TOOLCHAIN_ARCH=aarch64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=arm64 ; TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu ;; \ + riscv64) TOOLCHAIN_ARCH=riscv64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=riscv64 ; TOOLCHAIN_ARCH_TARGET=riscv64-linux-gnu ;; \ + x86) TOOLCHAIN_ARCH=i386-linux-gnu ; TOOLCHAIN_ARCH_SHORT=i386 ; TOOLCHAIN_ARCH_TARGET=i686-linux-gnu ;; \ + x64) TOOLCHAIN_ARCH=x86_64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=amd64 ; TOOLCHAIN_ARCH_TARGET=x86_64-linux-gnu ;; \ + *) echo "Unsupported architecture: ${BUILD_ARCH} (use ../loongarch64/ for loongarch64)" && exit 1 ;; \ esac \ && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ && echo "export TOOLCHAIN_ARCH_SHORT=${TOOLCHAIN_ARCH_SHORT}" >> /etc/skia-env \ @@ -99,7 +100,7 @@ RUN case ${BUILD_ARCH} in \ # Choose the Debian release for the sysroot. # Bullseye (glibc 2.31) is default — oldest with C++20 libstdc++. -# loongarch64 needs Trixie — no binary archive exists in Bullseye/Bookworm. +# riscv64 uses Trixie because lld 18 is incompatible with GCC 10's static libstdc++.a. ARG SYSROOT_RELEASE=bullseye # Determine GCC/libstdc++ version from the release. @@ -118,171 +119,56 @@ RUN case ${SYSROOT_RELEASE} in \ # --variant=minbase: Only essential packages (dpkg, apt, coreutils). # --include: Add the gcc cross-compilation development packages we need. # -# NOTE: loongarch64 is not a release arch in any current Debian version — use -# gcc-cross-ports package extraction. riscv64 became a release arch in Trixie -# so it uses debootstrap (below) when built with Trixie. +# NOTE: Trixie's base-files creates merged-usr symlinks (bin→usr/bin etc.) +# but debootstrap pre-creates /bin/ as a directory, causing tar to fail with +# "Cannot create symlink: File exists" (exit 2). This is harmless — debootstrap +# still downloads all packages to the cache and creates the symlinks afterward. +# We tolerate ONLY this specific scenario and verify ALL required packages cached. ARG SYSROOT_DIR=/sysroot RUN . /etc/skia-env \ - && case "${BUILD_ARCH}" in \ - loongarch64) \ - # loongarch64 has no standard Debian binary archive (even in Trixie) — - # use gcc-cross package extraction. - # This produces the cross-package layout at /sysroot/usr/{TOOLCHAIN_ARCH}/ - mkdir -p ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH} \ - && APT_REPO=https://deb.debian.org/debian \ - && POOL="${APT_REPO}/pool/main" \ - && PORTS_SUFFIX="-ports" \ - && FETCH_FAILURES=0 \ - && mkdir -p /tmp/cross-pkgs && cd /tmp/cross-pkgs \ - && for pkg in \ - "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++6-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libgcc-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libgcc-s1-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "c/cross-toolchain-base${PORTS_SUFFIX}/linux-libc-dev-${TOOLCHAIN_ARCH_SHORT}-cross"; do \ - dir=$(echo $pkg | rev | cut -d/ -f1 | rev) ; \ - base=$(echo $pkg | rev | cut -d/ -f2- | rev) ; \ - url="${POOL}/${base}/" ; \ - echo "Fetching index: $url (looking for ${dir}_*.deb)" ; \ - deb_url=$(curl -fsSL "$url" | grep -o "href=\"${dir}_[^\"]*\.deb\"" | sed 's/href="//;s/"$//' | sort -V | tail -1) ; \ - if [ -n "$deb_url" ]; then \ - echo " Downloading: ${url}${deb_url}" ; \ - curl -fsSL -O "${url}${deb_url}" ; \ - ar x "${deb_url}" ; \ - mkdir -p extract && tar xf data.tar.* -C extract/ ; \ - rm -f control.tar.* data.tar.* debian-binary "${deb_url}" ; \ - else \ - echo " FATAL: No package found for ${dir}" ; \ - FETCH_FAILURES=$((FETCH_FAILURES + 1)) ; \ - fi ; \ - done \ - && if [ "${FETCH_FAILURES}" -gt 0 ]; then \ - echo "ERROR: ${FETCH_FAILURES} package(s) could not be fetched" && exit 1 ; \ - fi \ - && echo "Extracted files:" && ls extract/usr/${TOOLCHAIN_ARCH}/ | head -5 \ - && if [ -d extract/usr/${TOOLCHAIN_ARCH} ]; then \ - cp -a extract/usr/${TOOLCHAIN_ARCH}/* ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/ ; \ - fi \ - && if [ -d extract/usr/lib/gcc-cross ]; then \ - mkdir -p ${SYSROOT_DIR}/usr/lib/gcc-cross ; \ - cp -a extract/usr/lib/gcc-cross/* ${SYSROOT_DIR}/usr/lib/gcc-cross/ ; \ - fi \ - && rm -rf /tmp/cross-pkgs \ - && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}" >> /etc/skia-env \ - && echo "export GCC_LIB_DIR=${SYSROOT_DIR}/usr/lib/gcc-cross/${TOOLCHAIN_ARCH}/${TOOLCHAIN_VERSION}" >> /etc/skia-env ;; \ - *) \ - # Standard debootstrap for architectures with Debian binary archives. - # Produces native layout: headers at /usr/include/, libs at /usr/lib/{triple}/ - # NOTE: --foreign only extracts essential/base packages; --include packages - # are downloaded to var/cache/apt/archives/ but NOT extracted. We must - # manually extract them after debootstrap completes. - # NOTE: Trixie's base-files creates merged-usr symlinks (bin→usr/bin etc.) - # but debootstrap pre-creates /bin/ as a directory, causing tar to fail with - # "Cannot create symlink: File exists" (exit 2). This is harmless — debootstrap - # still downloads all packages to the cache and creates the symlinks afterward. - # We tolerate ONLY this specific scenario and verify ALL required packages cached. - debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ - --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev,libc6-dev \ - ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ - || { DEBOOTSTRAP_EXIT=$? ; \ - echo "debootstrap exited ${DEBOOTSTRAP_EXIT} — verifying cache..." ; \ - for required_pkg in \ - "libstdc++-${TOOLCHAIN_VERSION}-dev" \ - "libgcc-${TOOLCHAIN_VERSION}-dev" \ - "linux-libc-dev" \ - "libc6-dev" \ - "libc6" \ - "libgcc-s1"; do \ - ls ${SYSROOT_DIR}/var/cache/apt/archives/${required_pkg}_*.deb > /dev/null 2>&1 \ - || { echo "FATAL: required package '${required_pkg}' not in cache (debootstrap exit ${DEBOOTSTRAP_EXIT})" && exit 1 ; } ; \ - done ; \ - echo "All required packages found in cache." ; } \ - && echo "Extracting all cached packages into sysroot..." \ - && for deb in ${SYSROOT_DIR}/var/cache/apt/archives/*.deb; do \ - if [ -f "$deb" ]; then \ - echo " Extracting: $(basename $deb)" ; \ - cd /tmp && ar x "$deb" \ - && tar xf data.tar.* -C ${SYSROOT_DIR}/ 2>&1 \ - | { grep -v "Cannot create symlink" || true ; } \ - && rm -f control.tar.* data.tar.* debian-binary ; \ - fi ; \ - done \ - && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env \ - && echo "export SYSROOT_ROOT=${SYSROOT_DIR}" >> /etc/skia-env \ - && GCC_DIR=$(find ${SYSROOT_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ - && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env ;; \ - esac \ - && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" - -# Create the c++/current symlink so build.cake can reference libstdc++ headers -# without baking the GCC version number into the build script. -# Debootstrap: headers at /sysroot/usr/include/c++/VERSION/ -# Cross-packages (loongarch64): headers at /sysroot/usr/{ARCH}/include/c++/VERSION/ -# -# Additionally, Debian native packages place arch-specific c++ headers at: -# /usr/include/{TOOLCHAIN_ARCH}/c++/VERSION/ (e.g. /usr/include/arm-linux-gnueabihf/c++/10/) -# But build.cake expects them at: -# /usr/include/c++/VERSION/{TOOLCHAIN_ARCH}/ (the cross-toolchain layout) -# We create a symlink to bridge this gap. -RUN . /etc/skia-env \ - && INCLUDE_DIR=${SYSROOT_PATH}/include/c++ \ - && echo "Looking for c++ headers in: ${INCLUDE_DIR}" \ - && echo "TOOLCHAIN_VERSION=${TOOLCHAIN_VERSION}, TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" \ - && if [ -d "${INCLUDE_DIR}" ]; then ls ${INCLUDE_DIR}/ ; fi \ - && if [ -d "${INCLUDE_DIR}/${TOOLCHAIN_VERSION}" ]; then \ - ln -sf ${TOOLCHAIN_VERSION} ${INCLUDE_DIR}/current ; \ - echo "Created symlink: ${INCLUDE_DIR}/current -> ${TOOLCHAIN_VERSION}" ; \ - elif [ -d "${INCLUDE_DIR}" ]; then \ - GCC_VERSION=$(ls ${INCLUDE_DIR}/ | grep -E '^[0-9]' | sort -V | tail -1) ; \ - if [ -n "$GCC_VERSION" ]; then \ - ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current ; \ - echo "Created symlink: ${INCLUDE_DIR}/current -> ${GCC_VERSION}" ; \ - TOOLCHAIN_VERSION=${GCC_VERSION} ; \ - else \ - echo "ERROR: No version directory found in ${INCLUDE_DIR}" && exit 1 ; \ + && debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ + --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev,libc6-dev \ + ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ + || { DEBOOTSTRAP_EXIT=$? ; \ + echo "debootstrap exited ${DEBOOTSTRAP_EXIT} — verifying cache..." ; \ + for required_pkg in \ + "libstdc++-${TOOLCHAIN_VERSION}-dev" \ + "libgcc-${TOOLCHAIN_VERSION}-dev" \ + "linux-libc-dev" \ + "libc6-dev" \ + "libc6" \ + "libgcc-s1"; do \ + ls ${SYSROOT_DIR}/var/cache/apt/archives/${required_pkg}_*.deb > /dev/null 2>&1 \ + || { echo "FATAL: required package '${required_pkg}' not in cache (debootstrap exit ${DEBOOTSTRAP_EXIT})" && exit 1 ; } ; \ + done ; \ + echo "All required packages found in cache." ; } \ + && echo "Extracting all cached packages into sysroot..." \ + && for deb in ${SYSROOT_DIR}/var/cache/apt/archives/*.deb; do \ + if [ -f "$deb" ]; then \ + echo " Extracting: $(basename $deb)" ; \ + cd /tmp && ar x "$deb" \ + && tar xf data.tar.* -C ${SYSROOT_DIR}/ 2>&1 \ + | { grep -v "Cannot create symlink" || true ; } \ + && rm -f control.tar.* data.tar.* debian-binary ; \ fi ; \ - else \ - echo "ERROR: ${INCLUDE_DIR} does not exist!" && exit 1 ; \ - fi \ - && ARCH_INCLUDE=${SYSROOT_PATH}/include/${TOOLCHAIN_ARCH}/c++/${TOOLCHAIN_VERSION} \ - && if [ -d "${ARCH_INCLUDE}" ] && [ ! -e "${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH}" ]; then \ - ln -sf ${ARCH_INCLUDE} ${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH} ; \ - echo "Created arch symlink: ${INCLUDE_DIR}/${TOOLCHAIN_VERSION}/${TOOLCHAIN_ARCH} -> ${ARCH_INCLUDE}" ; \ - fi \ - && if [ ! -e "${INCLUDE_DIR}/current/${TOOLCHAIN_ARCH}" ] && [ -d "${INCLUDE_DIR}/current/" ]; then \ - EXISTING_ARCH=$(ls -d ${INCLUDE_DIR}/current/*-linux-gnu* 2>/dev/null | head -1 | xargs basename 2>/dev/null) ; \ - if [ -n "${EXISTING_ARCH}" ] && [ "${EXISTING_ARCH}" != "${TOOLCHAIN_ARCH}" ]; then \ - ln -sf ${EXISTING_ARCH} ${INCLUDE_DIR}/current/${TOOLCHAIN_ARCH} ; \ - echo "Created arch compat symlink: ${TOOLCHAIN_ARCH} -> ${EXISTING_ARCH}" ; \ - fi ; \ - fi + done \ + && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env \ + && echo "export SYSROOT_ROOT=${SYSROOT_DIR}" >> /etc/skia-env \ + && GCC_DIR=$(find ${SYSROOT_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ + && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env \ + && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" -# Fix libc/libpthread linker scripts to use relative paths. -# Only needed for cross-package builds (loongarch64) which don't use --sysroot. -# For debootstrap builds, --sysroot causes lld to automatically prepend the sysroot -# to absolute paths in linker scripts, so no modification is needed. +# Create "current" symlinks so build.cake can reference c++ headers version-agnostically. +# include/c++/current -> VERSION (main c++ headers) +# include/{ARCH}/c++/current -> VERSION (arch-specific c++ headers, Debian native layout) RUN . /etc/skia-env \ - && if [ -z "${SYSROOT_ROOT:-}" ]; then \ - for script in \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libc.so \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so.0 \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib64/libc.so \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib64/libpthread.so; do \ - if [ -f "$script" ]; then \ - sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib64/||g; s|/usr/${TOOLCHAIN_ARCH}/lib/||g; s|/lib/${TOOLCHAIN_ARCH}/||g; s|/lib64/||g; s|/lib/||g" "$script" ; \ - echo "Fixed linker script: $script" ; \ - fi ; \ - done ; \ - else \ - echo "Skipping linker script fix (--sysroot handles absolute paths)" ; \ + && ln -sf ${TOOLCHAIN_VERSION} ${SYSROOT_PATH}/include/c++/current \ + && if [ -d "${SYSROOT_PATH}/include/${TOOLCHAIN_ARCH}/c++/${TOOLCHAIN_VERSION}" ]; then \ + ln -sf ${TOOLCHAIN_VERSION} ${SYSROOT_PATH}/include/${TOOLCHAIN_ARCH}/c++/current ; \ fi # Install fontconfig into the sysroot. # Download .deb packages from Debian's pool and extract headers + libraries. -# For cross-package arches (loongarch64), relocate to match the SYSROOT_PATH layout. RUN . /etc/skia-env \ && mkdir -p /tmp/fontconfig \ && cd /tmp/fontconfig \ @@ -300,18 +186,7 @@ RUN . /etc/skia-env \ tar xf data.tar.* -C /tmp/fontconfig/extract/ ; \ rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ done \ - && case "${BUILD_ARCH}" in \ - loongarch64) \ - # Cross-package layout: put headers and libs under SYSROOT_PATH - mkdir -p ${SYSROOT_PATH}/include ${SYSROOT_PATH}/lib ; \ - cp -a /tmp/fontconfig/extract/usr/include/* ${SYSROOT_PATH}/include/ ; \ - if [ -d "/tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}" ]; then \ - cp -a /tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}/* ${SYSROOT_PATH}/lib/ ; \ - fi ;; \ - *) \ - # Debootstrap layout: extract directly to sysroot root - cp -a /tmp/fontconfig/extract/* ${SYSROOT_DIR}/ ;; \ - esac \ + && cp -a /tmp/fontconfig/extract/* ${SYSROOT_DIR}/ \ && rm -rf /tmp/fontconfig WORKDIR /work diff --git a/scripts/infra/native/linux/docker/loongarch64/Dockerfile b/scripts/infra/native/linux/docker/loongarch64/Dockerfile new file mode 100644 index 00000000000..bcd64604514 --- /dev/null +++ b/scripts/infra/native/linux/docker/loongarch64/Dockerfile @@ -0,0 +1,184 @@ +# loongarch64 glibc cross-compilation image. +# +# loongarch64 has no Debian binary archive (even in Trixie), so it cannot use +# debootstrap. Instead, we download gcc-cross-ports packages from Debian's pool +# and manually extract them to create a sysroot. +# +# Architecture: +# Azure Linux 3.0 (host) → clang-18 → --sysroot=/sysroot/usr/loongarch64-linux-gnu → libSkiaSharp.so +# +# The cross-package layout places headers/libs under /usr/{TOOLCHAIN_ARCH}/ rather +# than the native /usr/include/ + /usr/lib/{triple}/ layout that debootstrap produces. +# This requires linker-script path fixups since the scripts use absolute paths. + +FROM mcr.microsoft.com/azurelinux/base/core:3.0 + +# Use bash with strict error handling for all RUN commands. +SHELL ["/bin/bash", "-euo", "pipefail", "-c"] + +# Install host build tools from Azure Linux packages. +RUN tdnf install -y \ + ninja-build \ + python3 \ + git \ + curl \ + tar \ + xz \ + gawk \ + ca-certificates \ + binutils \ + libxml2 \ + ncurses-libs \ + zlib \ + libffi \ + && tdnf clean all + +# Install official LLVM 18 release with all target backends. +# SHA-256 checksums computed from official GitHub releases (llvm/llvm-project). +ARG LLVM_VERSION=18.1.8 +ARG MACHINE_ARCH=x86_64 +ARG LLVM_SHA256_X86_64=54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36 +ARG LLVM_SHA256_AARCH64=dcaa1bebbfbb86953fdfbdc7f938800229f75ad26c5c9375ef242edad737d999 +RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ + && ln -sf /usr/lib64/libncursesw.so.6 /usr/lib64/libncurses.so.5 \ + && case "${MACHINE_ARCH}" in \ + x86_64) LLVM_SUFFIX="x86_64-linux-gnu-ubuntu-18.04" ; LLVM_SHA256="${LLVM_SHA256_X86_64}" ;; \ + aarch64) LLVM_SUFFIX="aarch64-linux-gnu" ; LLVM_SHA256="${LLVM_SHA256_AARCH64}" ;; \ + *) echo "Unsupported MACHINE_ARCH: ${MACHINE_ARCH}" && exit 1 ;; \ + esac \ + && curl -fSL -o /tmp/llvm.tar.xz \ + "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${LLVM_SUFFIX}.tar.xz" \ + && echo "${LLVM_SHA256} /tmp/llvm.tar.xz" | sha256sum -c - \ + && tar xJf /tmp/llvm.tar.xz --strip-components=1 -C /usr/local \ + && rm -f /tmp/llvm.tar.xz \ + && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ + && clang --version && ld.lld --version && llvm-ar --version + +# Install the .NET SDK (needed for Cake build scripts). +ARG DOTNET_SDK_VERSION=10.0.108 +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ + && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ + && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ + && rm dotnet-install.sh \ + && dotnet help \ + && dotnet --info + +ENV CC=clang CXX=clang++ + +# loongarch64 constants — no BUILD_ARCH arg needed since this image is single-arch. +ARG SYSROOT_RELEASE=trixie +ARG SYSROOT_DIR=/sysroot + +RUN TOOLCHAIN_ARCH=loongarch64-linux-gnu \ + && TOOLCHAIN_ARCH_SHORT=loong64 \ + && TOOLCHAIN_ARCH_TARGET=loongarch64-linux-gnu \ + && case "${SYSROOT_RELEASE}" in \ + trixie) TOOLCHAIN_VERSION=14 ;; \ + *) echo "Unsupported release for loongarch64: ${SYSROOT_RELEASE}" && exit 1 ;; \ + esac \ + && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ + && echo "export TOOLCHAIN_ARCH_SHORT=${TOOLCHAIN_ARCH_SHORT}" >> /etc/skia-env \ + && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env \ + && echo "export TOOLCHAIN_VERSION=${TOOLCHAIN_VERSION}" >> /etc/skia-env + +# Download and extract gcc-cross-ports packages from Debian's package pool. +# loongarch64 is only available via cross-compilation packages (gcc-14-cross-ports). +# We fetch: libstdc++-dev, libstdc++6, libgcc-dev, libgcc-s1, libc6-dev, libc6, linux-libc-dev. +RUN . /etc/skia-env \ + && mkdir -p ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH} \ + && APT_REPO=https://deb.debian.org/debian \ + && POOL="${APT_REPO}/pool/main" \ + && PORTS_SUFFIX="-ports" \ + && FETCH_FAILURES=0 \ + && mkdir -p /tmp/cross-pkgs && cd /tmp/cross-pkgs \ + && for pkg in \ + "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++6-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libgcc-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libgcc-s1-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-${TOOLCHAIN_ARCH_SHORT}-cross" \ + "c/cross-toolchain-base${PORTS_SUFFIX}/linux-libc-dev-${TOOLCHAIN_ARCH_SHORT}-cross"; do \ + dir=$(echo $pkg | rev | cut -d/ -f1 | rev) ; \ + base=$(echo $pkg | rev | cut -d/ -f2- | rev) ; \ + url="${POOL}/${base}/" ; \ + echo "Fetching index: $url (looking for ${dir}_*.deb)" ; \ + deb_url=$(curl -fsSL "$url" | grep -o "href=\"${dir}_[^\"]*\.deb\"" | sed 's/href="//;s/"$//' | sort -V | tail -1) ; \ + if [ -n "$deb_url" ]; then \ + echo " Downloading: ${url}${deb_url}" ; \ + curl -fsSL -O "${url}${deb_url}" ; \ + ar x "${deb_url}" ; \ + mkdir -p extract && tar xf data.tar.* -C extract/ ; \ + rm -f control.tar.* data.tar.* debian-binary "${deb_url}" ; \ + else \ + echo " FATAL: No package found for ${dir}" ; \ + FETCH_FAILURES=$((FETCH_FAILURES + 1)) ; \ + fi ; \ + done \ + && if [ "${FETCH_FAILURES}" -gt 0 ]; then \ + echo "ERROR: ${FETCH_FAILURES} package(s) could not be fetched" && exit 1 ; \ + fi \ + && echo "Extracted cross-package contents:" && find extract/usr/${TOOLCHAIN_ARCH}/ -maxdepth 1 -type d \ + && if [ -d extract/usr/${TOOLCHAIN_ARCH} ]; then \ + cp -a extract/usr/${TOOLCHAIN_ARCH}/* ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/ ; \ + fi \ + && if [ -d extract/usr/lib/gcc-cross ]; then \ + mkdir -p ${SYSROOT_DIR}/usr/lib/gcc-cross ; \ + cp -a extract/usr/lib/gcc-cross/* ${SYSROOT_DIR}/usr/lib/gcc-cross/ ; \ + fi \ + && rm -rf /tmp/cross-pkgs \ + && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}" >> /etc/skia-env \ + && echo "export GCC_LIB_DIR=${SYSROOT_DIR}/usr/lib/gcc-cross/${TOOLCHAIN_ARCH}/${TOOLCHAIN_VERSION}" >> /etc/skia-env + +# Create "current" symlink so build.cake can reference c++ headers version-agnostically. +# include/c++/current -> VERSION +RUN . /etc/skia-env \ + && ln -sf ${TOOLCHAIN_VERSION} ${SYSROOT_PATH}/include/c++/current + +# Fix linker scripts to use relative paths. +# Cross-packages contain linker scripts (libc.so, libpthread.so) with absolute paths like +# /usr/loongarch64-linux-gnu/lib/libc.so.6 — but we've relocated them to /sysroot/. +# Strip the absolute prefixes so the linker finds libs relative to the script location. +RUN . /etc/skia-env \ + && for script in \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libc.so \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so.0 \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib64/libc.so \ + ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib64/libpthread.so; do \ + if [ -f "$script" ]; then \ + sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib64/||g; s|/usr/${TOOLCHAIN_ARCH}/lib/||g; s|/lib/${TOOLCHAIN_ARCH}/||g; s|/lib64/||g; s|/lib/||g" "$script" ; \ + echo "Fixed linker script: $script" ; \ + fi ; \ + done + +# Install fontconfig into the sysroot. +# Cross-package layout: headers go to SYSROOT_PATH/include, libs to SYSROOT_PATH/lib. +RUN . /etc/skia-env \ + && mkdir -p /tmp/fontconfig && cd /tmp/fontconfig \ + && case "${SYSROOT_RELEASE}" in \ + trixie) FC_VERSION=2.17.1-5 ;; \ + esac \ + && APT_REPO=https://deb.debian.org/debian \ + && mkdir -p /tmp/fontconfig/extract \ + && for pkg in libfontconfig-dev libfontconfig1; do \ + url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb" ; \ + echo "Downloading $url" ; \ + curl -fSL -o "${pkg}.deb" "$url" ; \ + ar x "${pkg}.deb" ; \ + tar xf data.tar.* -C /tmp/fontconfig/extract/ ; \ + rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ + done \ + && mkdir -p ${SYSROOT_PATH}/include ${SYSROOT_PATH}/lib \ + && cp -a /tmp/fontconfig/extract/usr/include/* ${SYSROOT_PATH}/include/ \ + && if [ -d "/tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}" ]; then \ + cp -a /tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}/* ${SYSROOT_PATH}/lib/ ; \ + fi \ + && rm -rf /tmp/fontconfig + +WORKDIR /work + +COPY ./startup.sh / +RUN chmod +x /startup.sh +ENTRYPOINT [ "/startup.sh" ] diff --git a/scripts/infra/native/linux/docker/loongarch64/startup.sh b/scripts/infra/native/linux/docker/loongarch64/startup.sh new file mode 100644 index 00000000000..512bb431bf7 --- /dev/null +++ b/scripts/infra/native/linux/docker/loongarch64/startup.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -e + +source /etc/skia-env + +exec "$@" From 9b96c0288e942b660d2f8e54204874c8487d5d5b Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 19:44:01 +0200 Subject: [PATCH 21/45] add .dockerignore and build-local.sh for all Docker images - .dockerignore in each dir: only sends Dockerfile + startup.sh to daemon (faster builds, no accidental context bloat) - loongarch64/build-local.sh: new script matching the pattern of other dirs - glibc/build-local.sh: remove loongarch64 case (now separate image) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/alpine/.dockerignore | 4 +++ .../native/linux/docker/bionic/.dockerignore | 4 +++ .../native/linux/docker/glibc/.dockerignore | 4 +++ .../native/linux/docker/glibc/build-local.sh | 6 ++-- .../linux/docker/loongarch64/.dockerignore | 4 +++ .../linux/docker/loongarch64/build-local.sh | 31 +++++++++++++++++++ 6 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 scripts/infra/native/linux/docker/alpine/.dockerignore create mode 100644 scripts/infra/native/linux/docker/bionic/.dockerignore create mode 100644 scripts/infra/native/linux/docker/glibc/.dockerignore create mode 100644 scripts/infra/native/linux/docker/loongarch64/.dockerignore create mode 100755 scripts/infra/native/linux/docker/loongarch64/build-local.sh diff --git a/scripts/infra/native/linux/docker/alpine/.dockerignore b/scripts/infra/native/linux/docker/alpine/.dockerignore new file mode 100644 index 00000000000..45599257251 --- /dev/null +++ b/scripts/infra/native/linux/docker/alpine/.dockerignore @@ -0,0 +1,4 @@ +# Only send Dockerfile and startup.sh to the Docker daemon. +* +!Dockerfile +!startup.sh diff --git a/scripts/infra/native/linux/docker/bionic/.dockerignore b/scripts/infra/native/linux/docker/bionic/.dockerignore new file mode 100644 index 00000000000..45599257251 --- /dev/null +++ b/scripts/infra/native/linux/docker/bionic/.dockerignore @@ -0,0 +1,4 @@ +# Only send Dockerfile and startup.sh to the Docker daemon. +* +!Dockerfile +!startup.sh diff --git a/scripts/infra/native/linux/docker/glibc/.dockerignore b/scripts/infra/native/linux/docker/glibc/.dockerignore new file mode 100644 index 00000000000..45599257251 --- /dev/null +++ b/scripts/infra/native/linux/docker/glibc/.dockerignore @@ -0,0 +1,4 @@ +# Only send Dockerfile and startup.sh to the Docker daemon. +* +!Dockerfile +!startup.sh diff --git a/scripts/infra/native/linux/docker/glibc/build-local.sh b/scripts/infra/native/linux/docker/glibc/build-local.sh index b5265dd550a..5d2eb2e5693 100755 --- a/scripts/infra/native/linux/docker/glibc/build-local.sh +++ b/scripts/infra/native/linux/docker/glibc/build-local.sh @@ -2,7 +2,7 @@ set -ex # Parameters: -# $1 - The target architecture to build for [ arm | arm64 | riscv64 | x86 | x64 | loongarch64 ] +# $1 - The target architecture to build for [ arm | arm64 | riscv64 | x86 | x64 ] # $2+ - Additional arguments to pass to the cake script DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" @@ -13,8 +13,8 @@ EXTRA_ARGS="$@" # Determine sysroot release based on architecture case "$ARCH" in - loongarch64) SYSROOT_RELEASE=trixie ;; - *) SYSROOT_RELEASE=bullseye ;; + riscv64) SYSROOT_RELEASE=trixie ;; + *) SYSROOT_RELEASE=bullseye ;; esac # the docker platform to use diff --git a/scripts/infra/native/linux/docker/loongarch64/.dockerignore b/scripts/infra/native/linux/docker/loongarch64/.dockerignore new file mode 100644 index 00000000000..45599257251 --- /dev/null +++ b/scripts/infra/native/linux/docker/loongarch64/.dockerignore @@ -0,0 +1,4 @@ +# Only send Dockerfile and startup.sh to the Docker daemon. +* +!Dockerfile +!startup.sh diff --git a/scripts/infra/native/linux/docker/loongarch64/build-local.sh b/scripts/infra/native/linux/docker/loongarch64/build-local.sh new file mode 100755 index 00000000000..70d971fc20c --- /dev/null +++ b/scripts/infra/native/linux/docker/loongarch64/build-local.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -ex + +# Build loongarch64 glibc cross-compilation image and run the build. +# No architecture parameter — this image is single-arch. +# +# Parameters: +# $@ - Additional arguments to pass to the cake script + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +EXTRA_ARGS="$@" + +# the docker platform to use +MACHINE_ARCH="$(uname -m)" +case $MACHINE_ARCH in + arm64) PLATFORM=linux/arm64 ; MACHINE_ARCH=aarch64 ;; + *) PLATFORM=linux/amd64 ;; +esac + +# Build the loongarch64 image +(cd "$DIR" && + docker build --tag skiasharp-linux-gnu-cross-loongarch64 \ + --platform=$PLATFORM \ + --build-arg MACHINE_ARCH=$MACHINE_ARCH \ + .) + +(cd "$DIR/../../../../../.." && + docker run --rm --name skiasharp-linux-gnu-cross-loongarch64 --volume $(pwd):/work skiasharp-linux-gnu-cross-loongarch64 /bin/bash -c " \ + dotnet tool restore ; \ + dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=loongarch64 --verifyGlibcMax=2.38 $EXTRA_ARGS ") From c6b45d0662a4159be9444b025117257341d0dabf Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 20:01:43 +0200 Subject: [PATCH 22/45] Fix audit items #2/#3/#5/#6: clean extraction, --sysroot, pinned URLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - glibc/Dockerfile: Extract only the 6 required packages from debootstrap cache instead of all cached .debs. This eliminates the 'Cannot create symlink' errors from base-files and removes the grep filter workaround. - loongarch64/Dockerfile: Replace HTML scraping with hardcoded package URLs and SHA-256 checksums for all 7 cross-packages. Reproducible and verifiable — no more runtime HTML parsing. - loongarch64/Dockerfile: Remove linker-script sed fixup entirely. Instead, export SYSROOT_ROOT=/sysroot so build.cake passes --sysroot=/sysroot to lld, which automatically prepends the sysroot to absolute paths in linker scripts. This is the same mechanism used by the glibc/debootstrap path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/glibc/Dockerfile | 26 +++--- .../linux/docker/loongarch64/Dockerfile | 86 ++++++++----------- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index fe3d6ef55c6..1a07a548053 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -119,11 +119,9 @@ RUN case ${SYSROOT_RELEASE} in \ # --variant=minbase: Only essential packages (dpkg, apt, coreutils). # --include: Add the gcc cross-compilation development packages we need. # -# NOTE: Trixie's base-files creates merged-usr symlinks (bin→usr/bin etc.) -# but debootstrap pre-creates /bin/ as a directory, causing tar to fail with -# "Cannot create symlink: File exists" (exit 2). This is harmless — debootstrap -# still downloads all packages to the cache and creates the symlinks afterward. -# We tolerate ONLY this specific scenario and verify ALL required packages cached. +# debootstrap --foreign downloads all packages to var/cache/apt/archives/ but +# does NOT extract the --include packages. We extract only the specific packages +# we need (not all cached debs) to avoid conflicts with base-files symlinks. ARG SYSROOT_DIR=/sysroot RUN . /etc/skia-env \ && debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ @@ -141,14 +139,20 @@ RUN . /etc/skia-env \ ls ${SYSROOT_DIR}/var/cache/apt/archives/${required_pkg}_*.deb > /dev/null 2>&1 \ || { echo "FATAL: required package '${required_pkg}' not in cache (debootstrap exit ${DEBOOTSTRAP_EXIT})" && exit 1 ; } ; \ done ; \ - echo "All required packages found in cache." ; } \ - && echo "Extracting all cached packages into sysroot..." \ - && for deb in ${SYSROOT_DIR}/var/cache/apt/archives/*.deb; do \ - if [ -f "$deb" ]; then \ + echo "All required packages found in cache — debootstrap exit was non-fatal." ; } \ + && echo "Extracting required packages into sysroot..." \ + && for required_pkg in \ + "libstdc++-${TOOLCHAIN_VERSION}-dev" \ + "libgcc-${TOOLCHAIN_VERSION}-dev" \ + "linux-libc-dev" \ + "libc6-dev" \ + "libc6" \ + "libgcc-s1"; do \ + deb=$(ls ${SYSROOT_DIR}/var/cache/apt/archives/${required_pkg}_*.deb 2>/dev/null | head -1) ; \ + if [ -n "$deb" ]; then \ echo " Extracting: $(basename $deb)" ; \ cd /tmp && ar x "$deb" \ - && tar xf data.tar.* -C ${SYSROOT_DIR}/ 2>&1 \ - | { grep -v "Cannot create symlink" || true ; } \ + && tar xf data.tar.* -C ${SYSROOT_DIR}/ \ && rm -f control.tar.* data.tar.* debian-binary ; \ fi ; \ done \ diff --git a/scripts/infra/native/linux/docker/loongarch64/Dockerfile b/scripts/infra/native/linux/docker/loongarch64/Dockerfile index bcd64604514..f5f854228df 100644 --- a/scripts/infra/native/linux/docker/loongarch64/Dockerfile +++ b/scripts/infra/native/linux/docker/loongarch64/Dockerfile @@ -5,11 +5,12 @@ # and manually extract them to create a sysroot. # # Architecture: -# Azure Linux 3.0 (host) → clang-18 → --sysroot=/sysroot/usr/loongarch64-linux-gnu → libSkiaSharp.so +# Azure Linux 3.0 (host) → clang-18 → --sysroot=/sysroot → libSkiaSharp.so # # The cross-package layout places headers/libs under /usr/{TOOLCHAIN_ARCH}/ rather # than the native /usr/include/ + /usr/lib/{triple}/ layout that debootstrap produces. -# This requires linker-script path fixups since the scripts use absolute paths. +# We set SYSROOT_ROOT=/sysroot so that lld resolves absolute paths in linker scripts +# (e.g., /usr/loongarch64-linux-gnu/lib/libc.so.6) relative to the sysroot automatically. FROM mcr.microsoft.com/azurelinux/base/core:3.0 @@ -84,41 +85,44 @@ RUN TOOLCHAIN_ARCH=loongarch64-linux-gnu \ # Download and extract gcc-cross-ports packages from Debian's package pool. # loongarch64 is only available via cross-compilation packages (gcc-14-cross-ports). -# We fetch: libstdc++-dev, libstdc++6, libgcc-dev, libgcc-s1, libc6-dev, libc6, linux-libc-dev. +# URLs and SHA-256 checksums are pinned for reproducibility — update when Debian releases new versions. RUN . /etc/skia-env \ && mkdir -p ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH} \ - && APT_REPO=https://deb.debian.org/debian \ - && POOL="${APT_REPO}/pool/main" \ - && PORTS_SUFFIX="-ports" \ - && FETCH_FAILURES=0 \ && mkdir -p /tmp/cross-pkgs && cd /tmp/cross-pkgs \ - && for pkg in \ - "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libstdc++6-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libgcc-${TOOLCHAIN_VERSION}-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "g/gcc-${TOOLCHAIN_VERSION}-cross${PORTS_SUFFIX}/libgcc-s1-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-dev-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "c/cross-toolchain-base${PORTS_SUFFIX}/libc6-${TOOLCHAIN_ARCH_SHORT}-cross" \ - "c/cross-toolchain-base${PORTS_SUFFIX}/linux-libc-dev-${TOOLCHAIN_ARCH_SHORT}-cross"; do \ - dir=$(echo $pkg | rev | cut -d/ -f1 | rev) ; \ - base=$(echo $pkg | rev | cut -d/ -f2- | rev) ; \ - url="${POOL}/${base}/" ; \ - echo "Fetching index: $url (looking for ${dir}_*.deb)" ; \ - deb_url=$(curl -fsSL "$url" | grep -o "href=\"${dir}_[^\"]*\.deb\"" | sed 's/href="//;s/"$//' | sort -V | tail -1) ; \ - if [ -n "$deb_url" ]; then \ - echo " Downloading: ${url}${deb_url}" ; \ - curl -fsSL -O "${url}${deb_url}" ; \ - ar x "${deb_url}" ; \ - mkdir -p extract && tar xf data.tar.* -C extract/ ; \ - rm -f control.tar.* data.tar.* debian-binary "${deb_url}" ; \ - else \ - echo " FATAL: No package found for ${dir}" ; \ - FETCH_FAILURES=$((FETCH_FAILURES + 1)) ; \ + && PACKAGES=" \ + https://deb.debian.org/debian/pool/main/g/gcc-14-cross-ports/libstdc++-14-dev-loong64-cross_14.3.0-14cross2_all.deb \ + 81600199ccac5a0a8c019ce973ea4532530a671dd3e4ee3c552a9d5b97f5bffa \ + https://deb.debian.org/debian/pool/main/g/gcc-14-cross-ports/libstdc++6-loong64-cross_14.2.0-19cross1_all.deb \ + 29ea1472a2b1b6c30db334bef7d7ba302db03ca46ff8a243359f6d488c62ef76 \ + https://deb.debian.org/debian/pool/main/g/gcc-14-cross-ports/libgcc-14-dev-loong64-cross_14.3.0-14cross2_all.deb \ + b4c019526c04c8d45ee6c7044446b2a968ab50b96c55600943312802d2d71531 \ + https://deb.debian.org/debian/pool/main/g/gcc-14-cross-ports/libgcc-s1-loong64-cross_14.2.0-19cross1_all.deb \ + a87453248b2a60ce083606d60950154291fc7e347df9c14263adade49fb18c41 \ + https://deb.debian.org/debian/pool/main/c/cross-toolchain-base-ports/libc6-dev-loong64-cross_2.42-10cross1_all.deb \ + 0b47a5a3f41c6d598ed880b38eb8d3b897d3b78ec13d6529d194a390216dc988 \ + https://deb.debian.org/debian/pool/main/c/cross-toolchain-base-ports/libc6-loong64-cross_2.42-10cross1_all.deb \ + fc58b06c95911192f413b785a2399c3e6c72dc302680def6ad0534fa27ece3e6 \ + https://deb.debian.org/debian/pool/main/c/cross-toolchain-base-ports/linux-libc-dev-loong64-cross_6.12.38-1cross1_all.deb \ + b30c0c8a0d1a8480695b498c1791c930df489b94b26d15ccafa37bfc2e37f3ed \ + " \ + && set -- $PACKAGES \ + && while [ $# -ge 2 ]; do \ + url="$1"; expected_sha="$2"; shift 2 ; \ + filename=$(basename "$url") ; \ + echo "Downloading: $filename" ; \ + curl -fsSL -o "$filename" "$url" ; \ + actual_sha=$(sha256sum "$filename" | cut -d' ' -f1) ; \ + if [ "$actual_sha" != "$expected_sha" ]; then \ + echo "FATAL: SHA-256 mismatch for $filename" ; \ + echo " Expected: $expected_sha" ; \ + echo " Actual: $actual_sha" ; \ + exit 1 ; \ fi ; \ + echo " SHA-256 verified: $actual_sha" ; \ + ar x "$filename" ; \ + mkdir -p extract && tar xf data.tar.* -C extract/ ; \ + rm -f control.tar.* data.tar.* debian-binary "$filename" ; \ done \ - && if [ "${FETCH_FAILURES}" -gt 0 ]; then \ - echo "ERROR: ${FETCH_FAILURES} package(s) could not be fetched" && exit 1 ; \ - fi \ && echo "Extracted cross-package contents:" && find extract/usr/${TOOLCHAIN_ARCH}/ -maxdepth 1 -type d \ && if [ -d extract/usr/${TOOLCHAIN_ARCH} ]; then \ cp -a extract/usr/${TOOLCHAIN_ARCH}/* ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/ ; \ @@ -129,6 +133,7 @@ RUN . /etc/skia-env \ fi \ && rm -rf /tmp/cross-pkgs \ && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}" >> /etc/skia-env \ + && echo "export SYSROOT_ROOT=${SYSROOT_DIR}" >> /etc/skia-env \ && echo "export GCC_LIB_DIR=${SYSROOT_DIR}/usr/lib/gcc-cross/${TOOLCHAIN_ARCH}/${TOOLCHAIN_VERSION}" >> /etc/skia-env # Create "current" symlink so build.cake can reference c++ headers version-agnostically. @@ -136,23 +141,6 @@ RUN . /etc/skia-env \ RUN . /etc/skia-env \ && ln -sf ${TOOLCHAIN_VERSION} ${SYSROOT_PATH}/include/c++/current -# Fix linker scripts to use relative paths. -# Cross-packages contain linker scripts (libc.so, libpthread.so) with absolute paths like -# /usr/loongarch64-linux-gnu/lib/libc.so.6 — but we've relocated them to /sysroot/. -# Strip the absolute prefixes so the linker finds libs relative to the script location. -RUN . /etc/skia-env \ - && for script in \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libc.so \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib/libpthread.so.0 \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib64/libc.so \ - ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/lib64/libpthread.so; do \ - if [ -f "$script" ]; then \ - sed -i "s|/usr/${TOOLCHAIN_ARCH}/lib64/||g; s|/usr/${TOOLCHAIN_ARCH}/lib/||g; s|/lib/${TOOLCHAIN_ARCH}/||g; s|/lib64/||g; s|/lib/||g" "$script" ; \ - echo "Fixed linker script: $script" ; \ - fi ; \ - done - # Install fontconfig into the sysroot. # Cross-package layout: headers go to SYSROOT_PATH/include, libs to SYSROOT_PATH/lib. RUN . /etc/skia-env \ From b4c87784a8acc6f27d958ecc39ee61d8e2eeed87 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 20:46:16 +0200 Subject: [PATCH 23/45] Add SHA-256 verification for fontconfig packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin fontconfig .deb checksums in both glibc/ and loongarch64/ Dockerfiles for consistent security posture — every external download is now verified. glibc/ uses a case statement keyed on release_arch to select the correct checksums for each of the 5 architecture × 2 release combinations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/glibc/Dockerfile | 36 ++++++++++++++----- .../linux/docker/loongarch64/Dockerfile | 18 ++++++++-- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 1a07a548053..19f27ae1c87 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -171,7 +171,7 @@ RUN . /etc/skia-env \ ln -sf ${TOOLCHAIN_VERSION} ${SYSROOT_PATH}/include/${TOOLCHAIN_ARCH}/c++/current ; \ fi -# Install fontconfig into the sysroot. +# Install fontconfig into the sysroot with SHA-256 verification. # Download .deb packages from Debian's pool and extract headers + libraries. RUN . /etc/skia-env \ && mkdir -p /tmp/fontconfig \ @@ -180,15 +180,35 @@ RUN . /etc/skia-env \ bullseye) FC_VERSION=2.13.1-4.2 ;; \ trixie) FC_VERSION=2.17.1-5 ;; \ esac \ + && case "${SYSROOT_RELEASE}_${TOOLCHAIN_ARCH_SHORT}" in \ + bullseye_armhf) FC_DEV_SHA256=4e352097b99831f5898b33729602b549f87b88b5d9ceaecfea10ec122f238f92 ; FC_LIB_SHA256=a29995b90aaec6b6bde70799f487b6eb12421e507342ae9035594fd7c1822e1d ;; \ + bullseye_arm64) FC_DEV_SHA256=8872d11ade9fbdc818099ccbc57b8fc06d3d74d33797d6467da60c49b247acc8 ; FC_LIB_SHA256=18b13ef8a46e9d79ba6a6ba2db0c86e42583277b5d47f6942f3223e349c22641 ;; \ + bullseye_i386) FC_DEV_SHA256=0a5205d19e771235de1577cf92413a821d8358c05fdc4e4ddeaef9959ff157db ; FC_LIB_SHA256=373807faea5e24bd23efc1867eeb0dceca322bc906e1b9b4f5a1b7539880b3ed ;; \ + bullseye_amd64) FC_DEV_SHA256=7655d4238ee7e6ced13501006d20986cbf9ff08454a4e502d5aa399f83e28876 ; FC_LIB_SHA256=b92861827627a76e74d6f447a5577d039ef2f95da18af1f29aa98fb96baea4c1 ;; \ + trixie_riscv64) FC_DEV_SHA256=17bba653a4385c870fd0518d8af094b6469524c639d29891bde526686e1fb9fa ; FC_LIB_SHA256=3d08f8a2fc4a325a0a4821848b804ba86b047eab92ce75184ab852b77ba72bde ;; \ + *) echo "No fontconfig SHA-256 for ${SYSROOT_RELEASE}_${TOOLCHAIN_ARCH_SHORT} — add checksum" && exit 1 ;; \ + esac \ && APT_REPO=https://deb.debian.org/debian \ && mkdir -p /tmp/fontconfig/extract \ - && for pkg in libfontconfig-dev libfontconfig1; do \ - url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb"; \ - echo "Downloading $url"; \ - curl -fSL -o "${pkg}.deb" "$url"; \ - ar x "${pkg}.deb" ; \ - tar xf data.tar.* -C /tmp/fontconfig/extract/ ; \ - rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ + && for pkg_info in \ + "libfontconfig-dev ${FC_DEV_SHA256}" \ + "libfontconfig1 ${FC_LIB_SHA256}"; do \ + pkg=$(echo $pkg_info | cut -d' ' -f1) ; \ + expected_sha=$(echo $pkg_info | cut -d' ' -f2) ; \ + url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb"; \ + echo "Downloading $url"; \ + curl -fSL -o "${pkg}.deb" "$url"; \ + actual_sha=$(sha256sum "${pkg}.deb" | cut -d' ' -f1) ; \ + if [ "$actual_sha" != "$expected_sha" ]; then \ + echo "FATAL: SHA-256 mismatch for ${pkg}.deb" ; \ + echo " Expected: $expected_sha" ; \ + echo " Actual: $actual_sha" ; \ + exit 1 ; \ + fi ; \ + echo " SHA-256 verified: $actual_sha" ; \ + ar x "${pkg}.deb" ; \ + tar xf data.tar.* -C /tmp/fontconfig/extract/ ; \ + rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ done \ && cp -a /tmp/fontconfig/extract/* ${SYSROOT_DIR}/ \ && rm -rf /tmp/fontconfig diff --git a/scripts/infra/native/linux/docker/loongarch64/Dockerfile b/scripts/infra/native/linux/docker/loongarch64/Dockerfile index f5f854228df..3509c521be5 100644 --- a/scripts/infra/native/linux/docker/loongarch64/Dockerfile +++ b/scripts/infra/native/linux/docker/loongarch64/Dockerfile @@ -141,8 +141,10 @@ RUN . /etc/skia-env \ RUN . /etc/skia-env \ && ln -sf ${TOOLCHAIN_VERSION} ${SYSROOT_PATH}/include/c++/current -# Install fontconfig into the sysroot. +# Install fontconfig into the sysroot with SHA-256 verification. # Cross-package layout: headers go to SYSROOT_PATH/include, libs to SYSROOT_PATH/lib. +ARG FC_DEV_SHA256=0d3aee5780ecbab1f709036cedd935f2af690bd5392227411d97e6b160537456 +ARG FC_LIB_SHA256=234a8998df450d4cd83f9c0a4a942fe1892b556122ff96fc737e0c74cb9c4421 RUN . /etc/skia-env \ && mkdir -p /tmp/fontconfig && cd /tmp/fontconfig \ && case "${SYSROOT_RELEASE}" in \ @@ -150,10 +152,22 @@ RUN . /etc/skia-env \ esac \ && APT_REPO=https://deb.debian.org/debian \ && mkdir -p /tmp/fontconfig/extract \ - && for pkg in libfontconfig-dev libfontconfig1; do \ + && for pkg_info in \ + "libfontconfig-dev ${FC_DEV_SHA256}" \ + "libfontconfig1 ${FC_LIB_SHA256}"; do \ + pkg=$(echo $pkg_info | cut -d' ' -f1) ; \ + expected_sha=$(echo $pkg_info | cut -d' ' -f2) ; \ url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb" ; \ echo "Downloading $url" ; \ curl -fSL -o "${pkg}.deb" "$url" ; \ + actual_sha=$(sha256sum "${pkg}.deb" | cut -d' ' -f1) ; \ + if [ "$actual_sha" != "$expected_sha" ]; then \ + echo "FATAL: SHA-256 mismatch for ${pkg}.deb" ; \ + echo " Expected: $expected_sha" ; \ + echo " Actual: $actual_sha" ; \ + exit 1 ; \ + fi ; \ + echo " SHA-256 verified: $actual_sha" ; \ ar x "${pkg}.deb" ; \ tar xf data.tar.* -C /tmp/fontconfig/extract/ ; \ rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ From b69911dad564a63681e7041a768d2389f2750863 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 21:01:58 +0200 Subject: [PATCH 24/45] Use --merged-usr for Trixie debootstrap (official fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of relying solely on error tolerance for merged-usr conflicts, pass --merged-usr to debootstrap when targeting Trixie (Debian 13+). This tells debootstrap to create /bin→/usr/bin symlinks first, avoiding the conflict with base-files. Error tolerance kept as fallback safety net. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/infra/native/linux/docker/glibc/Dockerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 19f27ae1c87..1d36e405053 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -118,13 +118,17 @@ RUN case ${SYSROOT_RELEASE} in \ # the unpacked headers + libraries for cross-compilation, not a bootable rootfs. # --variant=minbase: Only essential packages (dpkg, apt, coreutils). # --include: Add the gcc cross-compilation development packages we need. +# --merged-usr: (Trixie only) Create /bin→/usr/bin symlinks first to avoid +# conflicts with base-files. Required for Debian 13+ which enforces merged-usr. # # debootstrap --foreign downloads all packages to var/cache/apt/archives/ but # does NOT extract the --include packages. We extract only the specific packages -# we need (not all cached debs) to avoid conflicts with base-files symlinks. +# we need (not all cached debs) to avoid conflicts with base-files. ARG SYSROOT_DIR=/sysroot RUN . /etc/skia-env \ - && debootstrap --foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ + && DEBOOTSTRAP_OPTS="--foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT}" \ + && case "${SYSROOT_RELEASE}" in trixie) DEBOOTSTRAP_OPTS="${DEBOOTSTRAP_OPTS} --merged-usr" ;; esac \ + && debootstrap ${DEBOOTSTRAP_OPTS} \ --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev,libc6-dev \ ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ || { DEBOOTSTRAP_EXIT=$? ; \ From b80cd667aac10798bf6cb3c6abb014b439d9670b Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 21:06:12 +0200 Subject: [PATCH 25/45] =?UTF-8?q?Remove=20debootstrap=20error=20tolerance?= =?UTF-8?q?=20=E2=80=94=20require=20clean=20exit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit debootstrap must succeed (exit 0). With --merged-usr for Trixie, there is no reason for non-zero exits. If it fails, the build fails immediately rather than trying to recover from a partial state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/glibc/Dockerfile | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 1d36e405053..dfbec7b86ab 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -131,19 +131,6 @@ RUN . /etc/skia-env \ && debootstrap ${DEBOOTSTRAP_OPTS} \ --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev,libc6-dev \ ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ - || { DEBOOTSTRAP_EXIT=$? ; \ - echo "debootstrap exited ${DEBOOTSTRAP_EXIT} — verifying cache..." ; \ - for required_pkg in \ - "libstdc++-${TOOLCHAIN_VERSION}-dev" \ - "libgcc-${TOOLCHAIN_VERSION}-dev" \ - "linux-libc-dev" \ - "libc6-dev" \ - "libc6" \ - "libgcc-s1"; do \ - ls ${SYSROOT_DIR}/var/cache/apt/archives/${required_pkg}_*.deb > /dev/null 2>&1 \ - || { echo "FATAL: required package '${required_pkg}' not in cache (debootstrap exit ${DEBOOTSTRAP_EXIT})" && exit 1 ; } ; \ - done ; \ - echo "All required packages found in cache — debootstrap exit was non-fatal." ; } \ && echo "Extracting required packages into sysroot..." \ && for required_pkg in \ "libstdc++-${TOOLCHAIN_VERSION}-dev" \ @@ -153,12 +140,13 @@ RUN . /etc/skia-env \ "libc6" \ "libgcc-s1"; do \ deb=$(ls ${SYSROOT_DIR}/var/cache/apt/archives/${required_pkg}_*.deb 2>/dev/null | head -1) ; \ - if [ -n "$deb" ]; then \ - echo " Extracting: $(basename $deb)" ; \ - cd /tmp && ar x "$deb" \ - && tar xf data.tar.* -C ${SYSROOT_DIR}/ \ - && rm -f control.tar.* data.tar.* debian-binary ; \ + if [ -z "$deb" ]; then \ + echo "FATAL: required package '${required_pkg}' not found in cache" && exit 1 ; \ fi ; \ + echo " Extracting: $(basename $deb)" ; \ + cd /tmp && ar x "$deb" \ + && tar xf data.tar.* -C ${SYSROOT_DIR}/ \ + && rm -f control.tar.* data.tar.* debian-binary ; \ done \ && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env \ && echo "export SYSROOT_ROOT=${SYSROOT_DIR}" >> /etc/skia-env \ From 93386214cc7d8902de608de537a2cf95970153a3 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 21:06:52 +0200 Subject: [PATCH 26/45] Always pass --merged-usr to debootstrap (no conditional) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --merged-usr is supported since Debian 10 (Buster) and works fine with Bullseye. No need for conditional logic — just always pass it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/infra/native/linux/docker/glibc/Dockerfile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index dfbec7b86ab..5f3ea0022b3 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -118,17 +118,14 @@ RUN case ${SYSROOT_RELEASE} in \ # the unpacked headers + libraries for cross-compilation, not a bootable rootfs. # --variant=minbase: Only essential packages (dpkg, apt, coreutils). # --include: Add the gcc cross-compilation development packages we need. -# --merged-usr: (Trixie only) Create /bin→/usr/bin symlinks first to avoid -# conflicts with base-files. Required for Debian 13+ which enforces merged-usr. +# --merged-usr: Create /bin→/usr/bin symlinks before unpacking (supported since Debian 10). # # debootstrap --foreign downloads all packages to var/cache/apt/archives/ but # does NOT extract the --include packages. We extract only the specific packages # we need (not all cached debs) to avoid conflicts with base-files. ARG SYSROOT_DIR=/sysroot RUN . /etc/skia-env \ - && DEBOOTSTRAP_OPTS="--foreign --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT}" \ - && case "${SYSROOT_RELEASE}" in trixie) DEBOOTSTRAP_OPTS="${DEBOOTSTRAP_OPTS} --merged-usr" ;; esac \ - && debootstrap ${DEBOOTSTRAP_OPTS} \ + && debootstrap --foreign --merged-usr --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev,libc6-dev \ ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ && echo "Extracting required packages into sysroot..." \ From c8391b771213dedbd5e8a4f2246b9e89d5cc803d Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 22:02:17 +0200 Subject: [PATCH 27/45] Pivot to .NET official cross-compilation Docker images Replace custom Docker build infrastructure with thin layers on top of mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-. What the .NET images provide (eliminating ~90% of our Dockerfile code): - Azure Linux 3.0 host (clean CG surface) - Clang 20 + lld (built from source, GPG-verified) - Complete sysroots with glibc/musl headers, libstdc++ headers - Cross-compiled static libc++ and compiler-rt - cmake, ninja-build, git, python3 What we add on top (~30-40 lines per Dockerfile): - .NET SDK for Cake build scripts - fontconfig-dev headers in sysroot (SHA-256 verified) - /etc/skia-env toolchain variables for build.cake - c++/current symlinks for version-agnostic header paths Key changes: - Switch from -static-libstdc++ to -stdlib=libc++ (uses .NET's cross-compiled libc++ instead of old distro's libstdc++.a) - Sysroot path: /sysroot -> /crossrootfs/ - Compiler: clang-18 (binary download) -> clang-20 (pre-built in image) - Delete loongarch64/Dockerfile (absorbed into glibc with BASE_TAG arg) - Android: use .NET's NDK 27.2 image instead of custom NDK download glibc targets per arch: - arm64/x86/x64: bionic (glibc 2.27) - arm: jammy (glibc 2.35, 64-bit time_t) - riscv64: noble (glibc 2.39) - loongarch64: sid Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux/build.cake | 8 +- .../native/linux/docker/alpine/Dockerfile | 134 +++------- .../native/linux/docker/alpine/build-local.sh | 63 ++++- .../native/linux/docker/bionic/Dockerfile | 73 ++--- .../native/linux/docker/bionic/build-local.sh | 10 +- .../native/linux/docker/glibc/Dockerfile | 252 ++++++------------ .../native/linux/docker/glibc/build-local.sh | 98 +++++-- .../linux/docker/loongarch64/.dockerignore | 4 - .../linux/docker/loongarch64/Dockerfile | 186 ------------- .../linux/docker/loongarch64/build-local.sh | 25 +- .../linux/docker/loongarch64/startup.sh | 6 - 11 files changed, 285 insertions(+), 574 deletions(-) delete mode 100644 scripts/infra/native/linux/docker/loongarch64/.dockerignore delete mode 100644 scripts/infra/native/linux/docker/loongarch64/Dockerfile delete mode 100644 scripts/infra/native/linux/docker/loongarch64/startup.sh diff --git a/native/linux/build.cake b/native/linux/build.cake index 6fbc3c008a1..f290cae76a3 100644 --- a/native/linux/build.cake +++ b/native/linux/build.cake @@ -112,8 +112,8 @@ Task("libSkiaSharp") $"skia_use_vulkan=true " + bionicArgs + $"extra_asmflags=[] " + - $"extra_cflags=[ '-DSKIA_C_DLL', '-DHAVE_SYSCALL_GETRANDOM', '-DXML_DEV_URANDOM'{spectreFlags}{wordSizeDefine}{bionicDefine} ] " + - $"extra_ldflags=[ '-static-libstdc++', '-static-libgcc', '-Wl,--version-script={map}' ] " + + $"extra_cflags=[ '-DSKIA_C_DLL', '-DHAVE_SYSCALL_GETRANDOM', '-DXML_DEV_URANDOM', '-stdlib=libc++'{spectreFlags}{wordSizeDefine}{bionicDefine} ] " + + $"extra_ldflags=[ '-stdlib=libc++', '-static-libgcc', '-Wl,--version-script={map}' ] " + COMPILERS + $"linux_soname_version='{soname}' " + ADDITIONAL_GN_ARGS); @@ -146,8 +146,8 @@ Task("libHarfBuzzSharp") $"target_cpu='{skiaArch}' " + $"visibility_hidden=false " + $"extra_asmflags=[] " + - $"extra_cflags=[ {(VARIANT.ToLower().StartsWith("bionic") ? "'-DSK_BUILD_FOR_UNIX'" : "")} ] " + - $"extra_ldflags=[ '-static-libstdc++', '-static-libgcc', '-Wl,--version-script={map}' ] " + + $"extra_cflags=[ '-stdlib=libc++'{(VARIANT.ToLower().StartsWith("bionic") ? ", '-DSK_BUILD_FOR_UNIX'" : "")} ] " + + $"extra_ldflags=[ '-stdlib=libc++', '-static-libgcc', '-Wl,--version-script={map}' ] " + COMPILERS + $"linux_soname_version='{soname}' " + ADDITIONAL_GN_ARGS); diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index bd38cb6f54b..f8522e651b8 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -1,60 +1,22 @@ # musl (Alpine) cross-compilation image. # -# Uses apk.static to create an Alpine sysroot, same concept as glibc image. -# Host is Azure Linux 3.0; sysroot is Alpine 3.21 for musl target. +# Thin layer on top of .NET's official musl cross-compilation images which provide: +# - Azure Linux 3.0 host (clean CG surface) +# - Clang 20 + lld (built from source, GPG-verified) +# - Complete Alpine sysroot with musl headers, libstdc++ headers, kernel headers +# - Cross-compiled libc++ (static) # -# musl is forward-compatible: binaries built against 3.21 run on 3.22+. -# musl is NOT backward-compatible: cannot target 3.17 from 3.21 headers. +# We add: .NET SDK (Cake), fontconfig-dev from Alpine repos, skia-env toolchain vars. # -# Alpine 3.21 (musl 1.2.5) is the minimum supported — 3.17-3.20 are all EOL. -# .NET 10 officially supports only Alpine 3.21+. +# The base images use Alpine 3.17 (musl 1.2.3). musl is forward-compatible: +# binaries built against 3.17 run on any later Alpine version. -FROM mcr.microsoft.com/azurelinux/base/core:3.0 +ARG BUILD_ARCH=arm64 +ARG BASE_TAG=azurelinux-3.0-net10.0-cross-arm64-musl +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:${BASE_TAG} -# Use bash with strict error handling for all RUN commands. SHELL ["/bin/bash", "-euo", "pipefail", "-c"] -# Install host build tools from Azure Linux packages. -# NOTE: We do NOT install clang/lld/llvm from tdnf because the Azure Linux -# LLVM package only includes host+AMDGPU+BPF targets. We need ARM, AArch64, -# RISCV, LoongArch, and X86 backends for cross-compilation. -RUN tdnf install -y \ - ninja-build \ - python3 \ - git \ - curl \ - tar \ - xz \ - gawk \ - ca-certificates \ - binutils \ - libxml2 \ - ncurses-libs \ - zlib \ - libffi \ - && tdnf clean all - -# Install official LLVM 18 release with all target backends. -# SHA-256 checksums computed from official GitHub releases (llvm/llvm-project). -ARG LLVM_VERSION=18.1.8 -ARG MACHINE_ARCH=x86_64 -ARG LLVM_SHA256_X86_64=54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36 -ARG LLVM_SHA256_AARCH64=dcaa1bebbfbb86953fdfbdc7f938800229f75ad26c5c9375ef242edad737d999 -RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ - && ln -sf /usr/lib64/libncursesw.so.6 /usr/lib64/libncurses.so.5 \ - && case "${MACHINE_ARCH}" in \ - x86_64) LLVM_SUFFIX="x86_64-linux-gnu-ubuntu-18.04" ; LLVM_SHA256="${LLVM_SHA256_X86_64}" ;; \ - aarch64) LLVM_SUFFIX="aarch64-linux-gnu" ; LLVM_SHA256="${LLVM_SHA256_AARCH64}" ;; \ - *) echo "Unsupported MACHINE_ARCH: ${MACHINE_ARCH}" && exit 1 ;; \ - esac \ - && curl -fSL -o /tmp/llvm.tar.xz \ - "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${LLVM_SUFFIX}.tar.xz" \ - && echo "${LLVM_SHA256} /tmp/llvm.tar.xz" | sha256sum -c - \ - && tar xJf /tmp/llvm.tar.xz --strip-components=1 -C /usr/local \ - && rm -f /tmp/llvm.tar.xz \ - && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ - && clang --version && ld.lld --version && llvm-ar --version - # Install the .NET SDK (needed for Cake build scripts). ARG DOTNET_SDK_VERSION=10.0.108 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 @@ -67,72 +29,44 @@ RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-inst ENV CC=clang CXX=clang++ -ARG BUILD_ARCH=arm64 +# Toolchain configuration for build.cake. +ARG TOOLCHAIN_ARCH=aarch64-alpine-linux-musl +ARG TOOLCHAIN_ARCH_TARGET=aarch64-alpine-linux-musl +ARG APK_ARCH=aarch64 +ARG ROOTFS_DIR=/crossrootfs/arm64 -# Map BUILD_ARCH to Alpine triple and apk architecture -RUN case ${BUILD_ARCH} in \ - arm) TOOLCHAIN_ARCH=armv7-alpine-linux-musleabihf ; TOOLCHAIN_ARCH_TARGET=armv7-alpine-linux-musleabihf ; APK_ARCH=armv7 ;; \ - arm64) TOOLCHAIN_ARCH=aarch64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=aarch64-alpine-linux-musl ; APK_ARCH=aarch64 ;; \ - loongarch64) TOOLCHAIN_ARCH=loongarch64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=loongarch64-alpine-linux-musl ; APK_ARCH=loongarch64 ;; \ - riscv64) TOOLCHAIN_ARCH=riscv64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=riscv64-alpine-linux-musl ; APK_ARCH=riscv64 ;; \ - x86) TOOLCHAIN_ARCH=i586-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=i586-alpine-linux-musl ; APK_ARCH=x86 ;; \ - x64) TOOLCHAIN_ARCH=x86_64-alpine-linux-musl ; TOOLCHAIN_ARCH_TARGET=x86_64-alpine-linux-musl ; APK_ARCH=x86_64 ;; \ - *) echo "Unsupported architecture: ${BUILD_ARCH}" && exit 1 ;; \ - esac \ - && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ +RUN echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env \ - && echo "export APK_ARCH=${APK_ARCH}" >> /etc/skia-env \ - && echo "export SYSROOT_PATH=/alpine/usr" >> /etc/skia-env + && echo "export SYSROOT_PATH=${ROOTFS_DIR}/usr" >> /etc/skia-env + +# Create c++/current symlink and discover GCC lib dir. +RUN GCC_VER=$(basename "$(find ${ROOTFS_DIR}/usr/include/c++/ -mindepth 1 -maxdepth 1 -type d | sort -V | head -n1)") \ + && ln -sf ${GCC_VER} ${ROOTFS_DIR}/usr/include/c++/current \ + && echo "export GCC_LIB_DIR=${ROOTFS_DIR}/usr/lib/gcc/${TOOLCHAIN_ARCH}/${GCC_VER}" >> /etc/skia-env -# Create Alpine sysroot using apk.static. -# Only install the minimal packages needed for cross-compilation: -# musl-dev: C library headers and runtime -# linux-headers: kernel headers -# fontconfig-dev: font configuration headers and library -# libstdc++-dev: C++ standard library headers (needed by Skia) -# Avoids build-base which pulls in busybox (~35 CVEs) and binutils (~15 CVEs). -# SHA-256 of apk.static pinned for supply-chain integrity. +# Install fontconfig-dev into the sysroot from Alpine repos. +# The base image already has apk infrastructure in the sysroot, so we can +# use apk.static to add packages. If apk.static is not available, download it. +ARG APK_STATIC_SHA256=cd282c693059c39b68ce0575fbdc4e0c162af85ed633a7834f9cfd01b8b1d58b ARG DISTRO_VERSION=3.21 -ARG APK_STATIC_SHA256_X86_64=cd282c693059c39b68ce0575fbdc4e0c162af85ed633a7834f9cfd01b8b1d58b -ARG APK_STATIC_SHA256_AARCH64=77ebdbb193aaa605e10610586d443909331872799ac78648dff1216822d76299 -RUN . /etc/skia-env \ - && APK_DIR="$(mktemp -d)" \ - && case "${MACHINE_ARCH}" in \ - x86_64) APK_STATIC_SHA256="${APK_STATIC_SHA256_X86_64}" ;; \ - aarch64) APK_STATIC_SHA256="${APK_STATIC_SHA256_AARCH64}" ;; \ - esac \ +RUN APK_DIR="$(mktemp -d)" \ && curl -fSL --retry 5 --retry-delay 10 --retry-all-errors \ -o "$APK_DIR/apk.static" \ - "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.12.14/$MACHINE_ARCH/apk.static" \ + "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.12.14/x86_64/apk.static" \ && echo "${APK_STATIC_SHA256} $APK_DIR/apk.static" | sha256sum -c - \ && chmod +x "$APK_DIR/apk.static" \ - && mkdir -p /alpine/etc/apk/keys \ - && curl -fsSL -o /alpine/etc/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub \ + && mkdir -p ${ROOTFS_DIR}/etc/apk/keys \ + && curl -fsSL -o ${ROOTFS_DIR}/etc/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub \ "https://alpinelinux.org/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub" \ - && curl -fsSL -o /alpine/etc/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub \ + && curl -fsSL -o ${ROOTFS_DIR}/etc/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub \ "https://alpinelinux.org/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub" \ && "$APK_DIR/apk.static" \ -X "https://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ -X "https://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ - -U --keys-dir /alpine/etc/apk/keys --root /alpine --arch "$APK_ARCH" --initdb add \ - && "$APK_DIR/apk.static" \ - -X "https://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ - -X "https://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ - -U --keys-dir /alpine/etc/apk/keys --root /alpine --arch "$APK_ARCH" --no-scripts \ - add musl-dev linux-headers fontconfig-dev libstdc++-dev gcc \ + -U --keys-dir ${ROOTFS_DIR}/etc/apk/keys --root ${ROOTFS_DIR} --arch "$APK_ARCH" --no-scripts \ + add fontconfig-dev \ && rm -rf "$APK_DIR" -# Create c++/current symlink and discover GCC lib dir. -# Alpine installs headers at /alpine/usr/include/c++// (e.g. 14.2.0). -# We need the version to locate the GCC lib directory for CRT files. -RUN INCLUDE_DIR=/alpine/usr/include/c++ \ - && GCC_VERSION=$(ls ${INCLUDE_DIR}/ | grep -E '^[0-9]' | sort -V | tail -1) \ - && if [ -z "$GCC_VERSION" ]; then echo "ERROR: No GCC version in ${INCLUDE_DIR}" && exit 1 ; fi \ - && ln -sf ${GCC_VERSION} ${INCLUDE_DIR}/current \ - && . /etc/skia-env \ - && echo "export GCC_LIB_DIR=/alpine/usr/lib/gcc/${TOOLCHAIN_ARCH}/${GCC_VERSION}" >> /etc/skia-env \ - && echo "Created: ${INCLUDE_DIR}/current -> ${GCC_VERSION}" - WORKDIR /work COPY ./startup.sh / diff --git a/scripts/infra/native/linux/docker/alpine/build-local.sh b/scripts/infra/native/linux/docker/alpine/build-local.sh index a3efb14bc41..3b464bfa6d8 100755 --- a/scripts/infra/native/linux/docker/alpine/build-local.sh +++ b/scripts/infra/native/linux/docker/alpine/build-local.sh @@ -2,7 +2,7 @@ set -ex # Parameters: -# $1 - The target architecture to build for [ arm | arm64 | riscv64 | x64 | loongarch64 ] +# $1 - The target architecture to build for [ arm | arm64 | riscv64 | loongarch64 | x86 | x64 ] # $2+ - Additional arguments to pass to the cake script DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" @@ -11,19 +11,66 @@ ARCH="${1:-arm64}" shift 1 || true EXTRA_ARGS="$@" -# the docker platform to use -MACHINE_ARCH="$(uname -m)" -case $MACHINE_ARCH in - arm64) PLATFORM=linux/arm64 ; MACHINE_ARCH=aarch64 ;; - *) PLATFORM=linux/amd64 ;; +# Map architecture to .NET musl cross image tag and toolchain settings +case "$ARCH" in + arm) + BASE_TAG=azurelinux-3.0-net10.0-cross-arm-musl + TOOLCHAIN_ARCH=armv7-alpine-linux-musleabihf + TOOLCHAIN_ARCH_TARGET=armv7-alpine-linux-musleabihf + APK_ARCH=armv7 + ROOTFS_DIR=/crossrootfs/arm + ;; + arm64) + BASE_TAG=azurelinux-3.0-net10.0-cross-arm64-musl + TOOLCHAIN_ARCH=aarch64-alpine-linux-musl + TOOLCHAIN_ARCH_TARGET=aarch64-alpine-linux-musl + APK_ARCH=aarch64 + ROOTFS_DIR=/crossrootfs/arm64 + ;; + riscv64) + BASE_TAG=azurelinux-3.0-net10.0-cross-riscv64-musl + TOOLCHAIN_ARCH=riscv64-alpine-linux-musl + TOOLCHAIN_ARCH_TARGET=riscv64-alpine-linux-musl + APK_ARCH=riscv64 + ROOTFS_DIR=/crossrootfs/riscv64 + ;; + loongarch64) + BASE_TAG=azurelinux-3.0-net10.0-cross-loongarch64-musl + TOOLCHAIN_ARCH=loongarch64-alpine-linux-musl + TOOLCHAIN_ARCH_TARGET=loongarch64-alpine-linux-musl + APK_ARCH=loongarch64 + ROOTFS_DIR=/crossrootfs/loongarch64 + ;; + x86) + BASE_TAG=azurelinux-3.0-net10.0-cross-x86-musl + TOOLCHAIN_ARCH=i586-alpine-linux-musl + TOOLCHAIN_ARCH_TARGET=i586-alpine-linux-musl + APK_ARCH=x86 + ROOTFS_DIR=/crossrootfs/x86 + ;; + x64) + BASE_TAG=azurelinux-3.0-net10.0-cross-amd64-musl + TOOLCHAIN_ARCH=x86_64-alpine-linux-musl + TOOLCHAIN_ARCH_TARGET=x86_64-alpine-linux-musl + APK_ARCH=x86_64 + ROOTFS_DIR=/crossrootfs/x64 + ;; + *) + echo "Unsupported architecture: $ARCH" + exit 1 + ;; esac # Build the alpine image (cd "$DIR" && docker build --tag skiasharp-linux-musl-cross-$ARCH \ - --platform=$PLATFORM \ + --platform=linux/amd64 \ + --build-arg BASE_TAG=$BASE_TAG \ --build-arg BUILD_ARCH=$ARCH \ - --build-arg MACHINE_ARCH=$MACHINE_ARCH \ + --build-arg TOOLCHAIN_ARCH=$TOOLCHAIN_ARCH \ + --build-arg TOOLCHAIN_ARCH_TARGET=$TOOLCHAIN_ARCH_TARGET \ + --build-arg APK_ARCH=$APK_ARCH \ + --build-arg ROOTFS_DIR=$ROOTFS_DIR \ .) (cd "$DIR/../../../../../.." && diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index db38b9af941..e43e2976686 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -1,32 +1,19 @@ # Android NDK cross-compilation image. # -# The NDK provides its own Bionic sysroot and clang toolchain — no debootstrap needed. -# Host is Azure Linux 3.0 for consistent CG surface across all Linux build images. +# Thin layer on top of .NET's official Android image which provides: +# - Azure Linux 3.0 host (clean CG surface) +# - Android NDK 27.2 with SDK tools +# - Clang 20 + lld # -# The NDK's own clang is used for compilation (not the host clang-18), since the +# We add: .NET SDK (Cake), skia-env toolchain vars, libpthread stub. +# +# The NDK's own clang is used for compilation (not the host clang), since the # NDK bundles Android-specific runtime support (compiler-rt, libc++, etc.). -FROM mcr.microsoft.com/azurelinux/base/core:3.0 +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-android-amd64 -# Use bash with strict error handling for all RUN commands. SHELL ["/bin/bash", "-euo", "pipefail", "-c"] -# Install host build tools from Azure Linux packages. -# Bionic doesn't need clang/lld from host — NDK provides its own. -# But we need python3, git, ninja for Skia's GN build system and unzip for NDK. -RUN tdnf install -y \ - ninja-build \ - python3 \ - git \ - curl \ - tar \ - xz \ - unzip \ - gawk \ - ca-certificates \ - binutils \ - && tdnf clean all - # Install the .NET SDK (needed for Cake build scripts). ARG DOTNET_SDK_VERSION=10.0.108 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 @@ -39,34 +26,29 @@ RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-inst ARG BUILD_ARCH=x64 -# Map BUILD_ARCH to Android triple and sysroot lib directory +# Map BUILD_ARCH to Android triple and configure NDK paths. +# NDK 27.2 is at /usr/local/android-sdk/ndk/27.2.12479018 +ARG NDK_VERSION=27.2.12479018 RUN case ${BUILD_ARCH} in \ x64) ANDROID_TRIPLE=x86_64-linux-android29 ; ANDROID_SYSROOT_LIB=x86_64-linux-android ;; \ arm64) ANDROID_TRIPLE=aarch64-linux-android29 ; ANDROID_SYSROOT_LIB=aarch64-linux-android ;; \ *) echo "Unsupported architecture: ${BUILD_ARCH}" && exit 1 ;; \ esac \ + && NDK_ROOT="/usr/local/android-sdk/ndk/${NDK_VERSION}" \ + && NDK_HOST_TAG="linux-x86_64" \ && echo "export ANDROID_TRIPLE=${ANDROID_TRIPLE}" > /etc/skia-env \ - && echo "export ANDROID_SYSROOT_LIB=${ANDROID_SYSROOT_LIB}" >> /etc/skia-env - -# Install Android NDK r26b with SHA-256 verification. -ARG NDK_VERSION=r26b -ARG NDK_SHA256=ad73c0370f0b0a87d1671ed2fd5a9ac9acfd1eb5c43a7fbfbd330f85d19dd632 -ARG MACHINE_ARCH=x86_64 -RUN curl -fSL -o /tmp/ndk.zip \ - "https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux.zip" \ - && echo "${NDK_SHA256} /tmp/ndk.zip" | sha256sum -c - \ - && unzip -q /tmp/ndk.zip -d /opt \ - && rm /tmp/ndk.zip -ENV ANDROID_NDK_HOME=/opt/android-ndk-r26b + && echo "export ANDROID_SYSROOT_LIB=${ANDROID_SYSROOT_LIB}" >> /etc/skia-env \ + && echo "export NDK_HOST_TAG=${NDK_HOST_TAG}" >> /etc/skia-env \ + && echo "export ANDROID_NDK_HOME=${NDK_ROOT}" >> /etc/skia-env \ + && echo "export CC=${ANDROID_TRIPLE}-clang" >> /etc/skia-env \ + && echo "export CXX=${ANDROID_TRIPLE}-clang++" >> /etc/skia-env \ + && echo "export AR=llvm-ar" >> /etc/skia-env \ + && echo "export LD=lld" >> /etc/skia-env -# Determine NDK host platform (supports x86_64 and aarch64 build agents). -RUN case "${MACHINE_ARCH}" in \ - x86_64) NDK_HOST_TAG="linux-x86_64" ;; \ - aarch64) NDK_HOST_TAG="linux-aarch64" ;; \ - *) echo "Unsupported MACHINE_ARCH for NDK: ${MACHINE_ARCH}" && exit 1 ;; \ - esac \ - && echo "export NDK_HOST_TAG=${NDK_HOST_TAG}" >> /etc/skia-env -ENV PATH=$PATH:/opt/android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin +# Add NDK toolchain to PATH +ARG NDK_VERSION_FOR_PATH=27.2.12479018 +ENV ANDROID_NDK_HOME=/usr/local/android-sdk/ndk/${NDK_VERSION_FOR_PATH} +ENV PATH=$PATH:/usr/local/android-sdk/ndk/${NDK_VERSION_FOR_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin # Create empty libpthread stub - Bionic bundles pthreads into libc, but # Skia's GN build adds -lpthread for target_os='linux' builds. @@ -78,13 +60,6 @@ RUN . /etc/skia-env \ fi \ && printf '!\n' > "$PTHREAD_TARGET" -# Set cross-compiler to NDK clang -RUN . /etc/skia-env \ - && echo "export CC=${ANDROID_TRIPLE}-clang" >> /etc/skia-env \ - && echo "export CXX=${ANDROID_TRIPLE}-clang++" >> /etc/skia-env \ - && echo "export AR=llvm-ar" >> /etc/skia-env \ - && echo "export LD=lld" >> /etc/skia-env - WORKDIR /work COPY ./startup.sh / diff --git a/scripts/infra/native/linux/docker/bionic/build-local.sh b/scripts/infra/native/linux/docker/bionic/build-local.sh index b0c60578768..c4fd4f7fe43 100755 --- a/scripts/infra/native/linux/docker/bionic/build-local.sh +++ b/scripts/infra/native/linux/docker/bionic/build-local.sh @@ -8,14 +8,10 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" ARCH="${1:-x64}" -# Use linux/amd64 for Docker on Apple Silicon because the Android NDK only -# ships linux-x86_64 host tools. -PLATFORM_ARGS="" -if [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then - PLATFORM_ARGS="--platform linux/amd64" -fi +# The .NET Android image is x86_64 only (NDK ships linux-x86_64 host tools) +PLATFORM_ARGS="--platform linux/amd64" -# Build the bionic image +# Build the android image (cd "$DIR" && docker build $PLATFORM_ARGS --tag "skiasharp-bionic-$ARCH" \ --build-arg BUILD_ARCH=$ARCH \ diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 5f3ea0022b3..58e3ec9bc1c 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -1,77 +1,36 @@ # glibc cross-compilation image. # -# Uses debootstrap to create a Debian sysroot, then compiles with --sysroot. -# The sysroot's glibc version determines the minimum target distro. -# The host's glibc version is IRRELEVANT — only the sysroot matters. +# Thin layer on top of .NET's official cross-compilation images which provide: +# - Azure Linux 3.0 host (clean CG surface) +# - Clang 20 + lld (built from source, GPG-verified) +# - Complete sysroot with glibc headers, libstdc++ headers, kernel headers +# - Cross-compiled libc++ (static) and compiler-rt # -# Architecture: -# Azure Linux 3.0 (host) → clang-18 → --sysroot=/sysroot → libSkiaSharp.so -# -# Replaces: debian/11/Dockerfile (glibc 2.31) and debian/13/Dockerfile (glibc 2.38) +# We add: .NET SDK (Cake), fontconfig-dev headers, skia-env toolchain vars. # -# | SYSROOT_RELEASE | glibc | libstdc++ | Supported distros | -# |-----------------|-------|-----------|--------------------------------------| -# | bullseye (11) | 2.31 | GCC 10 | Debian 11+, Ubuntu 20.04+, RHEL 9+ | -# | trixie (13) | 2.38 | GCC 14 | Debian 13+, Ubuntu 24.04+ | +# Architecture: +# .NET cross image (host) → clang-20 → --sysroot=/crossrootfs/ → libSkiaSharp.so # -# Bullseye is the oldest with GCC 10 libstdc++ (C++20: std::span, std::countl_zero). +# Base images use these sysroot distros: +# | arch | sysroot distro | glibc | Supported distros | +# |---------|----------------|-------|--------------------------------------| +# | arm64 | bionic | 2.27 | Debian 10+, Ubuntu 18.04+, RHEL 8+ | +# | arm | jammy | 2.35 | Debian 12+, Ubuntu 22.04+, RHEL 9+ | +# | x86 | bionic | 2.27 | Debian 10+, Ubuntu 18.04+ | +# | x64 | bionic | 2.27 | Debian 10+, Ubuntu 18.04+, RHEL 8+ | +# | riscv64 | noble | 2.39 | Debian 13+, Ubuntu 24.04+ | +# | loong64 | sid | latest| Debian unstable | # -# Supported architectures: arm, arm64, x86, x64, riscv64 -# For loongarch64, see ../loongarch64/Dockerfile (cross-package extraction). +# Supported architectures: arm, arm64, x86, x64, riscv64, loongarch64 -FROM mcr.microsoft.com/azurelinux/base/core:3.0 +# Map BUILD_ARCH to the .NET cross image tag. +ARG BUILD_ARCH=arm64 +ARG BASE_TAG=azurelinux-3.0-net10.0-cross-arm64 +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:${BASE_TAG} -# Use bash with strict error handling for all RUN commands. -# -e: exit on error, -u: error on undefined vars, -o pipefail: pipe failures propagate. SHELL ["/bin/bash", "-euo", "pipefail", "-c"] -# Install host build tools from Azure Linux packages. -# NOTE: We do NOT install clang/lld/llvm from tdnf because the Azure Linux -# LLVM package only includes host+AMDGPU+BPF targets. We need ARM, AArch64, -# RISCV, LoongArch, and X86 backends for cross-compilation. -RUN tdnf install -y \ - ninja-build \ - python3 \ - git \ - curl \ - tar \ - xz \ - gawk \ - ca-certificates \ - binutils \ - debootstrap \ - libxml2 \ - ncurses-libs \ - zlib \ - libffi \ - && tdnf clean all - -# Install official LLVM 18 release with all target backends. -# Azure Linux's LLVM only has "host;AMDGPU;BPF" — we need all cross targets. -# The release binary needs libtinfo.so.5 (built on Ubuntu 18.04) but Azure Linux -# only has libtinfo.so.6 — create a compat symlink (ABI compatible for our use). -# SHA-256 checksums computed from official GitHub releases (llvm/llvm-project). -ARG LLVM_VERSION=18.1.8 -ARG MACHINE_ARCH=x86_64 -ARG LLVM_SHA256_X86_64=54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36 -ARG LLVM_SHA256_AARCH64=dcaa1bebbfbb86953fdfbdc7f938800229f75ad26c5c9375ef242edad737d999 -RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ - && ln -sf /usr/lib64/libncursesw.so.6 /usr/lib64/libncurses.so.5 \ - && case "${MACHINE_ARCH}" in \ - x86_64) LLVM_SUFFIX="x86_64-linux-gnu-ubuntu-18.04" ; LLVM_SHA256="${LLVM_SHA256_X86_64}" ;; \ - aarch64) LLVM_SUFFIX="aarch64-linux-gnu" ; LLVM_SHA256="${LLVM_SHA256_AARCH64}" ;; \ - *) echo "Unsupported MACHINE_ARCH: ${MACHINE_ARCH}" && exit 1 ;; \ - esac \ - && curl -fSL -o /tmp/llvm.tar.xz \ - "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${LLVM_SUFFIX}.tar.xz" \ - && echo "${LLVM_SHA256} /tmp/llvm.tar.xz" | sha256sum -c - \ - && tar xJf /tmp/llvm.tar.xz --strip-components=1 -C /usr/local \ - && rm -f /tmp/llvm.tar.xz \ - && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ - && clang --version && ld.lld --version && llvm-ar --version - # Install the .NET SDK (needed for Cake build scripts). -# Pin to exact version matching global.json for reproducible builds. ARG DOTNET_SDK_VERSION=10.0.108 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ @@ -83,124 +42,65 @@ RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-inst ENV CC=clang CXX=clang++ -ARG BUILD_ARCH=arm64 +# Toolchain configuration for build.cake. +# These map to the env vars expected by native/linux-clang-cross/build.cake. +ARG TOOLCHAIN_ARCH=aarch64-linux-gnu +ARG TOOLCHAIN_ARCH_SHORT=arm64 +ARG TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu +ARG ROOTFS_DIR=/crossrootfs/arm64 -# Map BUILD_ARCH to Debian triple, dpkg short arch, and clang target triple. -RUN case ${BUILD_ARCH} in \ - arm) TOOLCHAIN_ARCH=arm-linux-gnueabihf ; TOOLCHAIN_ARCH_SHORT=armhf ; TOOLCHAIN_ARCH_TARGET=armv7a-linux-gnueabihf ;; \ - arm64) TOOLCHAIN_ARCH=aarch64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=arm64 ; TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu ;; \ - riscv64) TOOLCHAIN_ARCH=riscv64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=riscv64 ; TOOLCHAIN_ARCH_TARGET=riscv64-linux-gnu ;; \ - x86) TOOLCHAIN_ARCH=i386-linux-gnu ; TOOLCHAIN_ARCH_SHORT=i386 ; TOOLCHAIN_ARCH_TARGET=i686-linux-gnu ;; \ - x64) TOOLCHAIN_ARCH=x86_64-linux-gnu ; TOOLCHAIN_ARCH_SHORT=amd64 ; TOOLCHAIN_ARCH_TARGET=x86_64-linux-gnu ;; \ - *) echo "Unsupported architecture: ${BUILD_ARCH} (use ../loongarch64/ for loongarch64)" && exit 1 ;; \ - esac \ - && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ +RUN echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ && echo "export TOOLCHAIN_ARCH_SHORT=${TOOLCHAIN_ARCH_SHORT}" >> /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env - -# Choose the Debian release for the sysroot. -# Bullseye (glibc 2.31) is default — oldest with C++20 libstdc++. -# riscv64 uses Trixie because lld 18 is incompatible with GCC 10's static libstdc++.a. -ARG SYSROOT_RELEASE=bullseye - -# Determine GCC/libstdc++ version from the release. -RUN case ${SYSROOT_RELEASE} in \ - bullseye) TOOLCHAIN_VERSION=10 ;; \ - trixie) TOOLCHAIN_VERSION=14 ;; \ - *) echo "Unsupported release: ${SYSROOT_RELEASE}" && exit 1 ;; \ - esac \ - && echo "export TOOLCHAIN_VERSION=${TOOLCHAIN_VERSION}" >> /etc/skia-env - -# Create the Debian sysroot using debootstrap. -# -# --foreign: Do not run second stage (we're creating a foreign-arch sysroot -# on an amd64/arm64 host — postinst scripts can't execute). We only need -# the unpacked headers + libraries for cross-compilation, not a bootable rootfs. -# --variant=minbase: Only essential packages (dpkg, apt, coreutils). -# --include: Add the gcc cross-compilation development packages we need. -# --merged-usr: Create /bin→/usr/bin symlinks before unpacking (supported since Debian 10). -# -# debootstrap --foreign downloads all packages to var/cache/apt/archives/ but -# does NOT extract the --include packages. We extract only the specific packages -# we need (not all cached debs) to avoid conflicts with base-files. -ARG SYSROOT_DIR=/sysroot -RUN . /etc/skia-env \ - && debootstrap --foreign --merged-usr --variant=minbase --arch=${TOOLCHAIN_ARCH_SHORT} \ - --include=libstdc++-${TOOLCHAIN_VERSION}-dev,libgcc-${TOOLCHAIN_VERSION}-dev,linux-libc-dev,libc6-dev \ - ${SYSROOT_RELEASE} ${SYSROOT_DIR} https://deb.debian.org/debian \ - && echo "Extracting required packages into sysroot..." \ - && for required_pkg in \ - "libstdc++-${TOOLCHAIN_VERSION}-dev" \ - "libgcc-${TOOLCHAIN_VERSION}-dev" \ - "linux-libc-dev" \ - "libc6-dev" \ - "libc6" \ - "libgcc-s1"; do \ - deb=$(ls ${SYSROOT_DIR}/var/cache/apt/archives/${required_pkg}_*.deb 2>/dev/null | head -1) ; \ - if [ -z "$deb" ]; then \ - echo "FATAL: required package '${required_pkg}' not found in cache" && exit 1 ; \ - fi ; \ - echo " Extracting: $(basename $deb)" ; \ - cd /tmp && ar x "$deb" \ - && tar xf data.tar.* -C ${SYSROOT_DIR}/ \ - && rm -f control.tar.* data.tar.* debian-binary ; \ - done \ - && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr" >> /etc/skia-env \ - && echo "export SYSROOT_ROOT=${SYSROOT_DIR}" >> /etc/skia-env \ - && GCC_DIR=$(find ${SYSROOT_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ - && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env \ - && echo "Sysroot created: ${SYSROOT_RELEASE} ${BUILD_ARCH}" + && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env \ + && echo "export SYSROOT_PATH=${ROOTFS_DIR}/usr" >> /etc/skia-env \ + && echo "export SYSROOT_ROOT=${ROOTFS_DIR}" >> /etc/skia-env \ + && GCC_DIR=$(find ${ROOTFS_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ + && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env -# Create "current" symlinks so build.cake can reference c++ headers version-agnostically. -# include/c++/current -> VERSION (main c++ headers) -# include/{ARCH}/c++/current -> VERSION (arch-specific c++ headers, Debian native layout) -RUN . /etc/skia-env \ - && ln -sf ${TOOLCHAIN_VERSION} ${SYSROOT_PATH}/include/c++/current \ - && if [ -d "${SYSROOT_PATH}/include/${TOOLCHAIN_ARCH}/c++/${TOOLCHAIN_VERSION}" ]; then \ - ln -sf ${TOOLCHAIN_VERSION} ${SYSROOT_PATH}/include/${TOOLCHAIN_ARCH}/c++/current ; \ +# Create c++/current symlinks so build.cake can reference headers version-agnostically. +RUN GCC_VER=$(basename "$(find ${ROOTFS_DIR}/usr/include/c++/ -mindepth 1 -maxdepth 1 -type d | sort -V | head -n1)") \ + && ln -sf ${GCC_VER} ${ROOTFS_DIR}/usr/include/c++/current \ + && if [ -d "${ROOTFS_DIR}/usr/include/${TOOLCHAIN_ARCH}/c++/${GCC_VER}" ]; then \ + ln -sf ${GCC_VER} ${ROOTFS_DIR}/usr/include/${TOOLCHAIN_ARCH}/c++/current ; \ + fi \ + && if [ -d "${ROOTFS_DIR}/usr/include/c++/${GCC_VER}/${TOOLCHAIN_ARCH}" ]; then \ + mkdir -p ${ROOTFS_DIR}/usr/include/c++/current 2>/dev/null || true ; \ + ln -sf ../${GCC_VER}/${TOOLCHAIN_ARCH} ${ROOTFS_DIR}/usr/include/c++/current/${TOOLCHAIN_ARCH} 2>/dev/null || true ; \ fi -# Install fontconfig into the sysroot with SHA-256 verification. -# Download .deb packages from Debian's pool and extract headers + libraries. -RUN . /etc/skia-env \ - && mkdir -p /tmp/fontconfig \ - && cd /tmp/fontconfig \ - && case "${SYSROOT_RELEASE}" in \ - bullseye) FC_VERSION=2.13.1-4.2 ;; \ - trixie) FC_VERSION=2.17.1-5 ;; \ - esac \ - && case "${SYSROOT_RELEASE}_${TOOLCHAIN_ARCH_SHORT}" in \ - bullseye_armhf) FC_DEV_SHA256=4e352097b99831f5898b33729602b549f87b88b5d9ceaecfea10ec122f238f92 ; FC_LIB_SHA256=a29995b90aaec6b6bde70799f487b6eb12421e507342ae9035594fd7c1822e1d ;; \ - bullseye_arm64) FC_DEV_SHA256=8872d11ade9fbdc818099ccbc57b8fc06d3d74d33797d6467da60c49b247acc8 ; FC_LIB_SHA256=18b13ef8a46e9d79ba6a6ba2db0c86e42583277b5d47f6942f3223e349c22641 ;; \ - bullseye_i386) FC_DEV_SHA256=0a5205d19e771235de1577cf92413a821d8358c05fdc4e4ddeaef9959ff157db ; FC_LIB_SHA256=373807faea5e24bd23efc1867eeb0dceca322bc906e1b9b4f5a1b7539880b3ed ;; \ - bullseye_amd64) FC_DEV_SHA256=7655d4238ee7e6ced13501006d20986cbf9ff08454a4e502d5aa399f83e28876 ; FC_LIB_SHA256=b92861827627a76e74d6f447a5577d039ef2f95da18af1f29aa98fb96baea4c1 ;; \ - trixie_riscv64) FC_DEV_SHA256=17bba653a4385c870fd0518d8af094b6469524c639d29891bde526686e1fb9fa ; FC_LIB_SHA256=3d08f8a2fc4a325a0a4821848b804ba86b047eab92ce75184ab852b77ba72bde ;; \ - *) echo "No fontconfig SHA-256 for ${SYSROOT_RELEASE}_${TOOLCHAIN_ARCH_SHORT} — add checksum" && exit 1 ;; \ - esac \ - && APT_REPO=https://deb.debian.org/debian \ - && mkdir -p /tmp/fontconfig/extract \ - && for pkg_info in \ - "libfontconfig-dev ${FC_DEV_SHA256}" \ - "libfontconfig1 ${FC_LIB_SHA256}"; do \ - pkg=$(echo $pkg_info | cut -d' ' -f1) ; \ - expected_sha=$(echo $pkg_info | cut -d' ' -f2) ; \ - url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb"; \ - echo "Downloading $url"; \ - curl -fSL -o "${pkg}.deb" "$url"; \ - actual_sha=$(sha256sum "${pkg}.deb" | cut -d' ' -f1) ; \ - if [ "$actual_sha" != "$expected_sha" ]; then \ - echo "FATAL: SHA-256 mismatch for ${pkg}.deb" ; \ - echo " Expected: $expected_sha" ; \ - echo " Actual: $actual_sha" ; \ - exit 1 ; \ - fi ; \ - echo " SHA-256 verified: $actual_sha" ; \ - ar x "${pkg}.deb" ; \ - tar xf data.tar.* -C /tmp/fontconfig/extract/ ; \ - rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ - done \ - && cp -a /tmp/fontconfig/extract/* ${SYSROOT_DIR}/ \ - && rm -rf /tmp/fontconfig +# Install fontconfig headers into the sysroot. +# Download .deb packages from the same Debian release as the sysroot and verify SHA-256. +ARG FC_DISTRO=bullseye +ARG FC_VERSION=2.13.1-4.2 +ARG FC_DEV_SHA256 +ARG FC_LIB_SHA256 +RUN if [ -n "${FC_DEV_SHA256:-}" ] && [ -n "${FC_LIB_SHA256:-}" ]; then \ + mkdir -p /tmp/fontconfig && cd /tmp/fontconfig \ + && APT_REPO=https://deb.debian.org/debian \ + && for pkg_info in \ + "libfontconfig-dev ${FC_DEV_SHA256}" \ + "libfontconfig1 ${FC_LIB_SHA256}"; do \ + pkg=$(echo $pkg_info | cut -d' ' -f1) ; \ + expected_sha=$(echo $pkg_info | cut -d' ' -f2) ; \ + url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb" ; \ + echo "Downloading $url" ; \ + curl -fSL -o "${pkg}.deb" "$url" ; \ + actual_sha=$(sha256sum "${pkg}.deb" | cut -d' ' -f1) ; \ + if [ "$actual_sha" != "$expected_sha" ]; then \ + echo "FATAL: SHA-256 mismatch for ${pkg}.deb" ; \ + echo " Expected: $expected_sha" ; \ + echo " Actual: $actual_sha" ; \ + exit 1 ; \ + fi ; \ + echo " SHA-256 verified: $actual_sha" ; \ + ar x "${pkg}.deb" ; \ + tar xf data.tar.* -C ${ROOTFS_DIR}/ ; \ + rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ + done \ + && rm -rf /tmp/fontconfig ; \ + else \ + echo "WARNING: No fontconfig SHA-256 provided — skipping fontconfig install" ; \ + fi WORKDIR /work diff --git a/scripts/infra/native/linux/docker/glibc/build-local.sh b/scripts/infra/native/linux/docker/glibc/build-local.sh index 5d2eb2e5693..f3965b02d29 100755 --- a/scripts/infra/native/linux/docker/glibc/build-local.sh +++ b/scripts/infra/native/linux/docker/glibc/build-local.sh @@ -11,36 +11,92 @@ ARCH="${1:-arm64}" shift 1 || true EXTRA_ARGS="$@" -# Determine sysroot release based on architecture +# Map architecture to .NET cross image tag and toolchain settings case "$ARCH" in - riscv64) SYSROOT_RELEASE=trixie ;; - *) SYSROOT_RELEASE=bullseye ;; -esac - -# the docker platform to use -MACHINE_ARCH="$(uname -m)" -case $MACHINE_ARCH in - arm64) PLATFORM=linux/arm64 ; MACHINE_ARCH=aarch64 ;; - *) PLATFORM=linux/amd64 ;; + arm) + BASE_TAG=azurelinux-3.0-net10.0-cross-arm + TOOLCHAIN_ARCH=arm-linux-gnueabihf + TOOLCHAIN_ARCH_SHORT=armhf + TOOLCHAIN_ARCH_TARGET=armv7a-linux-gnueabihf + ROOTFS_DIR=/crossrootfs/arm + VERIFY_GLIBC_MAX=2.35 + FC_DISTRO=jammy + FC_VERSION=2.13.1-4.4ubuntu0.1 + FC_DEV_SHA256="" + FC_LIB_SHA256="" + ;; + arm64) + BASE_TAG=azurelinux-3.0-net10.0-cross-arm64 + TOOLCHAIN_ARCH=aarch64-linux-gnu + TOOLCHAIN_ARCH_SHORT=arm64 + TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu + ROOTFS_DIR=/crossrootfs/arm64 + VERIFY_GLIBC_MAX=2.27 + FC_DISTRO=bionic + FC_VERSION=2.12.6-0ubuntu2 + FC_DEV_SHA256="" + FC_LIB_SHA256="" + ;; + riscv64) + BASE_TAG=azurelinux-3.0-net10.0-cross-riscv64 + TOOLCHAIN_ARCH=riscv64-linux-gnu + TOOLCHAIN_ARCH_SHORT=riscv64 + TOOLCHAIN_ARCH_TARGET=riscv64-linux-gnu + ROOTFS_DIR=/crossrootfs/riscv64 + VERIFY_GLIBC_MAX=2.39 + FC_DISTRO=noble + FC_VERSION=2.15.0-1.1ubuntu2 + FC_DEV_SHA256="" + FC_LIB_SHA256="" + ;; + x86) + BASE_TAG=azurelinux-3.0-net10.0-cross-x86 + TOOLCHAIN_ARCH=i386-linux-gnu + TOOLCHAIN_ARCH_SHORT=i386 + TOOLCHAIN_ARCH_TARGET=i686-linux-gnu + ROOTFS_DIR=/crossrootfs/x86 + VERIFY_GLIBC_MAX=2.27 + FC_DISTRO=bionic + FC_VERSION=2.12.6-0ubuntu2 + FC_DEV_SHA256="" + FC_LIB_SHA256="" + ;; + x64) + BASE_TAG=azurelinux-3.0-net10.0-cross-amd64 + TOOLCHAIN_ARCH=x86_64-linux-gnu + TOOLCHAIN_ARCH_SHORT=amd64 + TOOLCHAIN_ARCH_TARGET=x86_64-linux-gnu + ROOTFS_DIR=/crossrootfs/x64 + VERIFY_GLIBC_MAX=2.27 + FC_DISTRO=bionic + FC_VERSION=2.12.6-0ubuntu2 + FC_DEV_SHA256="" + FC_LIB_SHA256="" + ;; + *) + echo "Unsupported architecture: $ARCH" + echo "For loongarch64, use ../loongarch64/build-local.sh" + exit 1 + ;; esac # Build the glibc image (cd "$DIR" && docker build --tag skiasharp-linux-gnu-cross-$ARCH \ - --platform=$PLATFORM \ + --platform=linux/amd64 \ + --build-arg BASE_TAG=$BASE_TAG \ --build-arg BUILD_ARCH=$ARCH \ - --build-arg MACHINE_ARCH=$MACHINE_ARCH \ - --build-arg SYSROOT_RELEASE=$SYSROOT_RELEASE \ + --build-arg TOOLCHAIN_ARCH=$TOOLCHAIN_ARCH \ + --build-arg TOOLCHAIN_ARCH_SHORT=$TOOLCHAIN_ARCH_SHORT \ + --build-arg TOOLCHAIN_ARCH_TARGET=$TOOLCHAIN_ARCH_TARGET \ + --build-arg ROOTFS_DIR=$ROOTFS_DIR \ + --build-arg FC_DISTRO=$FC_DISTRO \ + --build-arg FC_VERSION=$FC_VERSION \ + --build-arg FC_DEV_SHA256=$FC_DEV_SHA256 \ + --build-arg FC_LIB_SHA256=$FC_LIB_SHA256 \ .) -# GLIBC verification based on sysroot release -ADDITIONAL_ARGS="" -case "$SYSROOT_RELEASE" in - bullseye) ADDITIONAL_ARGS="--verifyGlibcMax=2.31" ;; - trixie) ADDITIONAL_ARGS="--verifyGlibcMax=2.38" ;; -esac - (cd "$DIR/../../../../../.." && docker run --rm --name skiasharp-linux-gnu-cross-$ARCH --volume $(pwd):/work skiasharp-linux-gnu-cross-$ARCH /bin/bash -c " \ dotnet tool restore ; \ - dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=$ARCH $ADDITIONAL_ARGS $EXTRA_ARGS ") + dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=$ARCH --verifyGlibcMax=$VERIFY_GLIBC_MAX $EXTRA_ARGS ") diff --git a/scripts/infra/native/linux/docker/loongarch64/.dockerignore b/scripts/infra/native/linux/docker/loongarch64/.dockerignore deleted file mode 100644 index 45599257251..00000000000 --- a/scripts/infra/native/linux/docker/loongarch64/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -# Only send Dockerfile and startup.sh to the Docker daemon. -* -!Dockerfile -!startup.sh diff --git a/scripts/infra/native/linux/docker/loongarch64/Dockerfile b/scripts/infra/native/linux/docker/loongarch64/Dockerfile deleted file mode 100644 index 3509c521be5..00000000000 --- a/scripts/infra/native/linux/docker/loongarch64/Dockerfile +++ /dev/null @@ -1,186 +0,0 @@ -# loongarch64 glibc cross-compilation image. -# -# loongarch64 has no Debian binary archive (even in Trixie), so it cannot use -# debootstrap. Instead, we download gcc-cross-ports packages from Debian's pool -# and manually extract them to create a sysroot. -# -# Architecture: -# Azure Linux 3.0 (host) → clang-18 → --sysroot=/sysroot → libSkiaSharp.so -# -# The cross-package layout places headers/libs under /usr/{TOOLCHAIN_ARCH}/ rather -# than the native /usr/include/ + /usr/lib/{triple}/ layout that debootstrap produces. -# We set SYSROOT_ROOT=/sysroot so that lld resolves absolute paths in linker scripts -# (e.g., /usr/loongarch64-linux-gnu/lib/libc.so.6) relative to the sysroot automatically. - -FROM mcr.microsoft.com/azurelinux/base/core:3.0 - -# Use bash with strict error handling for all RUN commands. -SHELL ["/bin/bash", "-euo", "pipefail", "-c"] - -# Install host build tools from Azure Linux packages. -RUN tdnf install -y \ - ninja-build \ - python3 \ - git \ - curl \ - tar \ - xz \ - gawk \ - ca-certificates \ - binutils \ - libxml2 \ - ncurses-libs \ - zlib \ - libffi \ - && tdnf clean all - -# Install official LLVM 18 release with all target backends. -# SHA-256 checksums computed from official GitHub releases (llvm/llvm-project). -ARG LLVM_VERSION=18.1.8 -ARG MACHINE_ARCH=x86_64 -ARG LLVM_SHA256_X86_64=54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36 -ARG LLVM_SHA256_AARCH64=dcaa1bebbfbb86953fdfbdc7f938800229f75ad26c5c9375ef242edad737d999 -RUN ln -sf /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5 \ - && ln -sf /usr/lib64/libncursesw.so.6 /usr/lib64/libncurses.so.5 \ - && case "${MACHINE_ARCH}" in \ - x86_64) LLVM_SUFFIX="x86_64-linux-gnu-ubuntu-18.04" ; LLVM_SHA256="${LLVM_SHA256_X86_64}" ;; \ - aarch64) LLVM_SUFFIX="aarch64-linux-gnu" ; LLVM_SHA256="${LLVM_SHA256_AARCH64}" ;; \ - *) echo "Unsupported MACHINE_ARCH: ${MACHINE_ARCH}" && exit 1 ;; \ - esac \ - && curl -fSL -o /tmp/llvm.tar.xz \ - "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-${LLVM_SUFFIX}.tar.xz" \ - && echo "${LLVM_SHA256} /tmp/llvm.tar.xz" | sha256sum -c - \ - && tar xJf /tmp/llvm.tar.xz --strip-components=1 -C /usr/local \ - && rm -f /tmp/llvm.tar.xz \ - && rm -rf /usr/local/share/doc /usr/local/share/man /usr/local/share/clang \ - && clang --version && ld.lld --version && llvm-ar --version - -# Install the .NET SDK (needed for Cake build scripts). -ARG DOTNET_SDK_VERSION=10.0.108 -ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ - && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ - && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ - && rm dotnet-install.sh \ - && dotnet help \ - && dotnet --info - -ENV CC=clang CXX=clang++ - -# loongarch64 constants — no BUILD_ARCH arg needed since this image is single-arch. -ARG SYSROOT_RELEASE=trixie -ARG SYSROOT_DIR=/sysroot - -RUN TOOLCHAIN_ARCH=loongarch64-linux-gnu \ - && TOOLCHAIN_ARCH_SHORT=loong64 \ - && TOOLCHAIN_ARCH_TARGET=loongarch64-linux-gnu \ - && case "${SYSROOT_RELEASE}" in \ - trixie) TOOLCHAIN_VERSION=14 ;; \ - *) echo "Unsupported release for loongarch64: ${SYSROOT_RELEASE}" && exit 1 ;; \ - esac \ - && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_SHORT=${TOOLCHAIN_ARCH_SHORT}" >> /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env \ - && echo "export TOOLCHAIN_VERSION=${TOOLCHAIN_VERSION}" >> /etc/skia-env - -# Download and extract gcc-cross-ports packages from Debian's package pool. -# loongarch64 is only available via cross-compilation packages (gcc-14-cross-ports). -# URLs and SHA-256 checksums are pinned for reproducibility — update when Debian releases new versions. -RUN . /etc/skia-env \ - && mkdir -p ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH} \ - && mkdir -p /tmp/cross-pkgs && cd /tmp/cross-pkgs \ - && PACKAGES=" \ - https://deb.debian.org/debian/pool/main/g/gcc-14-cross-ports/libstdc++-14-dev-loong64-cross_14.3.0-14cross2_all.deb \ - 81600199ccac5a0a8c019ce973ea4532530a671dd3e4ee3c552a9d5b97f5bffa \ - https://deb.debian.org/debian/pool/main/g/gcc-14-cross-ports/libstdc++6-loong64-cross_14.2.0-19cross1_all.deb \ - 29ea1472a2b1b6c30db334bef7d7ba302db03ca46ff8a243359f6d488c62ef76 \ - https://deb.debian.org/debian/pool/main/g/gcc-14-cross-ports/libgcc-14-dev-loong64-cross_14.3.0-14cross2_all.deb \ - b4c019526c04c8d45ee6c7044446b2a968ab50b96c55600943312802d2d71531 \ - https://deb.debian.org/debian/pool/main/g/gcc-14-cross-ports/libgcc-s1-loong64-cross_14.2.0-19cross1_all.deb \ - a87453248b2a60ce083606d60950154291fc7e347df9c14263adade49fb18c41 \ - https://deb.debian.org/debian/pool/main/c/cross-toolchain-base-ports/libc6-dev-loong64-cross_2.42-10cross1_all.deb \ - 0b47a5a3f41c6d598ed880b38eb8d3b897d3b78ec13d6529d194a390216dc988 \ - https://deb.debian.org/debian/pool/main/c/cross-toolchain-base-ports/libc6-loong64-cross_2.42-10cross1_all.deb \ - fc58b06c95911192f413b785a2399c3e6c72dc302680def6ad0534fa27ece3e6 \ - https://deb.debian.org/debian/pool/main/c/cross-toolchain-base-ports/linux-libc-dev-loong64-cross_6.12.38-1cross1_all.deb \ - b30c0c8a0d1a8480695b498c1791c930df489b94b26d15ccafa37bfc2e37f3ed \ - " \ - && set -- $PACKAGES \ - && while [ $# -ge 2 ]; do \ - url="$1"; expected_sha="$2"; shift 2 ; \ - filename=$(basename "$url") ; \ - echo "Downloading: $filename" ; \ - curl -fsSL -o "$filename" "$url" ; \ - actual_sha=$(sha256sum "$filename" | cut -d' ' -f1) ; \ - if [ "$actual_sha" != "$expected_sha" ]; then \ - echo "FATAL: SHA-256 mismatch for $filename" ; \ - echo " Expected: $expected_sha" ; \ - echo " Actual: $actual_sha" ; \ - exit 1 ; \ - fi ; \ - echo " SHA-256 verified: $actual_sha" ; \ - ar x "$filename" ; \ - mkdir -p extract && tar xf data.tar.* -C extract/ ; \ - rm -f control.tar.* data.tar.* debian-binary "$filename" ; \ - done \ - && echo "Extracted cross-package contents:" && find extract/usr/${TOOLCHAIN_ARCH}/ -maxdepth 1 -type d \ - && if [ -d extract/usr/${TOOLCHAIN_ARCH} ]; then \ - cp -a extract/usr/${TOOLCHAIN_ARCH}/* ${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}/ ; \ - fi \ - && if [ -d extract/usr/lib/gcc-cross ]; then \ - mkdir -p ${SYSROOT_DIR}/usr/lib/gcc-cross ; \ - cp -a extract/usr/lib/gcc-cross/* ${SYSROOT_DIR}/usr/lib/gcc-cross/ ; \ - fi \ - && rm -rf /tmp/cross-pkgs \ - && echo "export SYSROOT_PATH=${SYSROOT_DIR}/usr/${TOOLCHAIN_ARCH}" >> /etc/skia-env \ - && echo "export SYSROOT_ROOT=${SYSROOT_DIR}" >> /etc/skia-env \ - && echo "export GCC_LIB_DIR=${SYSROOT_DIR}/usr/lib/gcc-cross/${TOOLCHAIN_ARCH}/${TOOLCHAIN_VERSION}" >> /etc/skia-env - -# Create "current" symlink so build.cake can reference c++ headers version-agnostically. -# include/c++/current -> VERSION -RUN . /etc/skia-env \ - && ln -sf ${TOOLCHAIN_VERSION} ${SYSROOT_PATH}/include/c++/current - -# Install fontconfig into the sysroot with SHA-256 verification. -# Cross-package layout: headers go to SYSROOT_PATH/include, libs to SYSROOT_PATH/lib. -ARG FC_DEV_SHA256=0d3aee5780ecbab1f709036cedd935f2af690bd5392227411d97e6b160537456 -ARG FC_LIB_SHA256=234a8998df450d4cd83f9c0a4a942fe1892b556122ff96fc737e0c74cb9c4421 -RUN . /etc/skia-env \ - && mkdir -p /tmp/fontconfig && cd /tmp/fontconfig \ - && case "${SYSROOT_RELEASE}" in \ - trixie) FC_VERSION=2.17.1-5 ;; \ - esac \ - && APT_REPO=https://deb.debian.org/debian \ - && mkdir -p /tmp/fontconfig/extract \ - && for pkg_info in \ - "libfontconfig-dev ${FC_DEV_SHA256}" \ - "libfontconfig1 ${FC_LIB_SHA256}"; do \ - pkg=$(echo $pkg_info | cut -d' ' -f1) ; \ - expected_sha=$(echo $pkg_info | cut -d' ' -f2) ; \ - url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb" ; \ - echo "Downloading $url" ; \ - curl -fSL -o "${pkg}.deb" "$url" ; \ - actual_sha=$(sha256sum "${pkg}.deb" | cut -d' ' -f1) ; \ - if [ "$actual_sha" != "$expected_sha" ]; then \ - echo "FATAL: SHA-256 mismatch for ${pkg}.deb" ; \ - echo " Expected: $expected_sha" ; \ - echo " Actual: $actual_sha" ; \ - exit 1 ; \ - fi ; \ - echo " SHA-256 verified: $actual_sha" ; \ - ar x "${pkg}.deb" ; \ - tar xf data.tar.* -C /tmp/fontconfig/extract/ ; \ - rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ - done \ - && mkdir -p ${SYSROOT_PATH}/include ${SYSROOT_PATH}/lib \ - && cp -a /tmp/fontconfig/extract/usr/include/* ${SYSROOT_PATH}/include/ \ - && if [ -d "/tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}" ]; then \ - cp -a /tmp/fontconfig/extract/usr/lib/${TOOLCHAIN_ARCH}/* ${SYSROOT_PATH}/lib/ ; \ - fi \ - && rm -rf /tmp/fontconfig - -WORKDIR /work - -COPY ./startup.sh / -RUN chmod +x /startup.sh -ENTRYPOINT [ "/startup.sh" ] diff --git a/scripts/infra/native/linux/docker/loongarch64/build-local.sh b/scripts/infra/native/linux/docker/loongarch64/build-local.sh index 70d971fc20c..289587c6cf0 100755 --- a/scripts/infra/native/linux/docker/loongarch64/build-local.sh +++ b/scripts/infra/native/linux/docker/loongarch64/build-local.sh @@ -2,7 +2,7 @@ set -ex # Build loongarch64 glibc cross-compilation image and run the build. -# No architecture parameter — this image is single-arch. +# Uses the glibc Dockerfile with loongarch64-specific BASE_TAG. # # Parameters: # $@ - Additional arguments to pass to the cake script @@ -11,21 +11,20 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" EXTRA_ARGS="$@" -# the docker platform to use -MACHINE_ARCH="$(uname -m)" -case $MACHINE_ARCH in - arm64) PLATFORM=linux/arm64 ; MACHINE_ARCH=aarch64 ;; - *) PLATFORM=linux/amd64 ;; -esac - -# Build the loongarch64 image -(cd "$DIR" && +# Build using the glibc Dockerfile with loongarch64 args +(cd "$DIR/../glibc" && docker build --tag skiasharp-linux-gnu-cross-loongarch64 \ - --platform=$PLATFORM \ - --build-arg MACHINE_ARCH=$MACHINE_ARCH \ + --platform=linux/amd64 \ + --build-arg BASE_TAG=azurelinux-3.0-net10.0-cross-loongarch64 \ + --build-arg BUILD_ARCH=loongarch64 \ + --build-arg TOOLCHAIN_ARCH=loongarch64-linux-gnu \ + --build-arg TOOLCHAIN_ARCH_SHORT=loong64 \ + --build-arg TOOLCHAIN_ARCH_TARGET=loongarch64-linux-gnu \ + --build-arg ROOTFS_DIR=/crossrootfs/loongarch64 \ + --build-arg FC_DISTRO=sid \ .) (cd "$DIR/../../../../../.." && docker run --rm --name skiasharp-linux-gnu-cross-loongarch64 --volume $(pwd):/work skiasharp-linux-gnu-cross-loongarch64 /bin/bash -c " \ dotnet tool restore ; \ - dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=loongarch64 --verifyGlibcMax=2.38 $EXTRA_ARGS ") + dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=loongarch64 $EXTRA_ARGS ") diff --git a/scripts/infra/native/linux/docker/loongarch64/startup.sh b/scripts/infra/native/linux/docker/loongarch64/startup.sh deleted file mode 100644 index 512bb431bf7..00000000000 --- a/scripts/infra/native/linux/docker/loongarch64/startup.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -e - -source /etc/skia-env - -exec "$@" From 932ad12f08994c588f8cb165519bc9dc3f524165 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 23:12:20 +0200 Subject: [PATCH 28/45] Pivot Linux Docker images to .NET official cross-compilation base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all custom Docker infrastructure (debootstrap, LLVM downloads, sysroot construction) with thin layers on top of .NET's official mcr.microsoft.com/dotnet-buildtools/prereqs cross-compilation images. Key changes: - Base on azurelinux-3.0-net10.0-cross- images (Clang 20, lld, complete sysroots with libc++) - Switch from -static-libstdc++ to -stdlib=libc++ (already done in native/linux/build.cake) - Consolidate loongarch64 into glibc Dockerfile case statement - All Dockerfiles take only BUILD_ARCH as input, derive everything internally via case statements - SHA-256 pinned fontconfig .deb downloads for each architecture - Remove old SYSROOT_RELEASE, verifyGlibcMax params from CI YAML - Delete scripts/infra/native/linux/docker/loongarch64/ (absorbed) Benefits: - Eliminates ~100 Component Governance security alerts - Delegates LLVM/sysroot maintenance to .NET infrastructure team - Reduces Dockerfile from ~150 lines to ~40 lines each - Deterministic, reproducible builds with pinned hashes Docker build tested locally: - glibc arm64 ✓ - glibc x64 (IMAGE_ARCH=amd64) ✓ - alpine arm64 ✓ - bionic arm64 ✓ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../azure-templates-stages-native-linux.yml | 39 ++-- .../native/linux/docker/alpine/Dockerfile | 87 +++++---- .../native/linux/docker/alpine/build-local.sh | 61 +----- .../native/linux/docker/bionic/Dockerfile | 29 ++- .../native/linux/docker/glibc/Dockerfile | 181 ++++++++++-------- .../native/linux/docker/glibc/build-local.sh | 86 +-------- .../linux/docker/loongarch64/build-local.sh | 30 --- 7 files changed, 208 insertions(+), 305 deletions(-) delete mode 100755 scripts/infra/native/linux/docker/loongarch64/build-local.sh diff --git a/scripts/azure-templates-stages-native-linux.yml b/scripts/azure-templates-stages-native-linux.yml index b6500d2448f..652258cb8e8 100644 --- a/scripts/azure-templates-stages-native-linux.yml +++ b/scripts/azure-templates-stages-native-linux.yml @@ -27,33 +27,34 @@ stages: additionalArgs: --verifyExcluded=fontconfig gnArgs: skia_use_fontconfig=false matrix: - # Azure Linux + loongarch64 cross-package extraction (gcc-14, glibc 2.38). - # loongarch64 has no Debian binary archive — uses its own Dockerfile. - - arch: loongarch64 - docker: scripts/infra/native/linux/docker/loongarch64 + # .NET official cross images (glibc) + - ${{ each arch in split('arm,arm64,x86', ',') }}: + - arch: ${{ arch }} + docker: scripts/infra/native/linux/docker/glibc + target: externals-linux-clang-cross + # x64 needs IMAGE_ARCH=amd64 (.NET uses "amd64" not "x64" in image tags) + - arch: x64 + docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross - additionalArgs: --verifyGlibcMax=2.38 - dockerArgs: --build-arg SYSROOT_RELEASE=trixie - # Azure Linux + Trixie sysroot (gcc-14, glibc 2.38) - for archs that - # need GCC 14 (lld 18 is incompatible with GCC 10's static libstdc++.a - # on riscv64 due to .eh_frame discarded-section errors). + dockerArgs: --build-arg IMAGE_ARCH=amd64 - arch: riscv64 docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross - additionalArgs: --verifyGlibcMax=2.38 - dockerArgs: --build-arg SYSROOT_RELEASE=trixie - # Azure Linux + Bullseye sysroot (gcc-10, glibc 2.31). - - ${{ each arch in split('arm,arm64,x86,x64', ',') }}: - - arch: ${{ arch }} - docker: scripts/infra/native/linux/docker/glibc - target: externals-linux-clang-cross - additionalArgs: --verifyGlibcMax=2.31 - dockerArgs: --build-arg SYSROOT_RELEASE=bullseye - - ${{ each arch in split('arm,arm64,x64,riscv64,loongarch64', ',') }}: # removed x86 as it is having issues building + - arch: loongarch64 + docker: scripts/infra/native/linux/docker/glibc + target: externals-linux-clang-cross + # .NET official cross images (musl/Alpine) + - ${{ each arch in split('arm,arm64,riscv64,loongarch64', ',') }}: - arch: ${{ arch }} variant: alpine docker: scripts/infra/native/linux/docker/alpine target: externals-linux-clang-cross + # x64 musl needs IMAGE_ARCH=amd64 + - arch: x64 + variant: alpine + docker: scripts/infra/native/linux/docker/alpine + target: externals-linux-clang-cross + dockerArgs: --build-arg IMAGE_ARCH=amd64 - template: /scripts/azure-templates-jobs-linux-matrix.yml@self # Build Native Linux (Bionic) parameters: diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index f8522e651b8..7575db13cfd 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -3,67 +3,86 @@ # Thin layer on top of .NET's official musl cross-compilation images which provide: # - Azure Linux 3.0 host (clean CG surface) # - Clang 20 + lld (built from source, GPG-verified) -# - Complete Alpine sysroot with musl headers, libstdc++ headers, kernel headers +# - Complete Alpine sysroot with musl headers, kernel headers # - Cross-compiled libc++ (static) # # We add: .NET SDK (Cake), fontconfig-dev from Alpine repos, skia-env toolchain vars. # -# The base images use Alpine 3.17 (musl 1.2.3). musl is forward-compatible: -# binaries built against 3.17 run on any later Alpine version. +# Usage: +# docker build --build-arg BUILD_ARCH=arm64 . +# +# Supported architectures: arm, arm64, x64, riscv64, loongarch64 ARG BUILD_ARCH=arm64 -ARG BASE_TAG=azurelinux-3.0-net10.0-cross-arm64-musl -FROM mcr.microsoft.com/dotnet-buildtools/prereqs:${BASE_TAG} + +# .NET cross images use "amd64" where SkiaSharp uses "x64". +# CI must pass IMAGE_ARCH=amd64 when BUILD_ARCH=x64. +# All other architectures: IMAGE_ARCH defaults to BUILD_ARCH. +ARG IMAGE_ARCH=${BUILD_ARCH} +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-${IMAGE_ARCH}-musl SHELL ["/bin/bash", "-euo", "pipefail", "-c"] +ARG BUILD_ARCH + +# Derive all toolchain variables from BUILD_ARCH. +RUN case "${BUILD_ARCH}" in \ + arm) TOOLCHAIN_ARCH=armv7-alpine-linux-musleabihf; \ + APK_ARCH=armv7; \ + ROOTFS_DIR=/crossrootfs/arm ;; \ + arm64) TOOLCHAIN_ARCH=aarch64-alpine-linux-musl; \ + APK_ARCH=aarch64; \ + ROOTFS_DIR=/crossrootfs/arm64 ;; \ + x64) TOOLCHAIN_ARCH=x86_64-alpine-linux-musl; \ + APK_ARCH=x86_64; \ + ROOTFS_DIR=/crossrootfs/x64 ;; \ + riscv64) TOOLCHAIN_ARCH=riscv64-alpine-linux-musl; \ + APK_ARCH=riscv64; \ + ROOTFS_DIR=/crossrootfs/riscv64 ;; \ + loongarch64) TOOLCHAIN_ARCH=loongarch64-alpine-linux-musl; \ + APK_ARCH=loongarch64; \ + ROOTFS_DIR=/crossrootfs/loongarch64 ;; \ + *) echo "FATAL: Unsupported BUILD_ARCH=${BUILD_ARCH}" && exit 1 ;; \ + esac \ + && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ + && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH}" >> /etc/skia-env \ + && echo "export SYSROOT_PATH=${ROOTFS_DIR}/usr" >> /etc/skia-env \ + && echo "export SYSROOT_ROOT=${ROOTFS_DIR}" >> /etc/skia-env \ + && echo "export APK_ARCH=${APK_ARCH}" >> /etc/skia-env \ + && GCC_VER=$(ls ${ROOTFS_DIR}/usr/lib/gcc/${TOOLCHAIN_ARCH}/ 2>/dev/null | sort -V | tail -1) \ + && echo "export GCC_LIB_DIR=${ROOTFS_DIR}/usr/lib/gcc/${TOOLCHAIN_ARCH}/${GCC_VER}" >> /etc/skia-env + # Install the .NET SDK (needed for Cake build scripts). ARG DOTNET_SDK_VERSION=10.0.108 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ - && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ && rm dotnet-install.sh \ - && dotnet help \ && dotnet --info ENV CC=clang CXX=clang++ -# Toolchain configuration for build.cake. -ARG TOOLCHAIN_ARCH=aarch64-alpine-linux-musl -ARG TOOLCHAIN_ARCH_TARGET=aarch64-alpine-linux-musl -ARG APK_ARCH=aarch64 -ARG ROOTFS_DIR=/crossrootfs/arm64 - -RUN echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env \ - && echo "export SYSROOT_PATH=${ROOTFS_DIR}/usr" >> /etc/skia-env - -# Create c++/current symlink and discover GCC lib dir. -RUN GCC_VER=$(basename "$(find ${ROOTFS_DIR}/usr/include/c++/ -mindepth 1 -maxdepth 1 -type d | sort -V | head -n1)") \ - && ln -sf ${GCC_VER} ${ROOTFS_DIR}/usr/include/c++/current \ - && echo "export GCC_LIB_DIR=${ROOTFS_DIR}/usr/lib/gcc/${TOOLCHAIN_ARCH}/${GCC_VER}" >> /etc/skia-env - # Install fontconfig-dev into the sysroot from Alpine repos. -# The base image already has apk infrastructure in the sysroot, so we can -# use apk.static to add packages. If apk.static is not available, download it. +# The sysroot is Alpine 3.17 but we pull fontconfig from 3.21 (newer keys needed). +# SSL_CERT_FILE is needed because apk.static (musl) can't find Azure Linux's CA bundle. +ARG APK_STATIC_VERSION=2.12.14 ARG APK_STATIC_SHA256=cd282c693059c39b68ce0575fbdc4e0c162af85ed633a7834f9cfd01b8b1d58b -ARG DISTRO_VERSION=3.21 -RUN APK_DIR="$(mktemp -d)" \ +ARG ALPINE_VERSION=3.21 +RUN . /etc/skia-env \ + && APK_DIR="$(mktemp -d)" \ && curl -fSL --retry 5 --retry-delay 10 --retry-all-errors \ -o "$APK_DIR/apk.static" \ - "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.12.14/x86_64/apk.static" \ + "https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v${APK_STATIC_VERSION}/x86_64/apk.static" \ && echo "${APK_STATIC_SHA256} $APK_DIR/apk.static" | sha256sum -c - \ && chmod +x "$APK_DIR/apk.static" \ - && mkdir -p ${ROOTFS_DIR}/etc/apk/keys \ - && curl -fsSL -o ${ROOTFS_DIR}/etc/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub \ + && curl -fsSL -o ${SYSROOT_ROOT}/etc/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub \ "https://alpinelinux.org/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub" \ - && curl -fsSL -o ${ROOTFS_DIR}/etc/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub \ + && curl -fsSL -o ${SYSROOT_ROOT}/etc/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub \ "https://alpinelinux.org/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub" \ - && "$APK_DIR/apk.static" \ - -X "https://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/main" \ - -X "https://dl-cdn.alpinelinux.org/alpine/v$DISTRO_VERSION/community" \ - -U --keys-dir ${ROOTFS_DIR}/etc/apk/keys --root ${ROOTFS_DIR} --arch "$APK_ARCH" --no-scripts \ + && SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt "$APK_DIR/apk.static" \ + -X "https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/main" \ + -X "https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/community" \ + --no-cache --root ${SYSROOT_ROOT} --arch "${APK_ARCH}" --no-scripts \ add fontconfig-dev \ && rm -rf "$APK_DIR" diff --git a/scripts/infra/native/linux/docker/alpine/build-local.sh b/scripts/infra/native/linux/docker/alpine/build-local.sh index 3b464bfa6d8..397d9983ed6 100755 --- a/scripts/infra/native/linux/docker/alpine/build-local.sh +++ b/scripts/infra/native/linux/docker/alpine/build-local.sh @@ -2,7 +2,7 @@ set -ex # Parameters: -# $1 - The target architecture to build for [ arm | arm64 | riscv64 | loongarch64 | x86 | x64 ] +# $1 - The target architecture to build for [ arm | arm64 | x64 | riscv64 | loongarch64 ] # $2+ - Additional arguments to pass to the cake script DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" @@ -11,66 +11,21 @@ ARCH="${1:-arm64}" shift 1 || true EXTRA_ARGS="$@" -# Map architecture to .NET musl cross image tag and toolchain settings +# Validate architecture and map to .NET image tag arch case "$ARCH" in - arm) - BASE_TAG=azurelinux-3.0-net10.0-cross-arm-musl - TOOLCHAIN_ARCH=armv7-alpine-linux-musleabihf - TOOLCHAIN_ARCH_TARGET=armv7-alpine-linux-musleabihf - APK_ARCH=armv7 - ROOTFS_DIR=/crossrootfs/arm - ;; - arm64) - BASE_TAG=azurelinux-3.0-net10.0-cross-arm64-musl - TOOLCHAIN_ARCH=aarch64-alpine-linux-musl - TOOLCHAIN_ARCH_TARGET=aarch64-alpine-linux-musl - APK_ARCH=aarch64 - ROOTFS_DIR=/crossrootfs/arm64 - ;; - riscv64) - BASE_TAG=azurelinux-3.0-net10.0-cross-riscv64-musl - TOOLCHAIN_ARCH=riscv64-alpine-linux-musl - TOOLCHAIN_ARCH_TARGET=riscv64-alpine-linux-musl - APK_ARCH=riscv64 - ROOTFS_DIR=/crossrootfs/riscv64 - ;; - loongarch64) - BASE_TAG=azurelinux-3.0-net10.0-cross-loongarch64-musl - TOOLCHAIN_ARCH=loongarch64-alpine-linux-musl - TOOLCHAIN_ARCH_TARGET=loongarch64-alpine-linux-musl - APK_ARCH=loongarch64 - ROOTFS_DIR=/crossrootfs/loongarch64 - ;; - x86) - BASE_TAG=azurelinux-3.0-net10.0-cross-x86-musl - TOOLCHAIN_ARCH=i586-alpine-linux-musl - TOOLCHAIN_ARCH_TARGET=i586-alpine-linux-musl - APK_ARCH=x86 - ROOTFS_DIR=/crossrootfs/x86 - ;; + arm|arm64|riscv64|loongarch64) + IMAGE_ARCH="$ARCH" ;; x64) - BASE_TAG=azurelinux-3.0-net10.0-cross-amd64-musl - TOOLCHAIN_ARCH=x86_64-alpine-linux-musl - TOOLCHAIN_ARCH_TARGET=x86_64-alpine-linux-musl - APK_ARCH=x86_64 - ROOTFS_DIR=/crossrootfs/x64 - ;; - *) - echo "Unsupported architecture: $ARCH" - exit 1 - ;; + IMAGE_ARCH="amd64" ;; + *) echo "Unsupported architecture: $ARCH"; exit 1 ;; esac -# Build the alpine image +# Build the alpine image — the Dockerfile handles all arch-specific config internally. (cd "$DIR" && docker build --tag skiasharp-linux-musl-cross-$ARCH \ --platform=linux/amd64 \ - --build-arg BASE_TAG=$BASE_TAG \ --build-arg BUILD_ARCH=$ARCH \ - --build-arg TOOLCHAIN_ARCH=$TOOLCHAIN_ARCH \ - --build-arg TOOLCHAIN_ARCH_TARGET=$TOOLCHAIN_ARCH_TARGET \ - --build-arg APK_ARCH=$APK_ARCH \ - --build-arg ROOTFS_DIR=$ROOTFS_DIR \ + --build-arg IMAGE_ARCH=$IMAGE_ARCH \ .) (cd "$DIR/../../../../../.." && diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index e43e2976686..228bf587185 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -7,8 +7,10 @@ # # We add: .NET SDK (Cake), skia-env toolchain vars, libpthread stub. # -# The NDK's own clang is used for compilation (not the host clang), since the -# NDK bundles Android-specific runtime support (compiler-rt, libc++, etc.). +# Usage: +# docker build --build-arg BUILD_ARCH=x64 . +# +# Supported architectures: x64, arm64 FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-android-amd64 @@ -19,44 +21,39 @@ ARG DOTNET_SDK_VERSION=10.0.108 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ - && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ && rm dotnet-install.sh \ - && dotnet help \ && dotnet --info ARG BUILD_ARCH=x64 # Map BUILD_ARCH to Android triple and configure NDK paths. -# NDK 27.2 is at /usr/local/android-sdk/ndk/27.2.12479018 ARG NDK_VERSION=27.2.12479018 RUN case ${BUILD_ARCH} in \ x64) ANDROID_TRIPLE=x86_64-linux-android29 ; ANDROID_SYSROOT_LIB=x86_64-linux-android ;; \ - arm64) ANDROID_TRIPLE=aarch64-linux-android29 ; ANDROID_SYSROOT_LIB=aarch64-linux-android ;; \ - *) echo "Unsupported architecture: ${BUILD_ARCH}" && exit 1 ;; \ + arm64) ANDROID_TRIPLE=aarch64-linux-android29 ; ANDROID_SYSROOT_LIB=aarch64-linux-android ;; \ + *) echo "FATAL: Unsupported BUILD_ARCH=${BUILD_ARCH}" && exit 1 ;; \ esac \ && NDK_ROOT="/usr/local/android-sdk/ndk/${NDK_VERSION}" \ - && NDK_HOST_TAG="linux-x86_64" \ && echo "export ANDROID_TRIPLE=${ANDROID_TRIPLE}" > /etc/skia-env \ && echo "export ANDROID_SYSROOT_LIB=${ANDROID_SYSROOT_LIB}" >> /etc/skia-env \ - && echo "export NDK_HOST_TAG=${NDK_HOST_TAG}" >> /etc/skia-env \ + && echo "export NDK_HOST_TAG=linux-x86_64" >> /etc/skia-env \ && echo "export ANDROID_NDK_HOME=${NDK_ROOT}" >> /etc/skia-env \ && echo "export CC=${ANDROID_TRIPLE}-clang" >> /etc/skia-env \ && echo "export CXX=${ANDROID_TRIPLE}-clang++" >> /etc/skia-env \ && echo "export AR=llvm-ar" >> /etc/skia-env \ && echo "export LD=lld" >> /etc/skia-env -# Add NDK toolchain to PATH -ARG NDK_VERSION_FOR_PATH=27.2.12479018 -ENV ANDROID_NDK_HOME=/usr/local/android-sdk/ndk/${NDK_VERSION_FOR_PATH} -ENV PATH=$PATH:/usr/local/android-sdk/ndk/${NDK_VERSION_FOR_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin +# Add NDK toolchain to PATH. +ENV ANDROID_NDK_HOME=/usr/local/android-sdk/ndk/27.2.12479018 +ENV PATH=$PATH:/usr/local/android-sdk/ndk/27.2.12479018/toolchains/llvm/prebuilt/linux-x86_64/bin -# Create empty libpthread stub - Bionic bundles pthreads into libc, but +# Create empty libpthread stub — Bionic bundles pthreads into libc, but # Skia's GN build adds -lpthread for target_os='linux' builds. RUN . /etc/skia-env \ - && NDK_SYSROOT="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/${NDK_HOST_TAG}/sysroot" \ + && NDK_SYSROOT="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot" \ && PTHREAD_TARGET="$NDK_SYSROOT/usr/lib/$ANDROID_SYSROOT_LIB/libpthread.a" \ && if [ -e "$PTHREAD_TARGET" ]; then \ - echo "WARNING: NDK already ships libpthread.a — review needed" && exit 1 ; \ + echo "FATAL: NDK already ships libpthread.a — review needed" && exit 1 ; \ fi \ && printf '!\n' > "$PTHREAD_TARGET" diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 58e3ec9bc1c..b59ae9854f2 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -3,104 +3,133 @@ # Thin layer on top of .NET's official cross-compilation images which provide: # - Azure Linux 3.0 host (clean CG surface) # - Clang 20 + lld (built from source, GPG-verified) -# - Complete sysroot with glibc headers, libstdc++ headers, kernel headers +# - Complete sysroot with glibc headers, kernel headers # - Cross-compiled libc++ (static) and compiler-rt # # We add: .NET SDK (Cake), fontconfig-dev headers, skia-env toolchain vars. # -# Architecture: -# .NET cross image (host) → clang-20 → --sysroot=/crossrootfs/ → libSkiaSharp.so -# -# Base images use these sysroot distros: -# | arch | sysroot distro | glibc | Supported distros | -# |---------|----------------|-------|--------------------------------------| -# | arm64 | bionic | 2.27 | Debian 10+, Ubuntu 18.04+, RHEL 8+ | -# | arm | jammy | 2.35 | Debian 12+, Ubuntu 22.04+, RHEL 9+ | -# | x86 | bionic | 2.27 | Debian 10+, Ubuntu 18.04+ | -# | x64 | bionic | 2.27 | Debian 10+, Ubuntu 18.04+, RHEL 8+ | -# | riscv64 | noble | 2.39 | Debian 13+, Ubuntu 24.04+ | -# | loong64 | sid | latest| Debian unstable | +# Usage: +# docker build --build-arg BUILD_ARCH=arm64 . # # Supported architectures: arm, arm64, x86, x64, riscv64, loongarch64 -# Map BUILD_ARCH to the .NET cross image tag. ARG BUILD_ARCH=arm64 -ARG BASE_TAG=azurelinux-3.0-net10.0-cross-arm64 -FROM mcr.microsoft.com/dotnet-buildtools/prereqs:${BASE_TAG} + +# .NET cross images use "amd64" where SkiaSharp uses "x64". +# CI must pass IMAGE_ARCH=amd64 when BUILD_ARCH=x64. +# All other architectures: IMAGE_ARCH defaults to BUILD_ARCH. +ARG IMAGE_ARCH=${BUILD_ARCH} +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-${IMAGE_ARCH} SHELL ["/bin/bash", "-euo", "pipefail", "-c"] +ARG BUILD_ARCH + +# Derive all toolchain variables from BUILD_ARCH. +# This is the single source of truth — no external build-args needed. +RUN case "${BUILD_ARCH}" in \ + arm) TOOLCHAIN_ARCH=arm-linux-gnueabihf; \ + TOOLCHAIN_ARCH_SHORT=armhf; \ + TOOLCHAIN_ARCH_TARGET=armv7a-linux-gnueabihf; \ + ROOTFS_DIR=/crossrootfs/arm ;; \ + arm64) TOOLCHAIN_ARCH=aarch64-linux-gnu; \ + TOOLCHAIN_ARCH_SHORT=arm64; \ + TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu; \ + ROOTFS_DIR=/crossrootfs/arm64 ;; \ + x86) TOOLCHAIN_ARCH=i386-linux-gnu; \ + TOOLCHAIN_ARCH_SHORT=i386; \ + TOOLCHAIN_ARCH_TARGET=i686-linux-gnu; \ + ROOTFS_DIR=/crossrootfs/x86 ;; \ + x64) TOOLCHAIN_ARCH=x86_64-linux-gnu; \ + TOOLCHAIN_ARCH_SHORT=amd64; \ + TOOLCHAIN_ARCH_TARGET=x86_64-linux-gnu; \ + ROOTFS_DIR=/crossrootfs/x64 ;; \ + riscv64) TOOLCHAIN_ARCH=riscv64-linux-gnu; \ + TOOLCHAIN_ARCH_SHORT=riscv64; \ + TOOLCHAIN_ARCH_TARGET=riscv64-linux-gnu; \ + ROOTFS_DIR=/crossrootfs/riscv64 ;; \ + loongarch64) TOOLCHAIN_ARCH=loongarch64-linux-gnu; \ + TOOLCHAIN_ARCH_SHORT=loong64; \ + TOOLCHAIN_ARCH_TARGET=loongarch64-linux-gnu; \ + ROOTFS_DIR=/crossrootfs/loongarch64 ;; \ + *) echo "FATAL: Unsupported BUILD_ARCH=${BUILD_ARCH}" && exit 1 ;; \ + esac \ + && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ + && echo "export TOOLCHAIN_ARCH_SHORT=${TOOLCHAIN_ARCH_SHORT}" >> /etc/skia-env \ + && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env \ + && echo "export SYSROOT_PATH=${ROOTFS_DIR}/usr" >> /etc/skia-env \ + && echo "export SYSROOT_ROOT=${ROOTFS_DIR}" >> /etc/skia-env \ + && GCC_DIR=$(find ${ROOTFS_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ + && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env + # Install the .NET SDK (needed for Cake build scripts). ARG DOTNET_SDK_VERSION=10.0.108 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ - && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ && rm dotnet-install.sh \ - && dotnet help \ && dotnet --info ENV CC=clang CXX=clang++ -# Toolchain configuration for build.cake. -# These map to the env vars expected by native/linux-clang-cross/build.cake. -ARG TOOLCHAIN_ARCH=aarch64-linux-gnu -ARG TOOLCHAIN_ARCH_SHORT=arm64 -ARG TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu -ARG ROOTFS_DIR=/crossrootfs/arm64 - -RUN echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_SHORT=${TOOLCHAIN_ARCH_SHORT}" >> /etc/skia-env \ - && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env \ - && echo "export SYSROOT_PATH=${ROOTFS_DIR}/usr" >> /etc/skia-env \ - && echo "export SYSROOT_ROOT=${ROOTFS_DIR}" >> /etc/skia-env \ - && GCC_DIR=$(find ${ROOTFS_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ - && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env - -# Create c++/current symlinks so build.cake can reference headers version-agnostically. -RUN GCC_VER=$(basename "$(find ${ROOTFS_DIR}/usr/include/c++/ -mindepth 1 -maxdepth 1 -type d | sort -V | head -n1)") \ - && ln -sf ${GCC_VER} ${ROOTFS_DIR}/usr/include/c++/current \ - && if [ -d "${ROOTFS_DIR}/usr/include/${TOOLCHAIN_ARCH}/c++/${GCC_VER}" ]; then \ - ln -sf ${GCC_VER} ${ROOTFS_DIR}/usr/include/${TOOLCHAIN_ARCH}/c++/current ; \ - fi \ - && if [ -d "${ROOTFS_DIR}/usr/include/c++/${GCC_VER}/${TOOLCHAIN_ARCH}" ]; then \ - mkdir -p ${ROOTFS_DIR}/usr/include/c++/current 2>/dev/null || true ; \ - ln -sf ../${GCC_VER}/${TOOLCHAIN_ARCH} ${ROOTFS_DIR}/usr/include/c++/current/${TOOLCHAIN_ARCH} 2>/dev/null || true ; \ - fi - # Install fontconfig headers into the sysroot. -# Download .deb packages from the same Debian release as the sysroot and verify SHA-256. -ARG FC_DISTRO=bullseye -ARG FC_VERSION=2.13.1-4.2 -ARG FC_DEV_SHA256 -ARG FC_LIB_SHA256 -RUN if [ -n "${FC_DEV_SHA256:-}" ] && [ -n "${FC_LIB_SHA256:-}" ]; then \ - mkdir -p /tmp/fontconfig && cd /tmp/fontconfig \ - && APT_REPO=https://deb.debian.org/debian \ - && for pkg_info in \ - "libfontconfig-dev ${FC_DEV_SHA256}" \ - "libfontconfig1 ${FC_LIB_SHA256}"; do \ - pkg=$(echo $pkg_info | cut -d' ' -f1) ; \ - expected_sha=$(echo $pkg_info | cut -d' ' -f2) ; \ - url="${APT_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb" ; \ - echo "Downloading $url" ; \ - curl -fSL -o "${pkg}.deb" "$url" ; \ - actual_sha=$(sha256sum "${pkg}.deb" | cut -d' ' -f1) ; \ - if [ "$actual_sha" != "$expected_sha" ]; then \ - echo "FATAL: SHA-256 mismatch for ${pkg}.deb" ; \ - echo " Expected: $expected_sha" ; \ - echo " Actual: $actual_sha" ; \ - exit 1 ; \ - fi ; \ - echo " SHA-256 verified: $actual_sha" ; \ - ar x "${pkg}.deb" ; \ - tar xf data.tar.* -C ${ROOTFS_DIR}/ ; \ - rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ - done \ - && rm -rf /tmp/fontconfig ; \ - else \ - echo "WARNING: No fontconfig SHA-256 provided — skipping fontconfig install" ; \ - fi +# Fontconfig .deb packages are downloaded and SHA-256 verified. +# Package names differ: Ubuntu uses libfontconfig1-dev, Debian uses libfontconfig-dev. +RUN . /etc/skia-env \ + && case "${BUILD_ARCH}" in \ + arm) FC_REPO=http://ports.ubuntu.com/ubuntu-ports; \ + FC_VERSION=2.13.1-4.2ubuntu5; \ + FC_DEV_PKG=libfontconfig1-dev; \ + FC_DEV_SHA256=afb8bd6d46df0faef0f75e0570cd9e52532afbc6cf316be83a80fa5c2d737276; \ + FC_LIB_SHA256=0ac3c1b2b2dcc7cabd11f7e2daa4b3c0f89fd30d97d0722302fc7db623208cef ;; \ + arm64) FC_REPO=http://ports.ubuntu.com/ubuntu-ports; \ + FC_VERSION=2.12.6-0ubuntu2; \ + FC_DEV_PKG=libfontconfig1-dev; \ + FC_DEV_SHA256=93fc9ec2f69a87cbbc2efe7e5f3769c56ea9453a1449f28d78f192062b532975; \ + FC_LIB_SHA256=de5942257801f0d344cc55c95cc190aae4e434b3d8d0d77a029f210da755fdc3 ;; \ + x86) FC_REPO=http://archive.ubuntu.com/ubuntu; \ + FC_VERSION=2.12.6-0ubuntu2; \ + FC_DEV_PKG=libfontconfig1-dev; \ + FC_DEV_SHA256=cd775534e5377dbb26a789781bfd06b93ca63fe366eec9304b7339b54ba55905; \ + FC_LIB_SHA256=f4324b87b99f1750241d9b61d2a0658ccc76ff8d85e539d4f7f4a07723941119 ;; \ + x64) FC_REPO=http://archive.ubuntu.com/ubuntu; \ + FC_VERSION=2.12.6-0ubuntu2; \ + FC_DEV_PKG=libfontconfig1-dev; \ + FC_DEV_SHA256=ce4d63624128b06eef6ea0f2d04f0baa1cba22d7325fcbcce48ce5cbe2533b1a; \ + FC_LIB_SHA256=647bb8f09e751a39d488b0260db6015ce1118c56114cc771dd5bf907180f0c80 ;; \ + riscv64) FC_REPO=http://ports.ubuntu.com/ubuntu-ports; \ + FC_VERSION=2.15.0-1.1ubuntu2; \ + FC_DEV_PKG=libfontconfig1-dev; \ + FC_DEV_SHA256=31193095ea3678815b34a577d68b00cca7609fbea1cbe5fff04cfbe098be539b; \ + FC_LIB_SHA256=eb2574c2316922efe83b5a91da5278cf8abfc65051ddce28308032c99fafd36b ;; \ + loongarch64) FC_REPO=https://deb.debian.org/debian; \ + FC_VERSION=2.17.1-5; \ + FC_DEV_PKG=libfontconfig-dev; \ + FC_DEV_SHA256=0d3aee5780ecbab1f709036cedd935f2af690bd5392227411d97e6b160537456; \ + FC_LIB_SHA256=234a8998df450d4cd83f9c0a4a942fe1892b556122ff96fc737e0c74cb9c4421 ;; \ + esac \ + && mkdir -p /tmp/fontconfig && cd /tmp/fontconfig \ + && for pkg_info in \ + "${FC_DEV_PKG} ${FC_DEV_SHA256}" \ + "libfontconfig1 ${FC_LIB_SHA256}"; do \ + pkg=$(echo $pkg_info | cut -d' ' -f1) ; \ + expected_sha=$(echo $pkg_info | cut -d' ' -f2) ; \ + url="${FC_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb" ; \ + echo "Downloading $url" ; \ + curl -fSL -o "${pkg}.deb" "$url" ; \ + actual_sha=$(sha256sum "${pkg}.deb" | cut -d' ' -f1) ; \ + if [ "$actual_sha" != "$expected_sha" ]; then \ + echo "FATAL: SHA-256 mismatch for ${pkg}.deb" ; \ + echo " Expected: $expected_sha" ; \ + echo " Actual: $actual_sha" ; \ + exit 1 ; \ + fi ; \ + echo " SHA-256 verified: $actual_sha" ; \ + ar x "${pkg}.deb" ; \ + tar xf data.tar.* -C ${SYSROOT_ROOT}/ ; \ + rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ + done \ + && rm -rf /tmp/fontconfig WORKDIR /work diff --git a/scripts/infra/native/linux/docker/glibc/build-local.sh b/scripts/infra/native/linux/docker/glibc/build-local.sh index f3965b02d29..0aa8d162bdb 100755 --- a/scripts/infra/native/linux/docker/glibc/build-local.sh +++ b/scripts/infra/native/linux/docker/glibc/build-local.sh @@ -2,7 +2,7 @@ set -ex # Parameters: -# $1 - The target architecture to build for [ arm | arm64 | riscv64 | x86 | x64 ] +# $1 - The target architecture to build for [ arm | arm64 | riscv64 | loongarch64 | x86 | x64 ] # $2+ - Additional arguments to pass to the cake script DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" @@ -11,92 +11,24 @@ ARCH="${1:-arm64}" shift 1 || true EXTRA_ARGS="$@" -# Map architecture to .NET cross image tag and toolchain settings +# Validate architecture and map to .NET image tag arch case "$ARCH" in - arm) - BASE_TAG=azurelinux-3.0-net10.0-cross-arm - TOOLCHAIN_ARCH=arm-linux-gnueabihf - TOOLCHAIN_ARCH_SHORT=armhf - TOOLCHAIN_ARCH_TARGET=armv7a-linux-gnueabihf - ROOTFS_DIR=/crossrootfs/arm - VERIFY_GLIBC_MAX=2.35 - FC_DISTRO=jammy - FC_VERSION=2.13.1-4.4ubuntu0.1 - FC_DEV_SHA256="" - FC_LIB_SHA256="" - ;; - arm64) - BASE_TAG=azurelinux-3.0-net10.0-cross-arm64 - TOOLCHAIN_ARCH=aarch64-linux-gnu - TOOLCHAIN_ARCH_SHORT=arm64 - TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu - ROOTFS_DIR=/crossrootfs/arm64 - VERIFY_GLIBC_MAX=2.27 - FC_DISTRO=bionic - FC_VERSION=2.12.6-0ubuntu2 - FC_DEV_SHA256="" - FC_LIB_SHA256="" - ;; - riscv64) - BASE_TAG=azurelinux-3.0-net10.0-cross-riscv64 - TOOLCHAIN_ARCH=riscv64-linux-gnu - TOOLCHAIN_ARCH_SHORT=riscv64 - TOOLCHAIN_ARCH_TARGET=riscv64-linux-gnu - ROOTFS_DIR=/crossrootfs/riscv64 - VERIFY_GLIBC_MAX=2.39 - FC_DISTRO=noble - FC_VERSION=2.15.0-1.1ubuntu2 - FC_DEV_SHA256="" - FC_LIB_SHA256="" - ;; - x86) - BASE_TAG=azurelinux-3.0-net10.0-cross-x86 - TOOLCHAIN_ARCH=i386-linux-gnu - TOOLCHAIN_ARCH_SHORT=i386 - TOOLCHAIN_ARCH_TARGET=i686-linux-gnu - ROOTFS_DIR=/crossrootfs/x86 - VERIFY_GLIBC_MAX=2.27 - FC_DISTRO=bionic - FC_VERSION=2.12.6-0ubuntu2 - FC_DEV_SHA256="" - FC_LIB_SHA256="" - ;; + arm|arm64|x86|riscv64|loongarch64) + IMAGE_ARCH="$ARCH" ;; x64) - BASE_TAG=azurelinux-3.0-net10.0-cross-amd64 - TOOLCHAIN_ARCH=x86_64-linux-gnu - TOOLCHAIN_ARCH_SHORT=amd64 - TOOLCHAIN_ARCH_TARGET=x86_64-linux-gnu - ROOTFS_DIR=/crossrootfs/x64 - VERIFY_GLIBC_MAX=2.27 - FC_DISTRO=bionic - FC_VERSION=2.12.6-0ubuntu2 - FC_DEV_SHA256="" - FC_LIB_SHA256="" - ;; - *) - echo "Unsupported architecture: $ARCH" - echo "For loongarch64, use ../loongarch64/build-local.sh" - exit 1 - ;; + IMAGE_ARCH="amd64" ;; + *) echo "Unsupported architecture: $ARCH"; exit 1 ;; esac -# Build the glibc image +# Build the glibc image — the Dockerfile handles all arch-specific config internally. (cd "$DIR" && docker build --tag skiasharp-linux-gnu-cross-$ARCH \ --platform=linux/amd64 \ - --build-arg BASE_TAG=$BASE_TAG \ --build-arg BUILD_ARCH=$ARCH \ - --build-arg TOOLCHAIN_ARCH=$TOOLCHAIN_ARCH \ - --build-arg TOOLCHAIN_ARCH_SHORT=$TOOLCHAIN_ARCH_SHORT \ - --build-arg TOOLCHAIN_ARCH_TARGET=$TOOLCHAIN_ARCH_TARGET \ - --build-arg ROOTFS_DIR=$ROOTFS_DIR \ - --build-arg FC_DISTRO=$FC_DISTRO \ - --build-arg FC_VERSION=$FC_VERSION \ - --build-arg FC_DEV_SHA256=$FC_DEV_SHA256 \ - --build-arg FC_LIB_SHA256=$FC_LIB_SHA256 \ + --build-arg IMAGE_ARCH=$IMAGE_ARCH \ .) (cd "$DIR/../../../../../.." && docker run --rm --name skiasharp-linux-gnu-cross-$ARCH --volume $(pwd):/work skiasharp-linux-gnu-cross-$ARCH /bin/bash -c " \ dotnet tool restore ; \ - dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=$ARCH --verifyGlibcMax=$VERIFY_GLIBC_MAX $EXTRA_ARGS ") + dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=$ARCH $EXTRA_ARGS ") diff --git a/scripts/infra/native/linux/docker/loongarch64/build-local.sh b/scripts/infra/native/linux/docker/loongarch64/build-local.sh deleted file mode 100755 index 289587c6cf0..00000000000 --- a/scripts/infra/native/linux/docker/loongarch64/build-local.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -set -ex - -# Build loongarch64 glibc cross-compilation image and run the build. -# Uses the glibc Dockerfile with loongarch64-specific BASE_TAG. -# -# Parameters: -# $@ - Additional arguments to pass to the cake script - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -EXTRA_ARGS="$@" - -# Build using the glibc Dockerfile with loongarch64 args -(cd "$DIR/../glibc" && - docker build --tag skiasharp-linux-gnu-cross-loongarch64 \ - --platform=linux/amd64 \ - --build-arg BASE_TAG=azurelinux-3.0-net10.0-cross-loongarch64 \ - --build-arg BUILD_ARCH=loongarch64 \ - --build-arg TOOLCHAIN_ARCH=loongarch64-linux-gnu \ - --build-arg TOOLCHAIN_ARCH_SHORT=loong64 \ - --build-arg TOOLCHAIN_ARCH_TARGET=loongarch64-linux-gnu \ - --build-arg ROOTFS_DIR=/crossrootfs/loongarch64 \ - --build-arg FC_DISTRO=sid \ - .) - -(cd "$DIR/../../../../../.." && - docker run --rm --name skiasharp-linux-gnu-cross-loongarch64 --volume $(pwd):/work skiasharp-linux-gnu-cross-loongarch64 /bin/bash -c " \ - dotnet tool restore ; \ - dotnet cake --target=externals-linux-clang-cross --configuration=Release --buildarch=loongarch64 $EXTRA_ARGS ") From 61df9c52f64e0bc468a230aeb8832d6bb6c056ce Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 23:20:11 +0200 Subject: [PATCH 29/45] Fix bionic Dockerfile ENV duplication Move ANDROID_NDK_HOME and PATH ENV declarations before the RUN that uses them, and derive both from the NDK_VERSION ARG. This eliminates the hardcoded path duplication (previously the same path appeared in 3 places: ARG, RUN, and two separate ENV lines). Now the flow is: - ARG NDK_VERSION defines the version once - ENV ANDROID_NDK_HOME and PATH use it (available in subsequent RUN) - RUN writes /etc/skia-env which re-exports ANDROID_NDK_HOME for the Cake startup script Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/infra/native/linux/docker/bionic/Dockerfile | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index 228bf587185..598c4d5cf72 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -26,27 +26,26 @@ RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-inst ARG BUILD_ARCH=x64 -# Map BUILD_ARCH to Android triple and configure NDK paths. +# NDK path — single source of truth for both build-time and runtime. ARG NDK_VERSION=27.2.12479018 +ENV ANDROID_NDK_HOME=/usr/local/android-sdk/ndk/${NDK_VERSION} +ENV PATH=${PATH}:${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin + +# Map BUILD_ARCH to Android triple and write skia-env for Cake. RUN case ${BUILD_ARCH} in \ x64) ANDROID_TRIPLE=x86_64-linux-android29 ; ANDROID_SYSROOT_LIB=x86_64-linux-android ;; \ arm64) ANDROID_TRIPLE=aarch64-linux-android29 ; ANDROID_SYSROOT_LIB=aarch64-linux-android ;; \ *) echo "FATAL: Unsupported BUILD_ARCH=${BUILD_ARCH}" && exit 1 ;; \ esac \ - && NDK_ROOT="/usr/local/android-sdk/ndk/${NDK_VERSION}" \ && echo "export ANDROID_TRIPLE=${ANDROID_TRIPLE}" > /etc/skia-env \ && echo "export ANDROID_SYSROOT_LIB=${ANDROID_SYSROOT_LIB}" >> /etc/skia-env \ && echo "export NDK_HOST_TAG=linux-x86_64" >> /etc/skia-env \ - && echo "export ANDROID_NDK_HOME=${NDK_ROOT}" >> /etc/skia-env \ + && echo "export ANDROID_NDK_HOME=${ANDROID_NDK_HOME}" >> /etc/skia-env \ && echo "export CC=${ANDROID_TRIPLE}-clang" >> /etc/skia-env \ && echo "export CXX=${ANDROID_TRIPLE}-clang++" >> /etc/skia-env \ && echo "export AR=llvm-ar" >> /etc/skia-env \ && echo "export LD=lld" >> /etc/skia-env -# Add NDK toolchain to PATH. -ENV ANDROID_NDK_HOME=/usr/local/android-sdk/ndk/27.2.12479018 -ENV PATH=$PATH:/usr/local/android-sdk/ndk/27.2.12479018/toolchains/llvm/prebuilt/linux-x86_64/bin - # Create empty libpthread stub — Bionic bundles pthreads into libc, but # Skia's GN build adds -lpthread for target_os='linux' builds. RUN . /etc/skia-env \ From e1e6e6f6db6d5bd70eebc52e82b3861893ee7292 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 23:27:35 +0200 Subject: [PATCH 30/45] Remove redundant ANDROID_NDK_HOME from skia-env MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Already set via Docker ENV — no need to re-export in the sourced file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/infra/native/linux/docker/bionic/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index 598c4d5cf72..316bcf62415 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -40,7 +40,6 @@ RUN case ${BUILD_ARCH} in \ && echo "export ANDROID_TRIPLE=${ANDROID_TRIPLE}" > /etc/skia-env \ && echo "export ANDROID_SYSROOT_LIB=${ANDROID_SYSROOT_LIB}" >> /etc/skia-env \ && echo "export NDK_HOST_TAG=linux-x86_64" >> /etc/skia-env \ - && echo "export ANDROID_NDK_HOME=${ANDROID_NDK_HOME}" >> /etc/skia-env \ && echo "export CC=${ANDROID_TRIPLE}-clang" >> /etc/skia-env \ && echo "export CXX=${ANDROID_TRIPLE}-clang++" >> /etc/skia-env \ && echo "export AR=llvm-ar" >> /etc/skia-env \ From 5a4ed3af6840e81c8c3fb3299e68dfe961bf054a Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 23:30:19 +0200 Subject: [PATCH 31/45] Discover NDK dynamically, use full paths instead of PATH Instead of hardcoding NDK_VERSION and adding the bin dir to PATH, dynamically discover the installed NDK and write full tool paths (CC, CXX, AR, LD) to /etc/skia-env. This means: - No hardcoded NDK version (adapts to whatever the base image ships) - No PATH manipulation (GN gets absolute paths) - Fails fast if NDK is missing or layout changes - ANDROID_NDK_HOME also in skia-env (needed by libpthread RUN step) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/bionic/Dockerfile | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index 316bcf62415..eff7346a89d 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -26,29 +26,34 @@ RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-inst ARG BUILD_ARCH=x64 -# NDK path — single source of truth for both build-time and runtime. -ARG NDK_VERSION=27.2.12479018 -ENV ANDROID_NDK_HOME=/usr/local/android-sdk/ndk/${NDK_VERSION} -ENV PATH=${PATH}:${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin - -# Map BUILD_ARCH to Android triple and write skia-env for Cake. +# Map BUILD_ARCH to Android triple, discover NDK dynamically, and write skia-env. RUN case ${BUILD_ARCH} in \ x64) ANDROID_TRIPLE=x86_64-linux-android29 ; ANDROID_SYSROOT_LIB=x86_64-linux-android ;; \ arm64) ANDROID_TRIPLE=aarch64-linux-android29 ; ANDROID_SYSROOT_LIB=aarch64-linux-android ;; \ *) echo "FATAL: Unsupported BUILD_ARCH=${BUILD_ARCH}" && exit 1 ;; \ esac \ + && NDK_HOME=$(ls -d /usr/local/android-sdk/ndk/*/ | head -1) \ + && NDK_HOME=${NDK_HOME%/} \ + && if [ -z "$NDK_HOME" ] || [ ! -d "$NDK_HOME" ]; then \ + echo "FATAL: No Android NDK found in /usr/local/android-sdk/ndk/" && exit 1 ; \ + fi \ + && NDK_BIN="${NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin" \ + && if [ ! -d "$NDK_BIN" ]; then \ + echo "FATAL: NDK bin directory not found: $NDK_BIN" && exit 1 ; \ + fi \ && echo "export ANDROID_TRIPLE=${ANDROID_TRIPLE}" > /etc/skia-env \ && echo "export ANDROID_SYSROOT_LIB=${ANDROID_SYSROOT_LIB}" >> /etc/skia-env \ && echo "export NDK_HOST_TAG=linux-x86_64" >> /etc/skia-env \ - && echo "export CC=${ANDROID_TRIPLE}-clang" >> /etc/skia-env \ - && echo "export CXX=${ANDROID_TRIPLE}-clang++" >> /etc/skia-env \ - && echo "export AR=llvm-ar" >> /etc/skia-env \ - && echo "export LD=lld" >> /etc/skia-env + && echo "export ANDROID_NDK_HOME=${NDK_HOME}" >> /etc/skia-env \ + && echo "export CC=${NDK_BIN}/${ANDROID_TRIPLE}-clang" >> /etc/skia-env \ + && echo "export CXX=${NDK_BIN}/${ANDROID_TRIPLE}-clang++" >> /etc/skia-env \ + && echo "export AR=${NDK_BIN}/llvm-ar" >> /etc/skia-env \ + && echo "export LD=${NDK_BIN}/lld" >> /etc/skia-env # Create empty libpthread stub — Bionic bundles pthreads into libc, but # Skia's GN build adds -lpthread for target_os='linux' builds. RUN . /etc/skia-env \ - && NDK_SYSROOT="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot" \ + && NDK_SYSROOT="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot" \ && PTHREAD_TARGET="$NDK_SYSROOT/usr/lib/$ANDROID_SYSROOT_LIB/libpthread.a" \ && if [ -e "$PTHREAD_TARGET" ]; then \ echo "FATAL: NDK already ships libpthread.a — review needed" && exit 1 ; \ From d32ab6f3d1e4d97a9af8eb80e29cd125f91d0aec Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 25 May 2026 23:55:23 +0200 Subject: [PATCH 32/45] Fix alpine sysroot: use SYSROOT_ROOT instead of hardcoded /alpine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old Docker images had the musl sysroot at /alpine. The new .NET official images place it at /crossrootfs// like all other variants. Remove the special-case for alpine in build.cake — SYSROOT_ROOT from /etc/skia-env is the correct path for all variants now. Found via PR review comment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux-clang-cross/build.cake | 1 - 1 file changed, 1 deletion(-) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index 85b230aa353..c5ca50e60bd 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -23,7 +23,6 @@ string GetGnArgs(string arch) { var sysrootArg = BUILD_VARIANT switch { - "alpine" or "alpinenodeps" => "'--sysroot=/alpine', ", _ when !string.IsNullOrEmpty(SYSROOT_ROOT) => $"'--sysroot={SYSROOT_ROOT}', ", _ => "", }; From 03282bd36bded9e27b52f657148695c4124bb9be Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 26 May 2026 00:26:17 +0200 Subject: [PATCH 33/45] Restore verifyGlibcMax per arch to detect sysroot changes Each glibc target now has its own max version cap matching the sysroot in the .NET base image. If the base image bumps its sysroot, the build will fail explicitly instead of silently shipping a binary with higher glibc requirements. Previous values (all arches shared one): 2.31 New per-arch values: arm64=2.27, x64=2.27, x86=2.27, arm=2.35, riscv64=2.39, loongarch64=2.42 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/azure-templates-stages-native-linux.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/azure-templates-stages-native-linux.yml b/scripts/azure-templates-stages-native-linux.yml index 652258cb8e8..696667f8b05 100644 --- a/scripts/azure-templates-stages-native-linux.yml +++ b/scripts/azure-templates-stages-native-linux.yml @@ -28,21 +28,30 @@ stages: gnArgs: skia_use_fontconfig=false matrix: # .NET official cross images (glibc) - - ${{ each arch in split('arm,arm64,x86', ',') }}: + # verifyGlibcMax ensures we detect if the base image bumps its sysroot + - ${{ each arch in split('arm64,x86', ',') }}: - arch: ${{ arch }} docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross + additionalArgs: --verifyGlibcMax=2.27 + - arch: arm + docker: scripts/infra/native/linux/docker/glibc + target: externals-linux-clang-cross + additionalArgs: --verifyGlibcMax=2.35 # x64 needs IMAGE_ARCH=amd64 (.NET uses "amd64" not "x64" in image tags) - arch: x64 docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross dockerArgs: --build-arg IMAGE_ARCH=amd64 + additionalArgs: --verifyGlibcMax=2.27 - arch: riscv64 docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross + additionalArgs: --verifyGlibcMax=2.39 - arch: loongarch64 docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross + additionalArgs: --verifyGlibcMax=2.42 # .NET official cross images (musl/Alpine) - ${{ each arch in split('arm,arm64,riscv64,loongarch64', ',') }}: - arch: ${{ arch }} From 3208d7a90c79d00e9e477896fb381b09fa6dc00b Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 26 May 2026 01:38:54 +0200 Subject: [PATCH 34/45] Fix linker: bypass clang driver rewrite for C++ ABI symbols The .NET cross-compilation sysroots provide libc++.a (static) but NOT libc++abi. The C++ ABI symbols (__cxa_throw, __cxa_begin_catch, operator new/delete, etc.) are provided by GCC's libstdc++.a (which bundles libsupc++, the ABI layer). Two complications prevented a simple -lstdc++ from working: 1. GN places extra_ldflags BEFORE the object archives in the link command, so libraries specified there won't satisfy forward references from objects that appear later. --whole-archive forces all objects from libstdc++.a to be included regardless of ordering. 2. When -stdlib=libc++ is active, clang's driver silently REWRITES -lstdc++ into -lc++ before invoking lld. The only way to pass -lstdc++ through to the actual linker is via -Wl,-lstdc++ which bypasses the driver entirely. The fix combines both: -Wl,--whole-archive,-lstdc++,--no-whole-archive Verified locally: - glibc x64: libSkiaSharp.so + libHarfBuzzSharp.so link successfully - Alpine arm64: libSkiaSharp.so + libHarfBuzzSharp.so link successfully Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux-clang-cross/build.cake | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index c5ca50e60bd..d0b4818cca9 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -52,10 +52,21 @@ string GetGnArgs(string arch) $"'-I{sysroot}/include', " + $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}' "; + // The sysroot provides libc++.a (static) but NOT libc++abi. The C++ ABI + // symbols (__cxa_throw, __cxa_begin_catch, operator new/delete, etc.) and + // std::runtime_error come from GCC's libstdc++.a (which includes libsupc++). + // + // Two complications with the link command: + // 1. extra_ldflags appears BEFORE the object archives in GN's link command, + // so we need --whole-archive to force inclusion regardless of ordering. + // 2. -stdlib=libc++ causes clang's driver to REWRITE -lstdc++ into -lc++. + // We bypass this by using -Wl,-lstdc++ to pass directly to the linker. + var abiLib = "'-Wl,--whole-archive,-lstdc++,--no-whole-archive'"; + return $"extra_asmflags+=[ {init}, '-no-integrated-as', {bin}, {includes} ] " + $"extra_cflags+=[ {init}, {bin}, {includes} ] " + - $"extra_ldflags+=[ {init}, {bin}, {libs}, {linker} ] " + + $"extra_ldflags+=[ {init}, {bin}, {libs}, {abiLib}, {linker} ] " + ADDITIONAL_GN_ARGS; } From eca0aa5c546fffe4891e809dd8cc309ddcf107c2 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 26 May 2026 15:44:12 +0200 Subject: [PATCH 35/45] Fix cross-build failures: x86 libc++, arm/riscv64 zstd Three fixes for remaining CI failures after the Docker migration: 1. x86: Build libc++ and libc++abi from LLVM 20.1.8 source in Dockerfile. The .NET x86 cross-image only has GCC 6/7 libstdc++ (no C++20) and no libc++.a. We sparse-clone LLVM, cross-compile with -fPIC for i686, and install both .a files into the sysroot. Build takes ~100s (cached after first image build). 2. arm/riscv64: Install zstd via tdnf. Ubuntu 22.04+ .deb packages use zstd-compressed data.tar.zst; the Azure Linux 3.0 base has zstd-libs but not the CLI tool needed for extraction. 3. build.cake: Three-way stdlib detection: - libc++.a + libc++abi.a (x86): whole-archive libc++abi for ABI symbols - libc++.a only (x64, arm64, etc): whole-archive libstdc++ for ABI - Neither: pure libstdc++ mode (fallback) Locally verified: x86, x64, arm64 (alpine), arm image builds all pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux-clang-cross/build.cake | 74 +++++++++++++------ .../native/linux/docker/glibc/Dockerfile | 65 +++++++++++++++- 2 files changed, 117 insertions(+), 22 deletions(-) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index d0b4818cca9..e142748345e 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -40,33 +40,65 @@ string GetGnArgs(string arch) // AFTER the directory where cmath was found. If C headers are listed first, // they won't be searched by #include_next from C++ headers. // - // The Dockerfile creates: include/c++/current -> VERSION - // Arch-specific headers vary by layout: - // Debootstrap: {sysroot}/include/{ARCH}/c++/current/ - // Cross-packages: {sysroot}/include/c++/current/{ARCH}/ - // We include both — non-existent paths are harmless to clang. - var includes = - $"'-I{sysroot}/include/c++/current', " + - $"'-I{sysroot}/include/c++/current/{TOOLCHAIN_ARCH}', " + - $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}/c++/current', " + - $"'-I{sysroot}/include', " + - $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}' "; + // When libc++ headers (c++/v1/) exist, clang finds them automatically via + // -stdlib=libc++ — no explicit -I needed. When only libstdc++ exists (no v1/), + // we add explicit paths to c++/current (a symlink to the highest GCC version). + var hasLibcppHeaders = System.IO.Directory.Exists($"{sysroot}/include/c++/v1"); + var includes = hasLibcppHeaders + ? $"'-I{sysroot}/include', " + + $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}' " + : $"'-I{sysroot}/include/c++/current', " + + $"'-I{sysroot}/include/c++/current/{TOOLCHAIN_ARCH}', " + + $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}/c++/current', " + + $"'-I{sysroot}/include', " + + $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}' "; - // The sysroot provides libc++.a (static) but NOT libc++abi. The C++ ABI - // symbols (__cxa_throw, __cxa_begin_catch, operator new/delete, etc.) and - // std::runtime_error come from GCC's libstdc++.a (which includes libsupc++). + // Detect libc++ availability. The Dockerfile ensures libc++ HEADERS (c++/v1/) + // exist for all arches (copied from x64 for x86). But libc++.a may not exist + // for all arches. When libc++.a is missing, we override the linker's stdlib to + // libstdc++ and link it directly. // - // Two complications with the link command: - // 1. extra_ldflags appears BEFORE the object archives in GN's link command, - // so we need --whole-archive to force inclusion regardless of ordering. - // 2. -stdlib=libc++ causes clang's driver to REWRITE -lstdc++ into -lc++. - // We bypass this by using -Wl,-lstdc++ to pass directly to the linker. - var abiLib = "'-Wl,--whole-archive,-lstdc++,--no-whole-archive'"; + // Three scenarios: + // 1. libc++.a + libc++abi.a both exist (x86 after Dockerfile builds them): + // Let clang link naturally — -stdlib=libc++ adds -lc++ -lc++abi automatically. + // No explicit ABI lib needed. + // 2. libc++.a exists but NOT libc++abi.a (arm64, x64, arm, riscv64, loongarch64): + // ABI symbols (operator delete, __cxa_throw) come from GCC's libstdc++. + // --whole-archive forces inclusion before objects reference them. + // -Wl,-lstdc++ bypasses clang driver's rewrite of -lstdc++ → -lc++. + // 3. Neither exists: fall back to libstdc++ for both compile and link. + var sysrootRoot = SYSROOT_ROOT; + var hasLibcppLib = System.IO.File.Exists($"{sysrootRoot}/usr/lib/libc++.a") + || System.IO.File.Exists($"{sysroot}/lib/libc++.a"); + var hasLibcppAbi = System.IO.File.Exists($"{sysrootRoot}/usr/lib/libc++abi.a") + || System.IO.File.Exists($"{sysroot}/lib/libc++abi.a"); + string stdlibLinkOverride; + string abiLib; + if (hasLibcppLib && hasLibcppAbi) + { + // Full libc++ stack. extra_ldflags appear BEFORE object archives in GN's link + // command, so --whole-archive forces libc++abi symbols to be included regardless + // of link ordering. -Wl bypasses clang driver's library name rewriting. + stdlibLinkOverride = ""; + abiLib = "'-Wl,--whole-archive,-lc++abi,--no-whole-archive'"; + } + else if (hasLibcppLib) + { + // libc++.a but no libc++abi — get ABI symbols from libstdc++ (libsupc++ inside). + stdlibLinkOverride = ""; + abiLib = "'-Wl,--whole-archive,-lstdc++,--no-whole-archive'"; + } + else + { + // No libc++ at all — pure libstdc++ mode. + stdlibLinkOverride = "'-stdlib=libstdc++', "; + abiLib = "'-lstdc++'"; + } return $"extra_asmflags+=[ {init}, '-no-integrated-as', {bin}, {includes} ] " + $"extra_cflags+=[ {init}, {bin}, {includes} ] " + - $"extra_ldflags+=[ {init}, {bin}, {libs}, {abiLib}, {linker} ] " + + $"extra_ldflags+=[ {init}, {bin}, {libs}, {stdlibLinkOverride}{abiLib}, {linker} ] " + ADDITIONAL_GN_ARGS; } diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index b59ae9854f2..6514bbd395c 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -19,6 +19,11 @@ ARG BUILD_ARCH=arm64 # CI must pass IMAGE_ARCH=amd64 when BUILD_ARCH=x64. # All other architectures: IMAGE_ARCH defaults to BUILD_ARCH. ARG IMAGE_ARCH=${BUILD_ARCH} + +# The x86 image lacks libc++ headers (only GCC 6/7 which don't support C++20). +# Pull libc++ headers from the x64 image — they're architecture-independent. +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64 AS libcxx-headers + FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-${IMAGE_ARCH} SHELL ["/bin/bash", "-euo", "pipefail", "-c"] @@ -60,7 +65,62 @@ RUN case "${BUILD_ARCH}" in \ && echo "export SYSROOT_PATH=${ROOTFS_DIR}/usr" >> /etc/skia-env \ && echo "export SYSROOT_ROOT=${ROOTFS_DIR}" >> /etc/skia-env \ && GCC_DIR=$(find ${ROOTFS_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ - && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env + && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env \ + # Create c++/current symlink for sysroots that use libstdc++ headers (no libc++/v1). + # The build.cake include paths reference c++/current; this ensures they resolve. + && if [ ! -d "${ROOTFS_DIR}/usr/include/c++/v1" ]; then \ + CXX_VER=$(ls -1 ${ROOTFS_DIR}/usr/include/c++/ | grep -E '^[0-9]+$' | sort -n | tail -1) \ + && ln -sf "${CXX_VER}" "${ROOTFS_DIR}/usr/include/c++/current" \ + && echo "Created c++/current -> ${CXX_VER} (no libc++ headers in sysroot)" ; \ + fi + +# For sysroots without libc++ (e.g., x86), copy the architecture-independent +# libc++ headers from the x64 image and build libc++/libc++abi from LLVM source. +# The x86 sysroot has GCC 6/7 headers (no C++20 support) and no libc++.a. +ARG BUILD_ARCH +COPY --from=libcxx-headers /crossrootfs/x64/usr/include/c++/v1 /tmp/libcxx-headers/ +RUN . /etc/skia-env \ + && if [ ! -d "${SYSROOT_ROOT}/usr/include/c++/v1" ]; then \ + cp -a /tmp/libcxx-headers "${SYSROOT_ROOT}/usr/include/c++/v1" \ + && echo "Installed libc++ headers into ${SYSROOT_ROOT}/usr/include/c++/v1" ; \ + fi \ + && rm -rf /tmp/libcxx-headers + +# Build libc++ and libc++abi from source for arches that lack them (x86). +# Must use -fPIC (CMAKE_POSITION_INDEPENDENT_CODE) since the .a is linked into .so. +ARG LLVM_VERSION=20.1.8 +RUN . /etc/skia-env \ + && if [ ! -f "${SYSROOT_ROOT}/usr/lib/libc++.a" ]; then \ + echo "Building libc++ ${LLVM_VERSION} for ${TOOLCHAIN_ARCH_TARGET}..." \ + && git clone --depth=1 --branch llvmorg-${LLVM_VERSION} \ + --filter=blob:none --sparse \ + https://github.com/llvm/llvm-project.git /tmp/llvm \ + && cd /tmp/llvm \ + && git sparse-checkout set runtimes libcxx libcxxabi libc cmake llvm/cmake llvm/utils/llvm-lit \ + && cmake -G Ninja -S runtimes -B /tmp/llvm-build \ + -DLLVM_COMMON_CMAKE_UTILS=/tmp/llvm/cmake \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_C_COMPILER_TARGET=${TOOLCHAIN_ARCH_TARGET} \ + -DCMAKE_CXX_COMPILER_TARGET=${TOOLCHAIN_ARCH_TARGET} \ + -DCMAKE_SYSROOT=${SYSROOT_ROOT} \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_C_COMPILER_WORKS=ON \ + -DCMAKE_CXX_COMPILER_WORKS=ON \ + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \ + -DLIBCXX_ENABLE_SHARED=OFF \ + -DLIBCXXABI_ENABLE_SHARED=OFF \ + -DLIBCXX_CXX_ABI=libcxxabi \ + -DLIBCXXABI_USE_LLVM_UNWINDER=OFF \ + -DLIBCXX_INCLUDE_BENCHMARKS=OFF \ + -DLIBCXX_INCLUDE_TESTS=OFF \ + -DLIBCXXABI_INCLUDE_TESTS=OFF \ + && ninja -C /tmp/llvm-build cxx cxxabi \ + && cp /tmp/llvm-build/lib/libc++.a ${SYSROOT_ROOT}/usr/lib/ \ + && cp /tmp/llvm-build/lib/libc++abi.a ${SYSROOT_ROOT}/usr/lib/ \ + && rm -rf /tmp/llvm /tmp/llvm-build \ + && echo "Installed libc++.a and libc++abi.a for ${TOOLCHAIN_ARCH_TARGET}" ; \ + fi # Install the .NET SDK (needed for Cake build scripts). ARG DOTNET_SDK_VERSION=10.0.108 @@ -72,6 +132,9 @@ RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-inst ENV CC=clang CXX=clang++ +# zstd is needed to extract newer .deb packages (Ubuntu 22.04+ uses zstd compression). +RUN tdnf install -y zstd && tdnf clean all + # Install fontconfig headers into the sysroot. # Fontconfig .deb packages are downloaded and SHA-256 verified. # Package names differ: Ubuntu uses libfontconfig1-dev, Debian uses libfontconfig-dev. From b482f67d90e96c630b797f484cb3bae461ee5613 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 26 May 2026 16:50:45 +0200 Subject: [PATCH 36/45] Remove fallback logic: deterministic libc++ linking for all arches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All cross-images now provide libc++.a (x86 gets it from Dockerfile.x86-libcxx, all others from .NET official images). Remove runtime detection of libc++ availability and c++/v1 headers — the build is deterministic with a single code path for all architectures. Changes: - build.cake: Remove three-way stdlib detection (libc++abi/libc++/libstdc++), hardcode single strategy: --whole-archive,-lstdc++ for ABI symbols - build.cake: Remove c++/v1 header detection, hardcode libc++ include path - Dockerfile: Replace c++/current symlink fallback with hard fail if c++/v1 headers are missing (catches broken base images early) - Add Dockerfile.x86-libcxx: builds libc++ for x86 using .NET team's pattern (-DLIBCXX_CXX_ABI=libstdc++), producing same layout as other arches Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- externals/skia | 2 +- native/linux-clang-cross/build.cake | 65 ++----- .../azure-templates-stages-native-linux.yml | 15 +- .../linux/docker/glibc-x86/.dockerignore | 4 + .../native/linux/docker/glibc-x86/Dockerfile | 162 ++++++++++++++++++ .../native/linux/docker/glibc-x86/startup.sh | 6 + .../native/linux/docker/glibc/Dockerfile | 77 ++------- .../native/linux/docker/glibc/build-local.sh | 20 ++- 8 files changed, 219 insertions(+), 132 deletions(-) create mode 100644 scripts/infra/native/linux/docker/glibc-x86/.dockerignore create mode 100644 scripts/infra/native/linux/docker/glibc-x86/Dockerfile create mode 100755 scripts/infra/native/linux/docker/glibc-x86/startup.sh diff --git a/externals/skia b/externals/skia index 7a2d809b3ce..fd80b8918b2 160000 --- a/externals/skia +++ b/externals/skia @@ -1 +1 @@ -Subproject commit 7a2d809b3ce116815073877469025dfd6ed42760 +Subproject commit fd80b8918b2e7f3f714757bd5e805030f62c08af diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index e142748345e..508dd32c3d0 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -40,65 +40,22 @@ string GetGnArgs(string arch) // AFTER the directory where cmath was found. If C headers are listed first, // they won't be searched by #include_next from C++ headers. // - // When libc++ headers (c++/v1/) exist, clang finds them automatically via - // -stdlib=libc++ — no explicit -I needed. When only libstdc++ exists (no v1/), - // we add explicit paths to c++/current (a symlink to the highest GCC version). - var hasLibcppHeaders = System.IO.Directory.Exists($"{sysroot}/include/c++/v1"); - var includes = hasLibcppHeaders - ? $"'-I{sysroot}/include', " + - $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}' " - : $"'-I{sysroot}/include/c++/current', " + - $"'-I{sysroot}/include/c++/current/{TOOLCHAIN_ARCH}', " + - $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}/c++/current', " + - $"'-I{sysroot}/include', " + - $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}' "; + // All cross-images have libc++ headers installed at c++/v1/. Clang finds them + // automatically via -stdlib=libc++ — we only need the C system include paths. + var includes = $"'-I{sysroot}/include', " + + $"'-I{sysroot}/include/{TOOLCHAIN_ARCH}' "; - // Detect libc++ availability. The Dockerfile ensures libc++ HEADERS (c++/v1/) - // exist for all arches (copied from x64 for x86). But libc++.a may not exist - // for all arches. When libc++.a is missing, we override the linker's stdlib to - // libstdc++ and link it directly. - // - // Three scenarios: - // 1. libc++.a + libc++abi.a both exist (x86 after Dockerfile builds them): - // Let clang link naturally — -stdlib=libc++ adds -lc++ -lc++abi automatically. - // No explicit ABI lib needed. - // 2. libc++.a exists but NOT libc++abi.a (arm64, x64, arm, riscv64, loongarch64): - // ABI symbols (operator delete, __cxa_throw) come from GCC's libstdc++. - // --whole-archive forces inclusion before objects reference them. - // -Wl,-lstdc++ bypasses clang driver's rewrite of -lstdc++ → -lc++. - // 3. Neither exists: fall back to libstdc++ for both compile and link. - var sysrootRoot = SYSROOT_ROOT; - var hasLibcppLib = System.IO.File.Exists($"{sysrootRoot}/usr/lib/libc++.a") - || System.IO.File.Exists($"{sysroot}/lib/libc++.a"); - var hasLibcppAbi = System.IO.File.Exists($"{sysrootRoot}/usr/lib/libc++abi.a") - || System.IO.File.Exists($"{sysroot}/lib/libc++abi.a"); - string stdlibLinkOverride; - string abiLib; - if (hasLibcppLib && hasLibcppAbi) - { - // Full libc++ stack. extra_ldflags appear BEFORE object archives in GN's link - // command, so --whole-archive forces libc++abi symbols to be included regardless - // of link ordering. -Wl bypasses clang driver's library name rewriting. - stdlibLinkOverride = ""; - abiLib = "'-Wl,--whole-archive,-lc++abi,--no-whole-archive'"; - } - else if (hasLibcppLib) - { - // libc++.a but no libc++abi — get ABI symbols from libstdc++ (libsupc++ inside). - stdlibLinkOverride = ""; - abiLib = "'-Wl,--whole-archive,-lstdc++,--no-whole-archive'"; - } - else - { - // No libc++ at all — pure libstdc++ mode. - stdlibLinkOverride = "'-stdlib=libstdc++', "; - abiLib = "'-lstdc++'"; - } + // All cross-images provide libc++.a built with -DLIBCXX_CXX_ABI=libstdc++. + // This means libc++ delegates ABI symbols (operator delete, __cxa_throw, etc.) + // to GCC's libstdc++. We link libstdc++ with --whole-archive so its symbols are + // available before GN's object archives reference them (extra_ldflags appear first + // in the link command). -Wl bypasses clang's driver rewrite of -lstdc++ → -lc++. + var abiLib = "'-Wl,--whole-archive,-lstdc++,--no-whole-archive'"; return $"extra_asmflags+=[ {init}, '-no-integrated-as', {bin}, {includes} ] " + $"extra_cflags+=[ {init}, {bin}, {includes} ] " + - $"extra_ldflags+=[ {init}, {bin}, {libs}, {stdlibLinkOverride}{abiLib}, {linker} ] " + + $"extra_ldflags+=[ {init}, {bin}, {libs}, {abiLib}, {linker} ] " + ADDITIONAL_GN_ARGS; } diff --git a/scripts/azure-templates-stages-native-linux.yml b/scripts/azure-templates-stages-native-linux.yml index 696667f8b05..64583850db1 100644 --- a/scripts/azure-templates-stages-native-linux.yml +++ b/scripts/azure-templates-stages-native-linux.yml @@ -29,11 +29,16 @@ stages: matrix: # .NET official cross images (glibc) # verifyGlibcMax ensures we detect if the base image bumps its sysroot - - ${{ each arch in split('arm64,x86', ',') }}: - - arch: ${{ arch }} - docker: scripts/infra/native/linux/docker/glibc - target: externals-linux-clang-cross - additionalArgs: --verifyGlibcMax=2.27 + - arch: arm64 + docker: scripts/infra/native/linux/docker/glibc + target: externals-linux-clang-cross + additionalArgs: --verifyGlibcMax=2.27 + # x86 needs libc++ built first (the .NET team doesn't ship it for x86). + # Uses a separate directory with a self-contained Dockerfile (multi-stage). + - arch: x86 + docker: scripts/infra/native/linux/docker/glibc-x86 + target: externals-linux-clang-cross + additionalArgs: --verifyGlibcMax=2.27 - arch: arm docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross diff --git a/scripts/infra/native/linux/docker/glibc-x86/.dockerignore b/scripts/infra/native/linux/docker/glibc-x86/.dockerignore new file mode 100644 index 00000000000..45599257251 --- /dev/null +++ b/scripts/infra/native/linux/docker/glibc-x86/.dockerignore @@ -0,0 +1,4 @@ +# Only send Dockerfile and startup.sh to the Docker daemon. +* +!Dockerfile +!startup.sh diff --git a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile new file mode 100644 index 00000000000..af7bb8aa3b0 --- /dev/null +++ b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile @@ -0,0 +1,162 @@ +# glibc cross-compilation image for x86 (i686-linux-gnu). +# +# This is a self-contained Dockerfile that: +# 1. Builds libc++ for x86 (stage: libcxx-builder) +# 2. Layers the shared SkiaSharp build environment on top (stage: final) +# +# The .NET team builds libc++ for amd64/arm64/arm/riscv64/loongarch64 but NOT x86 +# because they don't ship linux-x86 binaries. We need libc++ for x86 because Skia +# requires C++17 and the build uses -stdlib=libc++ which expects libc++ headers at +# c++/v1/. The x86 sysroot only has GCC 6/7 libstdc++ (incomplete C++17 support). +# +# SOURCE for the libc++ build stage: dotnet/dotnet-buildtools-prereqs-docker +# src/azurelinux/3.0/net10.0/cross/amd64/Dockerfile +# https://github.com/dotnet/dotnet-buildtools-prereqs-docker/blob/main/src/azurelinux/3.0/net10.0/cross/amd64/Dockerfile +# +# CHANGES from the amd64 original (only actual diffs listed): +# +# 1. ROOTFS_DIR: /crossrootfs/x64 → /crossrootfs/x86 +# The .NET cross images use /crossrootfs/ as the sysroot mount point. +# The shared Dockerfile expects this path via /etc/skia-env SYSROOT_ROOT. +# +# 2. build-rootfs.sh: x64 bionic → x86 bionic +# build-rootfs.sh is a .NET script that debootstraps a minimal Ubuntu sysroot. +# "x86" selects i386 packages; "bionic" (18.04) matches the distro used by +# the other .NET cross-images (arm64, x64, arm all use bionic sysroots). +# +# 3. TARGET_TRIPLE: x86_64-linux-gnu → i686-linux-gnu +# Controls clang's -target flag for cross-compilation. i686 produces 32-bit +# x86 code. This triple must match the sysroot's lib/gcc directory layout +# (i686-linux-gnu/) so cmake finds the correct GCC headers and CRT objects. +# +# EVERYTHING ELSE is identical to upstream: cmake flags, compiler-rt, libstdc++ +# ABI layer, GCC include path discovery, PIC, lld linker, sysroot settings. +# +# WHEN TO RE-SYNC: +# If the upstream amd64 Dockerfile changes (new LLVM version, new cmake flags), +# re-apply those changes here with the 3 substitutions above (ROOTFS, arch, triple). +# The shared layer below (stage: final) should match ../glibc/Dockerfile exactly. + +# =========================================================================== +# Stage 1: Build libc++ for x86 (mirrors upstream amd64 Dockerfile) +# =========================================================================== + +ARG ROOTFS_DIR=/crossrootfs/x86 + +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-crossdeps-builder-amd64 AS libcxx-builder +ARG ROOTFS_DIR + +RUN PYTHON_EXECUTABLE="$ROOTFS_PIP_HOME/bin/python" /scripts/eng/common/cross/build-rootfs.sh x86 bionic --skipunmount --skipemulation + +RUN TARGET_TRIPLE="i686-linux-gnu" && \ + GCC_VER=$(basename "$(find "$ROOTFS_DIR/usr/include/c++/" -mindepth 1 -maxdepth 1 -type d | sort -V | head -n1)") && \ + CPP_INCLUDES="$ROOTFS_DIR/usr/include/c++/$GCC_VER" && \ + TRIPLET_INCLUDES=$([ -e "$CPP_INCLUDES/$TARGET_TRIPLE" ] && echo "$CPP_INCLUDES/$TARGET_TRIPLE" || echo "$(realpath "$CPP_INCLUDES/../../$TARGET_TRIPLE");$(realpath "$CPP_INCLUDES/../../$TARGET_TRIPLE/c++/$GCC_VER")") && \ + CLANG_MAJOR_VERSION=$(clang --version | grep -oP "(?<=version )\d+") && \ + cmake -S llvm-project.src/runtimes -B runtimes \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_ASM_COMPILER=clang \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_ASM_COMPILER_TARGET="$TARGET_TRIPLE" \ + -DCMAKE_C_COMPILER_TARGET="$TARGET_TRIPLE" \ + -DCMAKE_CXX_COMPILER_TARGET="$TARGET_TRIPLE" \ + # We're going to link the static libraries we build here into PIC images, so build the static libraries as PIC. + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_SYSROOT="$ROOTFS_DIR" \ + # Specify linker to use for exes directly for CMake toolchain detection + -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \ + # Don't search for tools in the sysroot as we're cross-compiling + -DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM="NEVER" \ + -DLLVM_USE_LINKER=lld \ + -DLLVM_ENABLE_RUNTIMES="libcxx;compiler-rt" \ + -DLIBCXX_ENABLE_SHARED=OFF \ + -DLIBCXX_CXX_ABI=libstdc++ \ + -DLIBCXX_CXX_ABI_INCLUDE_PATHS="$CPP_INCLUDES;$TRIPLET_INCLUDES" \ + -DCOMPILER_RT_CXX_LIBRARY="libcxx" \ + -DCOMPILER_RT_STATIC_CXX_LIBRARY=ON \ + -DCOMPILER_RT_BUILD_SANITIZERS=OFF \ + -DCOMPILER_RT_BUILD_MEMPROF=OFF \ + # The libfuzzer build in LLVM doesn't correctly forward the required CMake properties to the "fuzzed libc++" build + # so skip it here. + -DCOMPILER_RT_BUILD_LIBFUZZER=OFF \ + -DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON \ + -DCOMPILER_RT_INSTALL_PATH="/usr/local/lib/clang/$CLANG_MAJOR_VERSION" && \ + cmake --build runtimes -j $(getconf _NPROCESSORS_ONLN) && \ + cmake --install runtimes --prefix "$ROOTFS_DIR/usr" + +# =========================================================================== +# Stage 2: Final image with libc++ + SkiaSharp build environment +# This section mirrors ../glibc/Dockerfile exactly (hardcoded for x86). +# =========================================================================== + +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-crossdeps-llvm-amd64 +ARG ROOTFS_DIR + +COPY --from=libcxx-builder /usr/local/lib/clang /usr/local/lib/clang/ +COPY --from=libcxx-builder $ROOTFS_DIR $ROOTFS_DIR + +SHELL ["/bin/bash", "-euo", "pipefail", "-c"] + +# Toolchain variables for x86 (hardcoded — this Dockerfile is x86-only). +RUN echo "export TOOLCHAIN_ARCH=i386-linux-gnu" > /etc/skia-env \ + && echo "export TOOLCHAIN_ARCH_SHORT=i386" >> /etc/skia-env \ + && echo "export TOOLCHAIN_ARCH_TARGET=i686-linux-gnu" >> /etc/skia-env \ + && echo "export SYSROOT_PATH=/crossrootfs/x86/usr" >> /etc/skia-env \ + && echo "export SYSROOT_ROOT=/crossrootfs/x86" >> /etc/skia-env \ + && GCC_DIR=$(find /crossrootfs/x86/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ + && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env \ + # Verify libc++ headers exist — deterministic build requirement. + && if [ ! -d "/crossrootfs/x86/usr/include/c++/v1" ]; then \ + echo "FATAL: libc++ headers not found at /crossrootfs/x86/usr/include/c++/v1" ; \ + exit 1 ; \ + fi + +# Install the .NET SDK (needed for Cake build scripts). +ARG DOTNET_SDK_VERSION=10.0.108 +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh -o dotnet-install.sh \ + && bash dotnet-install.sh --version ${DOTNET_SDK_VERSION} --install-dir /usr/share/dotnet --verbose \ + && rm dotnet-install.sh \ + && dotnet --info + +ENV CC=clang CXX=clang++ + +# zstd is needed to extract newer .deb packages (Ubuntu 22.04+ uses zstd compression). +RUN tdnf install -y zstd && tdnf clean all + +# Install fontconfig headers into the sysroot (x86/i386). +RUN . /etc/skia-env \ + && FC_REPO=http://archive.ubuntu.com/ubuntu \ + && FC_VERSION=2.12.6-0ubuntu2 \ + && FC_DEV_PKG=libfontconfig1-dev \ + && FC_DEV_SHA256=cd775534e5377dbb26a789781bfd06b93ca63fe366eec9304b7339b54ba55905 \ + && FC_LIB_SHA256=f4324b87b99f1750241d9b61d2a0658ccc76ff8d85e539d4f7f4a07723941119 \ + && mkdir -p /tmp/fontconfig && cd /tmp/fontconfig \ + && for pkg_info in \ + "${FC_DEV_PKG} ${FC_DEV_SHA256}" \ + "libfontconfig1 ${FC_LIB_SHA256}"; do \ + pkg=$(echo $pkg_info | cut -d' ' -f1) ; \ + expected_sha=$(echo $pkg_info | cut -d' ' -f2) ; \ + url="${FC_REPO}/pool/main/f/fontconfig/${pkg}_${FC_VERSION}_${TOOLCHAIN_ARCH_SHORT}.deb" ; \ + echo "Downloading $url" ; \ + curl -fSL -o "${pkg}.deb" "$url" ; \ + actual_sha=$(sha256sum "${pkg}.deb" | cut -d' ' -f1) ; \ + if [ "$actual_sha" != "$expected_sha" ]; then \ + echo "FATAL: SHA-256 mismatch for ${pkg}.deb" ; \ + echo " Expected: $expected_sha" ; \ + echo " Actual: $actual_sha" ; \ + exit 1 ; \ + fi ; \ + echo " SHA-256 verified: $actual_sha" ; \ + ar x "${pkg}.deb" ; \ + tar xf data.tar.* -C ${SYSROOT_ROOT}/ ; \ + rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ + done \ + && rm -rf /tmp/fontconfig + +WORKDIR /work + +COPY ./startup.sh / +RUN chmod +x /startup.sh +ENTRYPOINT [ "/startup.sh" ] diff --git a/scripts/infra/native/linux/docker/glibc-x86/startup.sh b/scripts/infra/native/linux/docker/glibc-x86/startup.sh new file mode 100755 index 00000000000..512bb431bf7 --- /dev/null +++ b/scripts/infra/native/linux/docker/glibc-x86/startup.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -e + +source /etc/skia-env + +exec "$@" diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 6514bbd395c..8cb8cdfe7c3 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -11,7 +11,11 @@ # Usage: # docker build --build-arg BUILD_ARCH=arm64 . # -# Supported architectures: arm, arm64, x86, x64, riscv64, loongarch64 +# NOTE: x86 uses a separate directory (glibc-x86/) with its own Dockerfile +# because the .NET team doesn't ship libc++ for x86. +# +# Supported architectures: arm, arm64, x64, riscv64, loongarch64 +# (x86 uses glibc-x86/Dockerfile instead) ARG BUILD_ARCH=arm64 @@ -20,10 +24,6 @@ ARG BUILD_ARCH=arm64 # All other architectures: IMAGE_ARCH defaults to BUILD_ARCH. ARG IMAGE_ARCH=${BUILD_ARCH} -# The x86 image lacks libc++ headers (only GCC 6/7 which don't support C++20). -# Pull libc++ headers from the x64 image — they're architecture-independent. -FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64 AS libcxx-headers - FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-${IMAGE_ARCH} SHELL ["/bin/bash", "-euo", "pipefail", "-c"] @@ -41,10 +41,6 @@ RUN case "${BUILD_ARCH}" in \ TOOLCHAIN_ARCH_SHORT=arm64; \ TOOLCHAIN_ARCH_TARGET=aarch64-linux-gnu; \ ROOTFS_DIR=/crossrootfs/arm64 ;; \ - x86) TOOLCHAIN_ARCH=i386-linux-gnu; \ - TOOLCHAIN_ARCH_SHORT=i386; \ - TOOLCHAIN_ARCH_TARGET=i686-linux-gnu; \ - ROOTFS_DIR=/crossrootfs/x86 ;; \ x64) TOOLCHAIN_ARCH=x86_64-linux-gnu; \ TOOLCHAIN_ARCH_SHORT=amd64; \ TOOLCHAIN_ARCH_TARGET=x86_64-linux-gnu; \ @@ -57,6 +53,7 @@ RUN case "${BUILD_ARCH}" in \ TOOLCHAIN_ARCH_SHORT=loong64; \ TOOLCHAIN_ARCH_TARGET=loongarch64-linux-gnu; \ ROOTFS_DIR=/crossrootfs/loongarch64 ;; \ + x86) echo "FATAL: x86 must use glibc-x86/Dockerfile (.NET doesn't ship libc++ for x86)" && exit 1 ;; \ *) echo "FATAL: Unsupported BUILD_ARCH=${BUILD_ARCH}" && exit 1 ;; \ esac \ && echo "export TOOLCHAIN_ARCH=${TOOLCHAIN_ARCH}" > /etc/skia-env \ @@ -66,60 +63,11 @@ RUN case "${BUILD_ARCH}" in \ && echo "export SYSROOT_ROOT=${ROOTFS_DIR}" >> /etc/skia-env \ && GCC_DIR=$(find ${ROOTFS_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env \ - # Create c++/current symlink for sysroots that use libstdc++ headers (no libc++/v1). - # The build.cake include paths reference c++/current; this ensures they resolve. + # Verify libc++ headers exist — deterministic build requirement. && if [ ! -d "${ROOTFS_DIR}/usr/include/c++/v1" ]; then \ - CXX_VER=$(ls -1 ${ROOTFS_DIR}/usr/include/c++/ | grep -E '^[0-9]+$' | sort -n | tail -1) \ - && ln -sf "${CXX_VER}" "${ROOTFS_DIR}/usr/include/c++/current" \ - && echo "Created c++/current -> ${CXX_VER} (no libc++ headers in sysroot)" ; \ - fi - -# For sysroots without libc++ (e.g., x86), copy the architecture-independent -# libc++ headers from the x64 image and build libc++/libc++abi from LLVM source. -# The x86 sysroot has GCC 6/7 headers (no C++20 support) and no libc++.a. -ARG BUILD_ARCH -COPY --from=libcxx-headers /crossrootfs/x64/usr/include/c++/v1 /tmp/libcxx-headers/ -RUN . /etc/skia-env \ - && if [ ! -d "${SYSROOT_ROOT}/usr/include/c++/v1" ]; then \ - cp -a /tmp/libcxx-headers "${SYSROOT_ROOT}/usr/include/c++/v1" \ - && echo "Installed libc++ headers into ${SYSROOT_ROOT}/usr/include/c++/v1" ; \ - fi \ - && rm -rf /tmp/libcxx-headers - -# Build libc++ and libc++abi from source for arches that lack them (x86). -# Must use -fPIC (CMAKE_POSITION_INDEPENDENT_CODE) since the .a is linked into .so. -ARG LLVM_VERSION=20.1.8 -RUN . /etc/skia-env \ - && if [ ! -f "${SYSROOT_ROOT}/usr/lib/libc++.a" ]; then \ - echo "Building libc++ ${LLVM_VERSION} for ${TOOLCHAIN_ARCH_TARGET}..." \ - && git clone --depth=1 --branch llvmorg-${LLVM_VERSION} \ - --filter=blob:none --sparse \ - https://github.com/llvm/llvm-project.git /tmp/llvm \ - && cd /tmp/llvm \ - && git sparse-checkout set runtimes libcxx libcxxabi libc cmake llvm/cmake llvm/utils/llvm-lit \ - && cmake -G Ninja -S runtimes -B /tmp/llvm-build \ - -DLLVM_COMMON_CMAKE_UTILS=/tmp/llvm/cmake \ - -DCMAKE_C_COMPILER=clang \ - -DCMAKE_CXX_COMPILER=clang++ \ - -DCMAKE_C_COMPILER_TARGET=${TOOLCHAIN_ARCH_TARGET} \ - -DCMAKE_CXX_COMPILER_TARGET=${TOOLCHAIN_ARCH_TARGET} \ - -DCMAKE_SYSROOT=${SYSROOT_ROOT} \ - -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ - -DCMAKE_C_COMPILER_WORKS=ON \ - -DCMAKE_CXX_COMPILER_WORKS=ON \ - -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \ - -DLIBCXX_ENABLE_SHARED=OFF \ - -DLIBCXXABI_ENABLE_SHARED=OFF \ - -DLIBCXX_CXX_ABI=libcxxabi \ - -DLIBCXXABI_USE_LLVM_UNWINDER=OFF \ - -DLIBCXX_INCLUDE_BENCHMARKS=OFF \ - -DLIBCXX_INCLUDE_TESTS=OFF \ - -DLIBCXXABI_INCLUDE_TESTS=OFF \ - && ninja -C /tmp/llvm-build cxx cxxabi \ - && cp /tmp/llvm-build/lib/libc++.a ${SYSROOT_ROOT}/usr/lib/ \ - && cp /tmp/llvm-build/lib/libc++abi.a ${SYSROOT_ROOT}/usr/lib/ \ - && rm -rf /tmp/llvm /tmp/llvm-build \ - && echo "Installed libc++.a and libc++abi.a for ${TOOLCHAIN_ARCH_TARGET}" ; \ + echo "FATAL: libc++ headers not found at ${ROOTFS_DIR}/usr/include/c++/v1" ; \ + echo " The .NET cross-image is expected to provide libc++." ; \ + exit 1 ; \ fi # Install the .NET SDK (needed for Cake build scripts). @@ -150,11 +98,6 @@ RUN . /etc/skia-env \ FC_DEV_PKG=libfontconfig1-dev; \ FC_DEV_SHA256=93fc9ec2f69a87cbbc2efe7e5f3769c56ea9453a1449f28d78f192062b532975; \ FC_LIB_SHA256=de5942257801f0d344cc55c95cc190aae4e434b3d8d0d77a029f210da755fdc3 ;; \ - x86) FC_REPO=http://archive.ubuntu.com/ubuntu; \ - FC_VERSION=2.12.6-0ubuntu2; \ - FC_DEV_PKG=libfontconfig1-dev; \ - FC_DEV_SHA256=cd775534e5377dbb26a789781bfd06b93ca63fe366eec9304b7339b54ba55905; \ - FC_LIB_SHA256=f4324b87b99f1750241d9b61d2a0658ccc76ff8d85e539d4f7f4a07723941119 ;; \ x64) FC_REPO=http://archive.ubuntu.com/ubuntu; \ FC_VERSION=2.12.6-0ubuntu2; \ FC_DEV_PKG=libfontconfig1-dev; \ diff --git a/scripts/infra/native/linux/docker/glibc/build-local.sh b/scripts/infra/native/linux/docker/glibc/build-local.sh index 0aa8d162bdb..b641bf86f5f 100755 --- a/scripts/infra/native/linux/docker/glibc/build-local.sh +++ b/scripts/infra/native/linux/docker/glibc/build-local.sh @@ -13,19 +13,29 @@ EXTRA_ARGS="$@" # Validate architecture and map to .NET image tag arch case "$ARCH" in - arm|arm64|x86|riscv64|loongarch64) + arm|arm64|riscv64|loongarch64) IMAGE_ARCH="$ARCH" ;; x64) IMAGE_ARCH="amd64" ;; + x86) + IMAGE_ARCH="" ;; # x86 uses a self-contained Dockerfile (glibc-x86/) *) echo "Unsupported architecture: $ARCH"; exit 1 ;; esac -# Build the glibc image — the Dockerfile handles all arch-specific config internally. -(cd "$DIR" && +# x86 has its own self-contained Dockerfile (builds libc++ in stage 1) since +# the .NET team doesn't ship libc++ for x86. All other arches share one Dockerfile. +if [ "$ARCH" = "x86" ]; then + DOCKER_DIR="$DIR/../glibc-x86" + BUILD_ARGS="" +else + DOCKER_DIR="$DIR" + BUILD_ARGS="--build-arg BUILD_ARCH=$ARCH --build-arg IMAGE_ARCH=$IMAGE_ARCH" +fi + +(cd "$DOCKER_DIR" && docker build --tag skiasharp-linux-gnu-cross-$ARCH \ --platform=linux/amd64 \ - --build-arg BUILD_ARCH=$ARCH \ - --build-arg IMAGE_ARCH=$IMAGE_ARCH \ + $BUILD_ARGS \ .) (cd "$DIR/../../../../../.." && From 8918b51c4d4421f750e091b9ba64d963bc2a689f Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 26 May 2026 18:14:15 +0200 Subject: [PATCH 37/45] Address review feedback: fail-fast validation for SYSROOT/GCC/NDK paths - bionic/Dockerfile: wrap NDK glob with || true so the friendly FATAL check is reachable when no NDK is installed (was masked by set -e + pipefail) - glibc/Dockerfile, glibc-x86/Dockerfile: validate GCC_LIB_DIR discovery so missing crtbeginS.o fails with a clear error instead of producing bad GN paths downstream - build.cake: validate SYSROOT_PATH/SYSROOT_ROOT/GCC_LIB_DIR are set before generating GN args - building-linux.md: switch x64 example to arm64 (avoids IMAGE_ARCH mapping) and document the x64 caveat - debugging-methodology.md: point at all 4 Dockerfiles, not just glibc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- documentation/dev/building-linux.md | 8 +++++--- documentation/dev/debugging-methodology.md | 2 +- native/linux-clang-cross/build.cake | 7 +++++++ scripts/infra/native/linux/docker/bionic/Dockerfile | 2 +- scripts/infra/native/linux/docker/glibc-x86/Dockerfile | 3 +++ scripts/infra/native/linux/docker/glibc/Dockerfile | 3 +++ 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/documentation/dev/building-linux.md b/documentation/dev/building-linux.md index c48b9511a70..d807c2d6376 100644 --- a/documentation/dev/building-linux.md +++ b/documentation/dev/building-linux.md @@ -81,16 +81,18 @@ To customize the build, modify the `--args` value: Building for different Linux distributions is easier with Docker. The repository includes Dockerfiles for various configurations. ```bash -# Build the Docker image +# Build the Docker image (arm64 doesn't need IMAGE_ARCH mapping) cd scripts/infra/native/linux/docker/alpine -docker build --tag skiasharp-alpine --build-arg BUILD_ARCH=x64 . +docker build --tag skiasharp-alpine --build-arg BUILD_ARCH=arm64 . # Run the build docker run --rm --name skiasharp-alpine --volume $(pwd):/work skiasharp-alpine \ - /bin/bash -c "dotnet tool restore && dotnet cake --target=externals-linux-clang-cross --buildarch=x64 --variant=alpine" + /bin/bash -c "dotnet tool restore && dotnet cake --target=externals-linux-clang-cross --buildarch=arm64 --variant=alpine" # Fix file ownership if needed chown -R $(id -u):$(id -g) . ``` +> Note: When building for x64, you must also pass `--build-arg IMAGE_ARCH=amd64` because the .NET cross-image tags use "amd64" instead of "x64". + > See the `scripts/infra/native/linux/docker/` directory for available Dockerfiles. \ No newline at end of file diff --git a/documentation/dev/debugging-methodology.md b/documentation/dev/debugging-methodology.md index e082c7d9d5f..b3ee6602866 100644 --- a/documentation/dev/debugging-methodology.md +++ b/documentation/dev/debugging-methodology.md @@ -199,7 +199,7 @@ but the actual `.so.1.2.3` file is in the runtime package (`libfoo1`), not the d | Root Cause | Fix Location | |------------|--------------| | Library missing from linker flags | `native/linux/build.cake` or `externals/skia/third_party/BUILD.gn` | -| Library missing from cross-compile sysroot | `scripts/infra/native/linux/docker/glibc/Dockerfile` | +| Library missing from cross-compile sysroot | `scripts/infra/native/linux/docker/{glibc,glibc-x86,alpine,bionic}/Dockerfile` | | Indirect dependency (A→B→C missing) | Fix B's linkage or add C explicitly | ### Real Example: ARM64 fontconfig issue (#3369) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index 508dd32c3d0..4a8774d29d4 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -9,6 +9,13 @@ string SYSROOT_PATH = Argument("sysrootPath", EnvironmentVariable("SYSROOT_PATH" string SYSROOT_ROOT = Argument("sysrootRoot", EnvironmentVariable("SYSROOT_ROOT") ?? ""); string GCC_LIB_DIR = Argument("gccLibDir", EnvironmentVariable("GCC_LIB_DIR") ?? ""); +if (string.IsNullOrEmpty(SYSROOT_PATH)) + throw new Exception("FATAL: SYSROOT_PATH is not set. Expected from /etc/skia-env in the cross-compile Docker image."); +if (string.IsNullOrEmpty(SYSROOT_ROOT)) + throw new Exception("FATAL: SYSROOT_ROOT is not set. Expected from /etc/skia-env in the cross-compile Docker image."); +if (string.IsNullOrEmpty(GCC_LIB_DIR)) + throw new Exception("FATAL: GCC_LIB_DIR is not set. Expected from /etc/skia-env in the cross-compile Docker image."); + Information("Toolchain:"); Information($" Arch: {TOOLCHAIN_ARCH} ({TOOLCHAIN_ARCH_SHORT})"); Information($" Target {TOOLCHAIN_ARCH_TARGET}"); diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index eff7346a89d..1dc2d028b8d 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -32,7 +32,7 @@ RUN case ${BUILD_ARCH} in \ arm64) ANDROID_TRIPLE=aarch64-linux-android29 ; ANDROID_SYSROOT_LIB=aarch64-linux-android ;; \ *) echo "FATAL: Unsupported BUILD_ARCH=${BUILD_ARCH}" && exit 1 ;; \ esac \ - && NDK_HOME=$(ls -d /usr/local/android-sdk/ndk/*/ | head -1) \ + && NDK_HOME=$(ls -d /usr/local/android-sdk/ndk/*/ 2>/dev/null | head -1 || true) \ && NDK_HOME=${NDK_HOME%/} \ && if [ -z "$NDK_HOME" ] || [ ! -d "$NDK_HOME" ]; then \ echo "FATAL: No Android NDK found in /usr/local/android-sdk/ndk/" && exit 1 ; \ diff --git a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile index af7bb8aa3b0..97d757ee908 100644 --- a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile @@ -105,6 +105,9 @@ RUN echo "export TOOLCHAIN_ARCH=i386-linux-gnu" > /etc/skia-env \ && echo "export SYSROOT_PATH=/crossrootfs/x86/usr" >> /etc/skia-env \ && echo "export SYSROOT_ROOT=/crossrootfs/x86" >> /etc/skia-env \ && GCC_DIR=$(find /crossrootfs/x86/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ + && if [ -z "$GCC_DIR" ] || [ ! -d "$GCC_DIR" ]; then \ + echo "FATAL: GCC lib dir not found (crtbeginS.o missing under /crossrootfs/x86/usr/lib/gcc)" && exit 1 ; \ + fi \ && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env \ # Verify libc++ headers exist — deterministic build requirement. && if [ ! -d "/crossrootfs/x86/usr/include/c++/v1" ]; then \ diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 8cb8cdfe7c3..143d6a2d387 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -62,6 +62,9 @@ RUN case "${BUILD_ARCH}" in \ && echo "export SYSROOT_PATH=${ROOTFS_DIR}/usr" >> /etc/skia-env \ && echo "export SYSROOT_ROOT=${ROOTFS_DIR}" >> /etc/skia-env \ && GCC_DIR=$(find ${ROOTFS_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ + && if [ -z "$GCC_DIR" ] || [ ! -d "$GCC_DIR" ]; then \ + echo "FATAL: GCC lib dir not found (crtbeginS.o missing under ${ROOTFS_DIR}/usr/lib/gcc)" && exit 1 ; \ + fi \ && echo "export GCC_LIB_DIR=${GCC_DIR}" >> /etc/skia-env \ # Verify libc++ headers exist — deterministic build requirement. && if [ ! -d "${ROOTFS_DIR}/usr/include/c++/v1" ]; then \ From 1c8ac0465b25fbfd0b38ba690d17fabe92145d9e Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 26 May 2026 18:29:51 +0200 Subject: [PATCH 38/45] Auto-detect arch image-tag inside Dockerfile (no IMAGE_ARCH needed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use Docker multi-FROM stage aliases to handle the x64→amd64 image-tag mapping internally: FROM mcr.../cross-arm AS base-arm FROM mcr.../cross-arm64 AS base-arm64 FROM mcr.../cross-amd64 AS base-x64 ... FROM base-${BUILD_ARCH} BuildKit's DAG only pulls the stage actually referenced, so this has no performance cost over the previous ARG-based approach. Removes the need for callers (build-local.sh, CI YAML, docs) to know about the x64↔amd64 mapping at all — BUILD_ARCH is the only input. Also reverts an unintentional skia submodule bump that crept into the previous "Remove fallback logic" commit via git add -A. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- documentation/dev/building-linux.md | 8 +++----- externals/skia | 2 +- .../azure-templates-stages-native-linux.yml | 10 +--------- .../native/linux/docker/alpine/Dockerfile | 19 +++++++++++++------ .../native/linux/docker/alpine/build-local.sh | 8 ++------ .../native/linux/docker/glibc/Dockerfile | 18 ++++++++++++------ .../native/linux/docker/glibc/build-local.sh | 13 +++++-------- 7 files changed, 37 insertions(+), 41 deletions(-) diff --git a/documentation/dev/building-linux.md b/documentation/dev/building-linux.md index d807c2d6376..c48b9511a70 100644 --- a/documentation/dev/building-linux.md +++ b/documentation/dev/building-linux.md @@ -81,18 +81,16 @@ To customize the build, modify the `--args` value: Building for different Linux distributions is easier with Docker. The repository includes Dockerfiles for various configurations. ```bash -# Build the Docker image (arm64 doesn't need IMAGE_ARCH mapping) +# Build the Docker image cd scripts/infra/native/linux/docker/alpine -docker build --tag skiasharp-alpine --build-arg BUILD_ARCH=arm64 . +docker build --tag skiasharp-alpine --build-arg BUILD_ARCH=x64 . # Run the build docker run --rm --name skiasharp-alpine --volume $(pwd):/work skiasharp-alpine \ - /bin/bash -c "dotnet tool restore && dotnet cake --target=externals-linux-clang-cross --buildarch=arm64 --variant=alpine" + /bin/bash -c "dotnet tool restore && dotnet cake --target=externals-linux-clang-cross --buildarch=x64 --variant=alpine" # Fix file ownership if needed chown -R $(id -u):$(id -g) . ``` -> Note: When building for x64, you must also pass `--build-arg IMAGE_ARCH=amd64` because the .NET cross-image tags use "amd64" instead of "x64". - > See the `scripts/infra/native/linux/docker/` directory for available Dockerfiles. \ No newline at end of file diff --git a/externals/skia b/externals/skia index fd80b8918b2..7a2d809b3ce 160000 --- a/externals/skia +++ b/externals/skia @@ -1 +1 @@ -Subproject commit fd80b8918b2e7f3f714757bd5e805030f62c08af +Subproject commit 7a2d809b3ce116815073877469025dfd6ed42760 diff --git a/scripts/azure-templates-stages-native-linux.yml b/scripts/azure-templates-stages-native-linux.yml index 64583850db1..b67788dcfc8 100644 --- a/scripts/azure-templates-stages-native-linux.yml +++ b/scripts/azure-templates-stages-native-linux.yml @@ -43,11 +43,9 @@ stages: docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross additionalArgs: --verifyGlibcMax=2.35 - # x64 needs IMAGE_ARCH=amd64 (.NET uses "amd64" not "x64" in image tags) - arch: x64 docker: scripts/infra/native/linux/docker/glibc target: externals-linux-clang-cross - dockerArgs: --build-arg IMAGE_ARCH=amd64 additionalArgs: --verifyGlibcMax=2.27 - arch: riscv64 docker: scripts/infra/native/linux/docker/glibc @@ -58,17 +56,11 @@ stages: target: externals-linux-clang-cross additionalArgs: --verifyGlibcMax=2.42 # .NET official cross images (musl/Alpine) - - ${{ each arch in split('arm,arm64,riscv64,loongarch64', ',') }}: + - ${{ each arch in split('arm,arm64,x64,riscv64,loongarch64', ',') }}: - arch: ${{ arch }} variant: alpine docker: scripts/infra/native/linux/docker/alpine target: externals-linux-clang-cross - # x64 musl needs IMAGE_ARCH=amd64 - - arch: x64 - variant: alpine - docker: scripts/infra/native/linux/docker/alpine - target: externals-linux-clang-cross - dockerArgs: --build-arg IMAGE_ARCH=amd64 - template: /scripts/azure-templates-jobs-linux-matrix.yml@self # Build Native Linux (Bionic) parameters: diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 7575db13cfd..200dcc006b0 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -8,18 +8,25 @@ # # We add: .NET SDK (Cake), fontconfig-dev from Alpine repos, skia-env toolchain vars. # -# Usage: +# Usage (BUILD_ARCH is the only input — the x64→amd64 image-tag mapping is +# handled internally via stage aliases below): # docker build --build-arg BUILD_ARCH=arm64 . +# docker build --build-arg BUILD_ARCH=x64 . # # Supported architectures: arm, arm64, x64, riscv64, loongarch64 ARG BUILD_ARCH=arm64 -# .NET cross images use "amd64" where SkiaSharp uses "x64". -# CI must pass IMAGE_ARCH=amd64 when BUILD_ARCH=x64. -# All other architectures: IMAGE_ARCH defaults to BUILD_ARCH. -ARG IMAGE_ARCH=${BUILD_ARCH} -FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-${IMAGE_ARCH}-musl +# One stage per arch — .NET cross images use "amd64" instead of "x64", so we +# map it here in a single place. BuildKit only pulls the stage referenced by +# the final `FROM base-${BUILD_ARCH}`; the others are inert. +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm-musl AS base-arm +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm64-musl AS base-arm64 +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64-musl AS base-x64 +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-riscv64-musl AS base-riscv64 +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-loongarch64-musl AS base-loongarch64 + +FROM base-${BUILD_ARCH} SHELL ["/bin/bash", "-euo", "pipefail", "-c"] diff --git a/scripts/infra/native/linux/docker/alpine/build-local.sh b/scripts/infra/native/linux/docker/alpine/build-local.sh index 397d9983ed6..ea37fb0ac3c 100755 --- a/scripts/infra/native/linux/docker/alpine/build-local.sh +++ b/scripts/infra/native/linux/docker/alpine/build-local.sh @@ -11,12 +11,9 @@ ARCH="${1:-arm64}" shift 1 || true EXTRA_ARGS="$@" -# Validate architecture and map to .NET image tag arch +# Validate architecture (Dockerfile handles x64→amd64 image-tag mapping internally) case "$ARCH" in - arm|arm64|riscv64|loongarch64) - IMAGE_ARCH="$ARCH" ;; - x64) - IMAGE_ARCH="amd64" ;; + arm|arm64|x64|riscv64|loongarch64) ;; *) echo "Unsupported architecture: $ARCH"; exit 1 ;; esac @@ -25,7 +22,6 @@ esac docker build --tag skiasharp-linux-musl-cross-$ARCH \ --platform=linux/amd64 \ --build-arg BUILD_ARCH=$ARCH \ - --build-arg IMAGE_ARCH=$IMAGE_ARCH \ .) (cd "$DIR/../../../../../.." && diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 143d6a2d387..7204eef0f29 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -8,8 +8,10 @@ # # We add: .NET SDK (Cake), fontconfig-dev headers, skia-env toolchain vars. # -# Usage: +# Usage (BUILD_ARCH is the only input — the x64→amd64 image-tag mapping is +# handled internally via stage aliases below): # docker build --build-arg BUILD_ARCH=arm64 . +# docker build --build-arg BUILD_ARCH=x64 . # # NOTE: x86 uses a separate directory (glibc-x86/) with its own Dockerfile # because the .NET team doesn't ship libc++ for x86. @@ -19,12 +21,16 @@ ARG BUILD_ARCH=arm64 -# .NET cross images use "amd64" where SkiaSharp uses "x64". -# CI must pass IMAGE_ARCH=amd64 when BUILD_ARCH=x64. -# All other architectures: IMAGE_ARCH defaults to BUILD_ARCH. -ARG IMAGE_ARCH=${BUILD_ARCH} +# One stage per arch — .NET cross images use "amd64" instead of "x64", so we +# map it here in a single place. BuildKit only pulls the stage referenced by +# the final `FROM base-${BUILD_ARCH}`; the others are inert. +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm AS base-arm +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-arm64 AS base-arm64 +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-amd64 AS base-x64 +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-riscv64 AS base-riscv64 +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-loongarch64 AS base-loongarch64 -FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-${IMAGE_ARCH} +FROM base-${BUILD_ARCH} SHELL ["/bin/bash", "-euo", "pipefail", "-c"] diff --git a/scripts/infra/native/linux/docker/glibc/build-local.sh b/scripts/infra/native/linux/docker/glibc/build-local.sh index b641bf86f5f..43509f1d87a 100755 --- a/scripts/infra/native/linux/docker/glibc/build-local.sh +++ b/scripts/infra/native/linux/docker/glibc/build-local.sh @@ -11,14 +11,11 @@ ARCH="${1:-arm64}" shift 1 || true EXTRA_ARGS="$@" -# Validate architecture and map to .NET image tag arch +# Validate architecture (Dockerfile handles x64→amd64 image-tag mapping internally). +# x86 uses a separate self-contained Dockerfile (glibc-x86/) — see below. case "$ARCH" in - arm|arm64|riscv64|loongarch64) - IMAGE_ARCH="$ARCH" ;; - x64) - IMAGE_ARCH="amd64" ;; - x86) - IMAGE_ARCH="" ;; # x86 uses a self-contained Dockerfile (glibc-x86/) + arm|arm64|x64|riscv64|loongarch64) ;; + x86) ;; *) echo "Unsupported architecture: $ARCH"; exit 1 ;; esac @@ -29,7 +26,7 @@ if [ "$ARCH" = "x86" ]; then BUILD_ARGS="" else DOCKER_DIR="$DIR" - BUILD_ARGS="--build-arg BUILD_ARCH=$ARCH --build-arg IMAGE_ARCH=$IMAGE_ARCH" + BUILD_ARGS="--build-arg BUILD_ARCH=$ARCH" fi (cd "$DOCKER_DIR" && From 093913be9fc00180d4a96d8e6eb0910337d43962 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 26 May 2026 19:52:28 +0200 Subject: [PATCH 39/45] =?UTF-8?q?Fix=20arm/riscv64=20fontconfig:=20libfont?= =?UTF-8?q?config1-dev=20=E2=86=92=20libfontconfig-dev?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .deb `libfontconfig1-dev__.deb` on Ubuntu 22.04+ (jammy, noble) is a transitional empty package containing only copyright/changelog — no headers. The actual headers moved to `libfontconfig-dev` (without the "1"). This affected arm (jammy/22.04) and riscv64 (noble/24.04). The Dockerfile silently succeeded at install but the compile failed later with: fatal error: 'fontconfig/fontconfig.h' file not found arm64/x64/x86 use the older bionic (18.04) packages where `libfontconfig1-dev` still has the headers — those continue to work. Also add a post-extract sanity check that fails fast if `/usr/include/fontconfig/fontconfig.h` is missing, so any future package-rename breakage is caught at image build time, not compile time. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native/linux/docker/glibc-x86/Dockerfile | 5 +++++ .../infra/native/linux/docker/glibc/Dockerfile | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile index 97d757ee908..d7dc3fa8dd2 100644 --- a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile @@ -156,6 +156,11 @@ RUN . /etc/skia-env \ tar xf data.tar.* -C ${SYSROOT_ROOT}/ ; \ rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ done \ + && if [ ! -f "${SYSROOT_ROOT}/usr/include/fontconfig/fontconfig.h" ]; then \ + echo "FATAL: fontconfig.h not found at ${SYSROOT_ROOT}/usr/include/fontconfig/fontconfig.h" ; \ + echo " The ${FC_DEV_PKG} package may be a transitional/empty shell." ; \ + exit 1 ; \ + fi \ && rm -rf /tmp/fontconfig WORKDIR /work diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index 7204eef0f29..ea6e517612b 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -99,8 +99,8 @@ RUN . /etc/skia-env \ && case "${BUILD_ARCH}" in \ arm) FC_REPO=http://ports.ubuntu.com/ubuntu-ports; \ FC_VERSION=2.13.1-4.2ubuntu5; \ - FC_DEV_PKG=libfontconfig1-dev; \ - FC_DEV_SHA256=afb8bd6d46df0faef0f75e0570cd9e52532afbc6cf316be83a80fa5c2d737276; \ + FC_DEV_PKG=libfontconfig-dev; \ + FC_DEV_SHA256=8e4b18145fd9990a1f8c6d05aadb4fa40f0fcab23a81c8ef801532551a341fdb; \ FC_LIB_SHA256=0ac3c1b2b2dcc7cabd11f7e2daa4b3c0f89fd30d97d0722302fc7db623208cef ;; \ arm64) FC_REPO=http://ports.ubuntu.com/ubuntu-ports; \ FC_VERSION=2.12.6-0ubuntu2; \ @@ -114,8 +114,8 @@ RUN . /etc/skia-env \ FC_LIB_SHA256=647bb8f09e751a39d488b0260db6015ce1118c56114cc771dd5bf907180f0c80 ;; \ riscv64) FC_REPO=http://ports.ubuntu.com/ubuntu-ports; \ FC_VERSION=2.15.0-1.1ubuntu2; \ - FC_DEV_PKG=libfontconfig1-dev; \ - FC_DEV_SHA256=31193095ea3678815b34a577d68b00cca7609fbea1cbe5fff04cfbe098be539b; \ + FC_DEV_PKG=libfontconfig-dev; \ + FC_DEV_SHA256=c219de19d80aa7195b1d2dcddc1b5c651eb85b4f616e7d837f2aada3e56ea5fd; \ FC_LIB_SHA256=eb2574c2316922efe83b5a91da5278cf8abfc65051ddce28308032c99fafd36b ;; \ loongarch64) FC_REPO=https://deb.debian.org/debian; \ FC_VERSION=2.17.1-5; \ @@ -144,6 +144,14 @@ RUN . /etc/skia-env \ tar xf data.tar.* -C ${SYSROOT_ROOT}/ ; \ rm -f control.tar.* data.tar.* debian-binary "${pkg}.deb" ; \ done \ + # Sanity check: catches empty/transitional dev packages (e.g., libfontconfig1-dev + # on Ubuntu 22.04+ which is a transitional shell with no headers — the real + # package is libfontconfig-dev without the "1"). + && if [ ! -f "${SYSROOT_ROOT}/usr/include/fontconfig/fontconfig.h" ]; then \ + echo "FATAL: fontconfig.h not found at ${SYSROOT_ROOT}/usr/include/fontconfig/fontconfig.h" ; \ + echo " The ${FC_DEV_PKG} package may be a transitional/empty shell." ; \ + exit 1 ; \ + fi \ && rm -rf /tmp/fontconfig WORKDIR /work From ea461a29db5814af6f5374eebc7ac9ed31049eac Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 26 May 2026 23:26:05 +0200 Subject: [PATCH 40/45] Make GCC_LIB_DIR discovery deterministic (sort -V | tail -1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .NET cross-image sysroots ship multiple GCC versions (verified: x86 and x64 both have GCC 6 and 7 installed). Using `find ... | head -1` picks whichever filesystem returns first — not deterministic across environments or future image bumps. Switch to `sort -V | tail -1` to always pick the highest GCC version, matching Ubuntu bionic's default toolchain (gcc-7). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/infra/native/linux/docker/glibc-x86/Dockerfile | 2 +- scripts/infra/native/linux/docker/glibc/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile index d7dc3fa8dd2..fd4fa824231 100644 --- a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile @@ -104,7 +104,7 @@ RUN echo "export TOOLCHAIN_ARCH=i386-linux-gnu" > /etc/skia-env \ && echo "export TOOLCHAIN_ARCH_TARGET=i686-linux-gnu" >> /etc/skia-env \ && echo "export SYSROOT_PATH=/crossrootfs/x86/usr" >> /etc/skia-env \ && echo "export SYSROOT_ROOT=/crossrootfs/x86" >> /etc/skia-env \ - && GCC_DIR=$(find /crossrootfs/x86/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ + && GCC_DIR=$(find /crossrootfs/x86/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | sort -V | tail -1) \ && if [ -z "$GCC_DIR" ] || [ ! -d "$GCC_DIR" ]; then \ echo "FATAL: GCC lib dir not found (crtbeginS.o missing under /crossrootfs/x86/usr/lib/gcc)" && exit 1 ; \ fi \ diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index ea6e517612b..be075458cce 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -67,7 +67,7 @@ RUN case "${BUILD_ARCH}" in \ && echo "export TOOLCHAIN_ARCH_TARGET=${TOOLCHAIN_ARCH_TARGET}" >> /etc/skia-env \ && echo "export SYSROOT_PATH=${ROOTFS_DIR}/usr" >> /etc/skia-env \ && echo "export SYSROOT_ROOT=${ROOTFS_DIR}" >> /etc/skia-env \ - && GCC_DIR=$(find ${ROOTFS_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | head -1) \ + && GCC_DIR=$(find ${ROOTFS_DIR}/usr/lib/gcc -maxdepth 3 -name "crtbeginS.o" -exec dirname {} \; | sort -V | tail -1) \ && if [ -z "$GCC_DIR" ] || [ ! -d "$GCC_DIR" ]; then \ echo "FATAL: GCC lib dir not found (crtbeginS.o missing under ${ROOTFS_DIR}/usr/lib/gcc)" && exit 1 ; \ fi \ From f1f6aaf7420564637aaebea5e28be9a1c236694d Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 26 May 2026 23:31:00 +0200 Subject: [PATCH 41/45] Rename libcxx stage 'libcxx-builder' -> 'builder' to match upstream verbatim The libcxx-builder stage in glibc-x86/Dockerfile now diffs from the upstream .NET amd64 Dockerfile by exactly 3 substitutions: - ARG ROOTFS_DIR=/crossrootfs/x64 -> x86 - build-rootfs.sh x64 bionic -> x86 bionic - TARGET_TRIPLE="x86_64-linux-gnu" -> i686-linux-gnu The COPY --from=builder block in stage 2 is also byte-for-byte identical to upstream. Renaming back from libcxx-builder makes the upstream sync diff cleaner for future updates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/infra/native/linux/docker/glibc-x86/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile index fd4fa824231..998916b8dd2 100644 --- a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile @@ -1,7 +1,7 @@ # glibc cross-compilation image for x86 (i686-linux-gnu). # # This is a self-contained Dockerfile that: -# 1. Builds libc++ for x86 (stage: libcxx-builder) +# 1. Builds libc++ for x86 (stage: builder) # 2. Layers the shared SkiaSharp build environment on top (stage: final) # # The .NET team builds libc++ for amd64/arm64/arm/riscv64/loongarch64 but NOT x86 @@ -43,7 +43,7 @@ ARG ROOTFS_DIR=/crossrootfs/x86 -FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-crossdeps-builder-amd64 AS libcxx-builder +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-crossdeps-builder-amd64 AS builder ARG ROOTFS_DIR RUN PYTHON_EXECUTABLE="$ROOTFS_PIP_HOME/bin/python" /scripts/eng/common/cross/build-rootfs.sh x86 bionic --skipunmount --skipemulation @@ -93,8 +93,8 @@ RUN TARGET_TRIPLE="i686-linux-gnu" && \ FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-crossdeps-llvm-amd64 ARG ROOTFS_DIR -COPY --from=libcxx-builder /usr/local/lib/clang /usr/local/lib/clang/ -COPY --from=libcxx-builder $ROOTFS_DIR $ROOTFS_DIR +COPY --from=builder /usr/local/lib/clang /usr/local/lib/clang/ +COPY --from=builder $ROOTFS_DIR $ROOTFS_DIR SHELL ["/bin/bash", "-euo", "pipefail", "-c"] From 547675845f00cea2bf67213f4b96e8c579b28a2a Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 27 May 2026 01:23:50 +0200 Subject: [PATCH 42/45] Address deep-review findings from Opus + GPT-5 Codex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opus findings (all verified): - Update .agents/skills/security-audit/references/cg-alerts.md to reflect the new Dockerfile layout — the deleted debian/11 and debian/13 paths are gone, alpine uses ALPINE_VERSION (not DISTRO_VERSION), and the new glibc-x86/ Dockerfile is now listed for CG fixes. - Fix stale sysroot path in documentation/dev/debugging-methodology.md: /usr/aarch64-linux-gnu/lib → ${SYSROOT_ROOT}/usr/lib/aarch64-linux-gnu (matches the new .NET cross-image layout exposed via /etc/skia-env). Codex findings: - bionic/Dockerfile: NDK_HOME=$(ls ... | head -1) was non-deterministic if multiple NDK versions are installed. Switch to find + sort -V | tail -1 so we always pick the highest version, matching the same fix already applied for GCC_LIB_DIR. Not actioned (verified non-issues): - xz install (Opus IMP-3): xz IS pre-installed in the .NET cross base image (verified: xz-5.4.4-3.azl3.x86_64). Don't add explicit dep. - Classic Docker builder pulling all stages (Codex BLOCKING #1): CI is using BuildKit (verified in build log #157963: '#5 [base-x64 1/1]' is BuildKit syntax, classic builder uses 'Step N/N'). Only one base image is pulled per build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/security-audit/references/cg-alerts.md | 13 +++++++------ documentation/dev/debugging-methodology.md | 2 +- scripts/infra/native/linux/docker/bionic/Dockerfile | 3 +-- .../infra/native/linux/docker/glibc-x86/Dockerfile | 1 + scripts/infra/native/linux/docker/glibc/Dockerfile | 1 + 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.agents/skills/security-audit/references/cg-alerts.md b/.agents/skills/security-audit/references/cg-alerts.md index d44582434f4..fbbd20dfcf2 100644 --- a/.agents/skills/security-audit/references/cg-alerts.md +++ b/.agents/skills/security-audit/references/cg-alerts.md @@ -125,8 +125,9 @@ az devops invoke --area build --resource logs \ | Category | Source | Fix Mechanism | |----------|--------|---------------| -| Alpine sysroot packages | `apk add` in alpine Dockerfile | Bump `DISTRO_VERSION` in Dockerfile | -| Debian base image packages | `apt-get` / base image | Update base image or wait for Debian patches | +| Alpine sysroot packages | `apk.static add` in alpine Dockerfile (fontconfig) | Bump `ALPINE_VERSION` in `alpine/Dockerfile` | +| Ubuntu/Debian .deb packages | SHA-pinned fontconfig .debs in glibc Dockerfiles | Bump `FC_VERSION` + new SHA-256 in `glibc/Dockerfile` or `glibc-x86/Dockerfile` | +| .NET cross base image | `mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-cross-*` | Bump the .NET image tag (controlled by .NET infra team) | | npm build tooling | .NET SDK / Cake dependencies | Update .NET SDK or pin versions | | Rust crate deps | .NET SDK internals | Update .NET SDK | | NuGet build deps | Build-time references | Update package version | @@ -135,10 +136,10 @@ az devops invoke --area build --resource logs \ | File | Controls | |------|----------| -| `scripts/infra/native/linux/docker/alpine/Dockerfile` (lines 43–47) | Alpine sysroot version (`DISTRO_VERSION`) | -| `scripts/infra/native/linux/docker/debian/11/Dockerfile` | Debian 11 base image (EOL June 2026) | -| `scripts/infra/native/linux/docker/debian/13/Dockerfile` | Debian 13 base image | -| `scripts/infra/native/linux/docker/bionic/Dockerfile` | Bionic/Android cross-compile | +| `scripts/infra/native/linux/docker/glibc/Dockerfile` | Glibc cross images (arm, arm64, x64, riscv64, loongarch64) — pinned fontconfig .debs + .NET SDK version | +| `scripts/infra/native/linux/docker/glibc-x86/Dockerfile` | x86 self-contained build (libc++ stage + fontconfig) | +| `scripts/infra/native/linux/docker/alpine/Dockerfile` | Musl/Alpine cross images — `ALPINE_VERSION` (look for `ARG ALPINE_VERSION`) controls fontconfig source | +| `scripts/infra/native/linux/docker/bionic/Dockerfile` | Bionic/Android cross-compile (NDK detected dynamically) | | `scripts/infra/native/wasm/docker/Dockerfile` | WASM build container | ## Including CG Data in the Report diff --git a/documentation/dev/debugging-methodology.md b/documentation/dev/debugging-methodology.md index b3ee6602866..bc62faba589 100644 --- a/documentation/dev/debugging-methodology.md +++ b/documentation/dev/debugging-methodology.md @@ -188,7 +188,7 @@ grep "libs = " externals/skia/out/linux/arm64/obj/SkiaSharp.ninja # Check if library exists in cross-compile sysroot docker run --rm skiasharp-linux-gnu-cross-arm64 bash -c \ - "ls -la /usr/aarch64-linux-gnu/lib/libfontconfig*" + "source /etc/skia-env && ls -la \${SYSROOT_ROOT}/usr/lib/aarch64-linux-gnu/libfontconfig*" ``` **Common issue:** The `-dev` package provides a broken symlink (`libfoo.so -> libfoo.so.1.2.3`) diff --git a/scripts/infra/native/linux/docker/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index 1dc2d028b8d..348624dedf1 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -32,8 +32,7 @@ RUN case ${BUILD_ARCH} in \ arm64) ANDROID_TRIPLE=aarch64-linux-android29 ; ANDROID_SYSROOT_LIB=aarch64-linux-android ;; \ *) echo "FATAL: Unsupported BUILD_ARCH=${BUILD_ARCH}" && exit 1 ;; \ esac \ - && NDK_HOME=$(ls -d /usr/local/android-sdk/ndk/*/ 2>/dev/null | head -1 || true) \ - && NDK_HOME=${NDK_HOME%/} \ + && NDK_HOME=$(find /usr/local/android-sdk/ndk -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1 || true) \ && if [ -z "$NDK_HOME" ] || [ ! -d "$NDK_HOME" ]; then \ echo "FATAL: No Android NDK found in /usr/local/android-sdk/ndk/" && exit 1 ; \ fi \ diff --git a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile index 998916b8dd2..1386792071b 100644 --- a/scripts/infra/native/linux/docker/glibc-x86/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile @@ -126,6 +126,7 @@ RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-inst ENV CC=clang CXX=clang++ # zstd is needed to extract newer .deb packages (Ubuntu 22.04+ uses zstd compression). +# (xz is already present in the .NET cross base image for the older .debs.) RUN tdnf install -y zstd && tdnf clean all # Install fontconfig headers into the sysroot (x86/i386). diff --git a/scripts/infra/native/linux/docker/glibc/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile index be075458cce..caeaa2ba490 100644 --- a/scripts/infra/native/linux/docker/glibc/Dockerfile +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -90,6 +90,7 @@ RUN curl -fsSL https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-inst ENV CC=clang CXX=clang++ # zstd is needed to extract newer .deb packages (Ubuntu 22.04+ uses zstd compression). +# (xz is already present in the .NET cross base image for the older .debs.) RUN tdnf install -y zstd && tdnf clean all # Install fontconfig headers into the sysroot. From a065940a72b5ab948477a0eb0dc4bda5a771e286 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 27 May 2026 05:29:26 +0200 Subject: [PATCH 43/45] Fix linux-bionic libc++_shared.so runtime dep regression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validation of CI-built nupkgs revealed that linux-bionic-arm64 and linux-bionic-x64 libSkiaSharp.so / libHarfBuzzSharp.so binaries had `libc++_shared.so` as DT_NEEDED — a runtime dependency we don't bundle. Root cause: This PR replaced `-static-libstdc++` with `-stdlib=libc++` to switch the C++ standard library. For the .NET glibc/musl cross-images this is fine because they ship libc++ as static-only (`-DLIBCXX_ENABLE_SHARED=OFF`), so `-lc++` resolves to `libc++.a` and gets embedded. But the Android NDK ships libc++ as both static and shared; without `-static-libstdc++` clang's driver picks the shared `libc++_shared.so`, leaving consumers needing to provide it themselves. Fix: re-add `-static-libstdc++` to ldflags for the bionic variant only. For glibc/musl this is a no-op (libc++.so doesn't exist in those sysroots); for bionic it forces clang to use libc++_static.a. Verified all other arches in the build: - linux-{arm,arm64,x64,x86,riscv64,loongarch64}: glibc max at or below per-arch verifyGlibcMax target - linux-musl-{arm,arm64,x64,riscv64,loongarch64}: correct musl libc names, zero GLIBC symbols - android-{arm,arm64,x64,x86}: clean, libc++ statically linked (via Skia's GN `ndk=` toolchain handling) - All packages: only expected DT_NEEDED entries (libc, libm, libdl, libpthread, librt, libstdc++, libfontconfig where appropriate) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux/build.cake | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/native/linux/build.cake b/native/linux/build.cake index f290cae76a3..bc55a7ea63d 100644 --- a/native/linux/build.cake +++ b/native/linux/build.cake @@ -91,9 +91,14 @@ Task("libSkiaSharp") // Bionic (Android NDK) builds need SK_BUILD_FOR_UNIX to prevent the // NDK's __ANDROID__ define from suppressing SkDebugf (stdio port). // Fontconfig is not available on Bionic. + // The Android NDK ships libc++ as both static and shared — without + // -static-libstdc++ clang would pick libc++_shared.so as a runtime dep + // that we don't bundle. The .NET glibc/musl cross-images ship libc++ + // static-only, so this flag is a no-op there but doesn't hurt. var isBionic = VARIANT.ToLower().StartsWith("bionic"); var bionicDefine = isBionic ? ", '-DSK_BUILD_FOR_UNIX'" : ""; var bionicArgs = isBionic ? "skia_use_fontconfig=false " : ""; + var staticLibcxx = isBionic ? ", '-static-libstdc++'" : ""; GnNinja($"{VARIANT}/{arch}", "SkiaSharp", $"target_os='linux' " + @@ -113,7 +118,7 @@ Task("libSkiaSharp") bionicArgs + $"extra_asmflags=[] " + $"extra_cflags=[ '-DSKIA_C_DLL', '-DHAVE_SYSCALL_GETRANDOM', '-DXML_DEV_URANDOM', '-stdlib=libc++'{spectreFlags}{wordSizeDefine}{bionicDefine} ] " + - $"extra_ldflags=[ '-stdlib=libc++', '-static-libgcc', '-Wl,--version-script={map}' ] " + + $"extra_ldflags=[ '-stdlib=libc++', '-static-libgcc'{staticLibcxx}, '-Wl,--version-script={map}' ] " + COMPILERS + $"linux_soname_version='{soname}' " + ADDITIONAL_GN_ARGS); @@ -137,6 +142,9 @@ Task("libHarfBuzzSharp") if (Skip(arch)) return; var skiaArch = GetSkiaArch(arch); + var isBionicHB = VARIANT.ToLower().StartsWith("bionic"); + var bionicDefineHB = isBionicHB ? ", '-DSK_BUILD_FOR_UNIX'" : ""; + var staticLibcxxHB = isBionicHB ? ", '-static-libstdc++'" : ""; var soname = GetVersion("HarfBuzz", "soname"); var map = MakeAbsolute((FilePath)"libHarfBuzzSharp/libHarfBuzzSharp.map"); @@ -146,8 +154,8 @@ Task("libHarfBuzzSharp") $"target_cpu='{skiaArch}' " + $"visibility_hidden=false " + $"extra_asmflags=[] " + - $"extra_cflags=[ '-stdlib=libc++'{(VARIANT.ToLower().StartsWith("bionic") ? ", '-DSK_BUILD_FOR_UNIX'" : "")} ] " + - $"extra_ldflags=[ '-stdlib=libc++', '-static-libgcc', '-Wl,--version-script={map}' ] " + + $"extra_cflags=[ '-stdlib=libc++'{bionicDefineHB} ] " + + $"extra_ldflags=[ '-stdlib=libc++', '-static-libgcc'{staticLibcxxHB}, '-Wl,--version-script={map}' ] " + COMPILERS + $"linux_soname_version='{soname}' " + ADDITIONAL_GN_ARGS); From ae71ce530cd528b4409564857bb8f8aee09bf647 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 27 May 2026 12:09:09 +0200 Subject: [PATCH 44/45] Statically link libstdc++.a to avoid new libstdc++.so.6 runtime dep Binary validation of build #4062.61 vs main showed every linux glibc/musl SkiaSharp.so and HarfBuzzSharp.so gained a new `libstdc++.so.6` DT_NEEDED entry compared to main. We do not want to add new runtime dependencies. Root cause: The .NET cross-image libc++.a is built with `-DLIBCXX_CXX_ABI=libstdc++`, so it delegates ABI symbols (operator delete, __cxa_throw, etc.) to libstdc++. The previous `-lstdc++` flag pulled the dynamic libstdc++.so.6 in to satisfy those references. Fix: switch to `-l:libstdc++.a` to pull the static archive instead. libstdc++.a is shipped in every .NET cross-image at GCC_LIB_DIR (we already add `-L$GCC_LIB_DIR` to the link search path), and on modern distros it's built as PIC so it embeds cleanly into our .so. The --whole-archive bracketing is still needed because GN places extra_ldflags BEFORE the object archives, so we must force libstdc++ symbols to be included before downstream references are processed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- native/linux-clang-cross/build.cake | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index 4a8774d29d4..f5aa993e73a 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -54,10 +54,11 @@ string GetGnArgs(string arch) // All cross-images provide libc++.a built with -DLIBCXX_CXX_ABI=libstdc++. // This means libc++ delegates ABI symbols (operator delete, __cxa_throw, etc.) - // to GCC's libstdc++. We link libstdc++ with --whole-archive so its symbols are - // available before GN's object archives reference them (extra_ldflags appear first - // in the link command). -Wl bypasses clang's driver rewrite of -lstdc++ → -lc++. - var abiLib = "'-Wl,--whole-archive,-lstdc++,--no-whole-archive'"; + // to GCC's libstdc++. We link the STATIC libstdc++.a (-l:libstdc++.a) from + // GCC_LIB_DIR so we don't add a runtime libstdc++.so.6 dependency. The + // --whole-archive bracketing forces inclusion before objects reference the + // symbols (extra_ldflags appear first in GN's link command). + var abiLib = "'-Wl,--whole-archive,-l:libstdc++.a,--no-whole-archive'"; return $"extra_asmflags+=[ {init}, '-no-integrated-as', {bin}, {includes} ] " + From d1b2f6fba1868e7f4b3827ba5e23935704c6f59a Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 27 May 2026 23:49:51 +0200 Subject: [PATCH 45/45] Revert "Statically link libstdc++.a to avoid new libstdc++.so.6 runtime dep" This reverts commit ae71ce530cd528b4409564857bb8f8aee09bf647. --- native/linux-clang-cross/build.cake | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/native/linux-clang-cross/build.cake b/native/linux-clang-cross/build.cake index f5aa993e73a..4a8774d29d4 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -54,11 +54,10 @@ string GetGnArgs(string arch) // All cross-images provide libc++.a built with -DLIBCXX_CXX_ABI=libstdc++. // This means libc++ delegates ABI symbols (operator delete, __cxa_throw, etc.) - // to GCC's libstdc++. We link the STATIC libstdc++.a (-l:libstdc++.a) from - // GCC_LIB_DIR so we don't add a runtime libstdc++.so.6 dependency. The - // --whole-archive bracketing forces inclusion before objects reference the - // symbols (extra_ldflags appear first in GN's link command). - var abiLib = "'-Wl,--whole-archive,-l:libstdc++.a,--no-whole-archive'"; + // to GCC's libstdc++. We link libstdc++ with --whole-archive so its symbols are + // available before GN's object archives reference them (extra_ldflags appear first + // in the link command). -Wl bypasses clang's driver rewrite of -lstdc++ → -lc++. + var abiLib = "'-Wl,--whole-archive,-lstdc++,--no-whole-archive'"; return $"extra_asmflags+=[ {init}, '-no-integrated-as', {bin}, {includes} ] " +