Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
396b0f6
Migrate Linux Docker builds to Azure Linux 3.0 + sysroot architecture
mattleibow May 24, 2026
dc57c7b
Remove azurelinux subfolder — flatten Docker directory structure
mattleibow May 24, 2026
db6ceef
Fix Docker build issues found during local testing
mattleibow May 24, 2026
fe7a44f
Fix CI failures: sysroot paths, missing packages, and security improv…
mattleibow May 25, 2026
739ff33
Fix cstddef not found: extract --include debs after debootstrap --for…
mattleibow May 25, 2026
7a23655
Fix include path order and cross-package diagnostics
mattleibow May 25, 2026
9e2e57a
Use official LLVM release instead of Azure Linux's stripped package
mattleibow May 25, 2026
40d2460
Add libtinfo.so.5 compat symlink for LLVM release binary
mattleibow May 25, 2026
1b1300d
Fix lld version check: use ld.lld instead of lld
mattleibow May 25, 2026
3fce9e1
Fix x86 multiarch: symlink i686-linux-gnu to i386-linux-gnu
mattleibow May 25, 2026
7dc70f6
fix: add --sysroot and GCC_LIB_DIR for linker CRT discovery
mattleibow May 25, 2026
6f13e8b
fix: resolve remaining linker errors for alpine and cross-packages
mattleibow May 25, 2026
179cf20
fix: only apply linker script sed for cross-packages (not debootstrap)
mattleibow May 25, 2026
82a9c2d
fix: add lib64 paths and MACHINE_ARCH for local testing
mattleibow May 25, 2026
52999f8
fix: add shared runtime libs and clean up x86 triple handling
mattleibow May 25, 2026
6c9ae4d
fix(riscv64): move to Trixie sysroot to resolve lld 18 incompatibility
mattleibow May 25, 2026
d190814
fix(glibc): handle Trixie debootstrap merged-usr exit code
mattleibow May 25, 2026
7648372
security: harden all Dockerfiles with SHA256 verification and strict …
mattleibow May 25, 2026
3df78a0
chore: bump .NET SDK to 10.0.108 (latest 10.0.1xx)
mattleibow May 25, 2026
371dbb2
refactor: split loongarch64 into dedicated Dockerfile and simplify in…
mattleibow May 25, 2026
9b96c02
add .dockerignore and build-local.sh for all Docker images
mattleibow May 25, 2026
c6b45d0
Fix audit items #2/#3/#5/#6: clean extraction, --sysroot, pinned URLs
mattleibow May 25, 2026
b4c8778
Add SHA-256 verification for fontconfig packages
mattleibow May 25, 2026
b69911d
Use --merged-usr for Trixie debootstrap (official fix)
mattleibow May 25, 2026
b80cd66
Remove debootstrap error tolerance — require clean exit
mattleibow May 25, 2026
9338621
Always pass --merged-usr to debootstrap (no conditional)
mattleibow May 25, 2026
c8391b7
Pivot to .NET official cross-compilation Docker images
mattleibow May 25, 2026
932ad12
Pivot Linux Docker images to .NET official cross-compilation base
mattleibow May 25, 2026
61df9c5
Fix bionic Dockerfile ENV duplication
mattleibow May 25, 2026
e1e6e6f
Remove redundant ANDROID_NDK_HOME from skia-env
mattleibow May 25, 2026
5a4ed3a
Discover NDK dynamically, use full paths instead of PATH
mattleibow May 25, 2026
d32ab6f
Fix alpine sysroot: use SYSROOT_ROOT instead of hardcoded /alpine
mattleibow May 25, 2026
6de0b3b
Merge remote-tracking branch 'origin/main' into mattleibow/animated-c…
mattleibow May 25, 2026
03282bd
Restore verifyGlibcMax per arch to detect sysroot changes
mattleibow May 25, 2026
3208d7a
Fix linker: bypass clang driver rewrite for C++ ABI symbols
mattleibow May 25, 2026
f8f301c
Merge remote-tracking branch 'origin/main' into mattleibow/animated-c…
mattleibow May 26, 2026
eca0aa5
Fix cross-build failures: x86 libc++, arm/riscv64 zstd
mattleibow May 26, 2026
b482f67
Remove fallback logic: deterministic libc++ linking for all arches
mattleibow May 26, 2026
8918b51
Address review feedback: fail-fast validation for SYSROOT/GCC/NDK paths
mattleibow May 26, 2026
1c8ac04
Auto-detect arch image-tag inside Dockerfile (no IMAGE_ARCH needed)
mattleibow May 26, 2026
093913b
Fix arm/riscv64 fontconfig: libfontconfig1-dev → libfontconfig-dev
mattleibow May 26, 2026
ea461a2
Make GCC_LIB_DIR discovery deterministic (sort -V | tail -1)
mattleibow May 26, 2026
f1f6aaf
Rename libcxx stage 'libcxx-builder' -> 'builder' to match upstream v…
mattleibow May 26, 2026
9e60ec4
Merge remote-tracking branch 'origin/main' into mattleibow/animated-c…
mattleibow May 26, 2026
d6cbd3c
Merge remote-tracking branch 'origin/main' into mattleibow/animated-c…
mattleibow May 26, 2026
5476758
Address deep-review findings from Opus + GPT-5 Codex
mattleibow May 26, 2026
a065940
Fix linux-bionic libc++_shared.so runtime dep regression
mattleibow May 27, 2026
ae71ce5
Statically link libstdc++.a to avoid new libstdc++.so.6 runtime dep
mattleibow May 27, 2026
d1b2f6f
Revert "Statically link libstdc++.a to avoid new libstdc++.so.6 runti…
mattleibow May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .agents/skills/issue-fix/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
13 changes: 7 additions & 6 deletions .agents/skills/security-audit/references/cg-alerts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions documentation/dev/building-linux.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) .
Expand Down
4 changes: 2 additions & 2 deletions documentation/dev/debugging-methodology.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand All @@ -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)
Expand Down
51 changes: 40 additions & 11 deletions native/linux-clang-cross/build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
Comment thread
mattleibow marked this conversation as resolved.

if (BUILD_ARCH.Length == 0)
BUILD_ARCH = new [] { "arm" };

string GetGnArgs(string arch)
{
var (sysrootArg, linker) = BUILD_VARIANT switch
var sysrootArg = BUILD_VARIANT switch
{
Comment thread
mattleibow marked this conversation as resolved.
"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 <cmath> does #include_next <math.h>, 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;
}

Expand Down
16 changes: 12 additions & 4 deletions native/linux/build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -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' " +
Expand All @@ -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);
Expand All @@ -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");
Expand All @@ -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);
Expand Down
51 changes: 30 additions & 21 deletions scripts/azure-templates-stages-native-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
mattleibow marked this conversation as resolved.
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
Expand Down
54 changes: 0 additions & 54 deletions scripts/infra/native/linux/docker/_clang-cross-common.sh

This file was deleted.

5 changes: 4 additions & 1 deletion scripts/infra/native/linux/docker/alpine/.dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
build-local.sh
# Only send Dockerfile and startup.sh to the Docker daemon.
*
!Dockerfile
!startup.sh
Loading
Loading