diff --git a/.agents/skills/issue-fix/SKILL.md b/.agents/skills/issue-fix/SKILL.md index 0f8ad233226..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/debian/11/build-local.sh +bash ./scripts/infra/native/linux/docker/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/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/.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/building-linux.md b/documentation/dev/building-linux.md index ec0b3f0ed56..c48b9511a70 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/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..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`) @@ -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/{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 5f614d7145d..4a8774d29d4 100644 --- a/native/linux-clang-cross/build.cake +++ b/native/linux-clang-cross/build.cake @@ -5,35 +5,64 @@ 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")); +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}"); +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'"), - _ => ("", ""), + _ when !string.IsNullOrEmpty(SYSROOT_ROOT) => $"'--sysroot={SYSROOT_ROOT}', ", + _ => "", }; + var linker = "'-fuse-ld=lld'"; - 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/' "; - var includes = - $"'-I{sysroot}/include', " + - $"'-I{sysroot}/include/c++/current', " + - $"'-I{sysroot}/include/c++/current/{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}/' "; + // 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. + // + // 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}' "; + + // 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}, {linker} ] " + + $"extra_ldflags+=[ {init}, {bin}, {libs}, {abiLib}, {linker} ] " + ADDITIONAL_GN_ARGS; } diff --git a/native/linux/build.cake b/native/linux/build.cake index 6fbc3c008a1..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' " + @@ -112,8 +117,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'{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=[ {(VARIANT.ToLower().StartsWith("bionic") ? "'-DSK_BUILD_FOR_UNIX'" : "")} ] " + - $"extra_ldflags=[ '-static-libstdc++', '-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); diff --git a/scripts/azure-templates-stages-native-linux.yml b/scripts/azure-templates-stages-native-linux.yml index ce97932a617..b67788dcfc8 100644 --- a/scripts/azure-templates-stages-native-linux.yml +++ b/scripts/azure-templates-stages-native-linux.yml @@ -27,27 +27,36 @@ stages: additionalArgs: --verifyExcluded=fontconfig gnArgs: skia_use_fontconfig=false matrix: - # Debian 13 / Trixie (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 - 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. - - ${{ each arch in split('arm,arm64,x86,x64,riscv64', ',') }}: - - arch: ${{ arch }} - docker: scripts/infra/native/linux/docker/debian/11 - target: externals-linux-clang-cross - additionalArgs: --verifyGlibcMax=2.31 - - ${{ each arch in split('arm,arm64,x64,riscv64,loongarch64', ',') }}: # removed x86 as it is having issues building + # .NET official cross images (glibc) + # verifyGlibcMax ensures we detect if the base image bumps its sysroot + - 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 + additionalArgs: --verifyGlibcMax=2.35 + - arch: x64 + docker: scripts/infra/native/linux/docker/glibc + target: externals-linux-clang-cross + 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,x64,riscv64,loongarch64', ',') }}: - arch: ${{ arch }} variant: alpine docker: scripts/infra/native/linux/docker/alpine 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 index 3a09fc21d8d..45599257251 100644 --- a/scripts/infra/native/linux/docker/alpine/.dockerignore +++ b/scripts/infra/native/linux/docker/alpine/.dockerignore @@ -1 +1,4 @@ -build-local.sh +# Only send Dockerfile and startup.sh to the Docker daemon. +* +!Dockerfile +!startup.sh diff --git a/scripts/infra/native/linux/docker/alpine/Dockerfile b/scripts/infra/native/linux/docker/alpine/Dockerfile index 7ae10de1000..200dcc006b0 100644 --- a/scripts/infra/native/linux/docker/alpine/Dockerfile +++ b/scripts/infra/native/linux/docker/alpine/Dockerfile @@ -1,74 +1,97 @@ -# 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. +# +# 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, kernel headers +# - Cross-compiled libc++ (static) +# +# We add: .NET SDK (Cake), fontconfig-dev from Alpine repos, skia-env toolchain vars. +# +# 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 -# Use --platform flag when building to select architecture -FROM mcr.microsoft.com/mirror/docker/library/debian:12 - -# Set the architecture-specific variables based on the value of the BUILD_ARCH argument ARG BUILD_ARCH=arm64 -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 ;; \ - *) 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 -# 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/* +# 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 -# Install the cross-compilation musl toolchain -# First, obtain apk.static from https://gitlab.alpinelinux.org/alpine/apk-tools/-/releases/v2.12.14 -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" \ - && chmod +x "$APK_DIR/apk.static" \ - && "$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 \ - add fontconfig-dev build-base linux-headers +FROM base-${BUILD_ARCH} + +SHELL ["/bin/bash", "-euo", "pipefail", "-c"] + +ARG BUILD_ARCH -# Install the .NET SDK -ARG DOTNET_SDK_VERSION=10.0.1xx +# 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 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 \ +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 help \ && dotnet --info -ENV CC=clang-${LLVM_VERSION} CXX=clang++-${LLVM_VERSION} +ENV CC=clang CXX=clang++ + +# Install fontconfig-dev into the sysroot from Alpine repos. +# 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 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/v${APK_STATIC_VERSION}/x86_64/apk.static" \ + && echo "${APK_STATIC_SHA256} $APK_DIR/apk.static" | sha256sum -c - \ + && chmod +x "$APK_DIR/apk.static" \ + && 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 ${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" \ + && 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" WORKDIR /work diff --git a/scripts/infra/native/linux/docker/alpine/build-local.sh b/scripts/infra/native/linux/docker/alpine/build-local.sh index 11fb297eb3f..ea37fb0ac3c 100755 --- a/scripts/infra/native/linux/docker/alpine/build-local.sh +++ b/scripts/infra/native/linux/docker/alpine/build-local.sh @@ -2,10 +2,29 @@ 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 | x64 | riscv64 | loongarch64 ] +# $2+ - Additional arguments to pass to the cake script DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -ARCH="${1:-arm}" +ARCH="${1:-arm64}" +shift 1 || true +EXTRA_ARGS="$@" -$DIR/../_clang-cross-common.sh "$DIR" "$ARCH" "musl" "alpine" +# Validate architecture (Dockerfile handles x64→amd64 image-tag mapping internally) +case "$ARCH" in + arm|arm64|x64|riscv64|loongarch64) ;; + *) echo "Unsupported architecture: $ARCH"; exit 1 ;; +esac + +# 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 BUILD_ARCH=$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/alpine/startup.sh old mode 100644 new mode 100755 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/bionic/Dockerfile b/scripts/infra/native/linux/docker/bionic/Dockerfile index c0ae19777bb..348624dedf1 100644 --- a/scripts/infra/native/linux/docker/bionic/Dockerfile +++ b/scripts/infra/native/linux/docker/bionic/Dockerfile @@ -1,53 +1,63 @@ -# 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. +# +# 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 +# +# We add: .NET SDK (Cake), skia-env toolchain vars, libpthread stub. +# +# Usage: +# docker build --build-arg BUILD_ARCH=x64 . +# +# Supported architectures: x64, arm64 -FROM mcr.microsoft.com/mirror/docker/library/debian:12 +FROM mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-net10.0-android-amd64 + +SHELL ["/bin/bash", "-euo", "pipefail", "-c"] + +# 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 -# Set the architecture-specific variables based on the value of the BUILD_ARCH argument ARG BUILD_ARCH=x64 + +# 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 "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_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 \ + && 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 - -# 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 \ - && 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 - -# 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 + && 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_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 -# Set cross-compiler to NDK clang +# 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 \ - && 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 - -# 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 + && 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 ; \ + fi \ + && printf '!\n' > "$PTHREAD_TARGET" WORKDIR /work diff --git a/scripts/infra/native/linux/docker/bionic/build-local.sh b/scripts/infra/native/linux/docker/bionic/build-local.sh old mode 100644 new mode 100755 index b0c0b6a4ed2..c4fd4f7fe43 --- a/scripts/infra/native/linux/docker/bionic/build-local.sh +++ b/scripts/infra/native/linux/docker/bionic/build-local.sh @@ -8,19 +8,16 @@ 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" -(cd "$DIR" && +# Build the android 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/bionic/startup.sh old mode 100644 new mode 100755 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/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..1386792071b --- /dev/null +++ b/scripts/infra/native/linux/docker/glibc-x86/Dockerfile @@ -0,0 +1,171 @@ +# glibc cross-compilation image for x86 (i686-linux-gnu). +# +# This is a self-contained Dockerfile that: +# 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 +# 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 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=builder /usr/local/lib/clang /usr/local/lib/clang/ +COPY --from=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 {} \; | 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 \ + && 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). +# (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). +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 \ + && 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 + +COPY ./startup.sh / +RUN chmod +x /startup.sh +ENTRYPOINT [ "/startup.sh" ] diff --git a/scripts/infra/native/linux/docker/debian/11/startup.sh b/scripts/infra/native/linux/docker/glibc-x86/startup.sh similarity index 100% rename from scripts/infra/native/linux/docker/debian/11/startup.sh rename to scripts/infra/native/linux/docker/glibc-x86/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/Dockerfile b/scripts/infra/native/linux/docker/glibc/Dockerfile new file mode 100644 index 00000000000..caeaa2ba490 --- /dev/null +++ b/scripts/infra/native/linux/docker/glibc/Dockerfile @@ -0,0 +1,162 @@ +# glibc cross-compilation image. +# +# 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, kernel headers +# - Cross-compiled libc++ (static) and compiler-rt +# +# We add: .NET SDK (Cake), fontconfig-dev headers, skia-env toolchain vars. +# +# 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. +# +# Supported architectures: arm, arm64, x64, riscv64, loongarch64 +# (x86 uses glibc-x86/Dockerfile instead) + +ARG BUILD_ARCH=arm64 + +# 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 base-${BUILD_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 ;; \ + 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 ;; \ + 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 \ + && 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 {} \; | 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 \ + && 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 \ + 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). +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). +# (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. +# 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=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; \ + FC_DEV_PKG=libfontconfig1-dev; \ + FC_DEV_SHA256=93fc9ec2f69a87cbbc2efe7e5f3769c56ea9453a1449f28d78f192062b532975; \ + FC_LIB_SHA256=de5942257801f0d344cc55c95cc190aae4e434b3d8d0d77a029f210da755fdc3 ;; \ + 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=libfontconfig-dev; \ + FC_DEV_SHA256=c219de19d80aa7195b1d2dcddc1b5c651eb85b4f616e7d837f2aada3e56ea5fd; \ + 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 \ + # 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 + +COPY ./startup.sh / +RUN chmod +x /startup.sh +ENTRYPOINT [ "/startup.sh" ] diff --git a/scripts/infra/native/linux/docker/glibc/build-local.sh b/scripts/infra/native/linux/docker/glibc/build-local.sh new file mode 100755 index 00000000000..43509f1d87a --- /dev/null +++ b/scripts/infra/native/linux/docker/glibc/build-local.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -ex + +# Parameters: +# $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 )" + +ARCH="${1:-arm64}" +shift 1 || true +EXTRA_ARGS="$@" + +# 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|x64|riscv64|loongarch64) ;; + x86) ;; + *) echo "Unsupported architecture: $ARCH"; exit 1 ;; +esac + +# 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" +fi + +(cd "$DOCKER_DIR" && + docker build --tag skiasharp-linux-gnu-cross-$ARCH \ + --platform=linux/amd64 \ + $BUILD_ARGS \ + .) + +(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 $EXTRA_ARGS ") diff --git a/scripts/infra/native/linux/docker/debian/13/startup.sh b/scripts/infra/native/linux/docker/glibc/startup.sh old mode 100644 new mode 100755 similarity index 100% rename from scripts/infra/native/linux/docker/debian/13/startup.sh rename to scripts/infra/native/linux/docker/glibc/startup.sh