From aa12a79fbde3311ac27d9e3f71531d37f0b73da2 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 12:47:43 +0100 Subject: [PATCH 01/13] chore(deps): update skia and update patches --- .gitmodules | 2 +- README.md | 2 +- build/Build.LibAlphaSkia.cs | 1 - build/Build.LibSkia.cs | 7 ++----- build/Build.MacOs.cs | 9 +-------- build/TargetOperatingSystem.cs | 1 - 6 files changed, 5 insertions(+), 17 deletions(-) diff --git a/.gitmodules b/.gitmodules index 412c801..79e21ae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,7 +5,7 @@ [submodule "externals/skia"] path = externals/skia url = https://skia.googlesource.com/skia.git - branch = chrome/m135 + branch = chrome/m147 [submodule "externals/node-api-headers"] path = externals/node-api-headers url = https://github.com/nodejs/node-api-headers.git diff --git a/README.md b/README.md index 6041b19..a16b788 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ alphaSkia is a special cross platform [Skia](https://skia.org/) wrapper used in alphaSkia a slightly adapted [Semantic Versioning](https://semver.org/) scheme where the `PATCH` part indicates the Skia milestone version we have integrated. -Currently we are on [Skia m135](https://github.com/CoderLine/alphaSkia/blob/main/.gitmodules#L8) +Currently we are on [Skia m147](https://github.com/CoderLine/alphaSkia/blob/main/.gitmodules#L8) Given a version number `MAJOR.MINOR.SKIA`: diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index e23b010..05df893 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -196,7 +196,6 @@ partial class Build """; PatchSkiaFile(SkiaPath / "BUILD.gn", buildNew, "Build", "#"); PatchSkiaToolchain(); - PatchSkiaMacOsVersion(); PatchLlvm(); }); diff --git a/build/Build.LibSkia.cs b/build/Build.LibSkia.cs index a89b70d..a1b3a70 100644 --- a/build/Build.LibSkia.cs +++ b/build/Build.LibSkia.cs @@ -154,7 +154,7 @@ partial class Build throw new IOException("BUILD.gn of skia changed, cannot patch files"); } - var sourcesEnd = buildFileSource.IndexOf("if (is_fuchsia)", sourcesStart, StringComparison.Ordinal); + var sourcesEnd = buildFileSource.IndexOf("if (skia_enable_spirv_validation)", sourcesStart, StringComparison.Ordinal); if (sourcesEnd == -1) { throw new IOException("BUILD.gn of skia changed, cannot patch files"); @@ -181,6 +181,7 @@ partial class Build newSources += " if (is_android) {\n"; newSources += " deps += [ \"//third_party/expat\" ]\n"; newSources += " sources += skia_ports_fontmgr_android_sources\n"; + newSources += " sources += skia_ports_fontmgr_android_parser_sources\n"; newSources += " }\n"; newSources += " frameworks = []\n"; newSources += " if (is_mac) {\n"; @@ -261,7 +262,6 @@ partial class Build buildFile.WriteAllText(buildFileSource); PatchSkiaToolchain(); - PatchSkiaMacOsVersion(); PatchVulcanAllocatorIncludes(); PatchLlvm(); }); @@ -304,7 +304,6 @@ void BuildSkia() // disable features we don't need gnArgs["skia_use_icu"] = "false"; gnArgs["skia_use_piex"] = "false"; - gnArgs["skia_use_sfntly"] = "false"; gnArgs["skia_use_libgrapheme"] = "true"; gnArgs["skia_enable_skshaper"] = "true"; gnArgs["skia_enable_skparagraph"] = "true"; @@ -322,8 +321,6 @@ void BuildSkia() gnArgs["skia_use_libjxl_decode"] = "false"; gnArgs["skia_enable_vello_shaders"] = "false"; - gnArgs["skia_enable_sksl"] = "false"; - gnArgs["skia_use_system_expat"] = "false"; gnArgs["skia_use_system_libjpeg_turbo"] = "false"; gnArgs["skia_use_system_libpng"] = "false"; diff --git a/build/Build.MacOs.cs b/build/Build.MacOs.cs index 7333507..26aa631 100644 --- a/build/Build.MacOs.cs +++ b/build/Build.MacOs.cs @@ -9,12 +9,5 @@ void SetClangMacOs(Dictionary gnArgs) AppendToFlagList(gnArgs, "extra_ldflags", "'-stdlib=libc++'"); } - void PatchSkiaMacOsVersion() - { - // Skia has hard-coded x86_64-apple-macos10.13 as target, we want it slightly newer as x86_64-apple-macos10.15 - // to use some more C++ types - var buildFile = SkiaPath / "gn" / "skia" / "BUILD.gn"; - var source = buildFile.ReadAllText(); - buildFile.WriteAllText(source.Replace("x86_64-apple-macos10.13", "x86_64-apple-macos10.15")); - } + } \ No newline at end of file diff --git a/build/TargetOperatingSystem.cs b/build/TargetOperatingSystem.cs index c51df58..bb9d8e3 100644 --- a/build/TargetOperatingSystem.cs +++ b/build/TargetOperatingSystem.cs @@ -50,7 +50,6 @@ public class TargetOperatingSystem : Enumeration ["skia_use_system_freetype2"] = "false", ["skia_enable_fontmgr_android"] = "false", ["skia_enable_fontmgr_empty"] = "false", - ["skia_enable_fontmgr_fuchsia"] = "false", ["skia_enable_fontmgr_FontConfigInterface"] = "false", ["skia_enable_fontmgr_fontconfig"] = "false", ["skia_enable_fontmgr_custom_directory"] = "false", From ed6a6c4e2d62524e115d3f9c844864b79907e625 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 12:52:49 +0100 Subject: [PATCH 02/13] build: use latest NDK --- .github/workflows/~native.yml | 18 ++++++++++ build/Build.Android.cs | 68 +++++++++++++++++++++-------------- 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/.github/workflows/~native.yml b/.github/workflows/~native.yml index 158bdf0..059d593 100644 --- a/.github/workflows/~native.yml +++ b/.github/workflows/~native.yml @@ -63,6 +63,15 @@ jobs: key: libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static-${{ github.run_id }} restore-keys: libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static + - name: Install Latest Android NDK + if: ${{ inputs.target-os == 'android' }} + run: | + LATEST_NDK=$(sdkmanager --list 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) + echo "Installing NDK: $LATEST_NDK" + sdkmanager "$LATEST_NDK" + NDK_VERSION="${LATEST_NDK#ndk;}" + echo "ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/$NDK_VERSION" >> "$GITHUB_ENV" + - name: Compiling LibSkia run: dotnet build/bin/Debug/publish/_build.dll LibSkiaWithCache --target-os ${{ inputs.target-os }} --architecture ${{ matrix.architecture }} --use-cache ${{ inputs.use-skia-cache }} shell: bash @@ -111,6 +120,15 @@ jobs: path: dist/.organize/libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static name: libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static + - name: Install Latest Android NDK + if: ${{ inputs.target-os == 'android' }} + run: | + LATEST_NDK=$(sdkmanager --list 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) + echo "Installing NDK: $LATEST_NDK" + sdkmanager "$LATEST_NDK" + NDK_VERSION="${LATEST_NDK#ndk;}" + echo "ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/$NDK_VERSION" >> "$GITHUB_ENV" + - name: Compiling LibAlphaSkia run: dotnet build/bin/Debug/publish/_build.dll LibAlphaSkia LibAlphaSkiaTest --target-os ${{ inputs.target-os }} --architecture ${{ matrix.architecture }} --variant ${{ matrix.variant }} shell: bash diff --git a/build/Build.Android.cs b/build/Build.Android.cs index e7d99fe..3b2f814 100644 --- a/build/Build.Android.cs +++ b/build/Build.Android.cs @@ -10,38 +10,54 @@ partial class Build [Parameter] readonly string NdkPath = GetVariable("ANDROID_NDK_HOME") ?? FindNdk(); static string FindNdk() - { + { + List candidates; if (OperatingSystem.IsWindows()) { - var candidates = new List() + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + candidates = + [ + Path.Combine(localAppData, "Android", "Sdk", "ndk"), + Path.Combine(localAppData, "Android", "Ndk") + ]; + } + else if (OperatingSystem.IsMacOS()) + { + var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + candidates = + [ + Path.Combine(home, "Library", "Android", "sdk", "ndk") + ]; + } + else + { + var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + candidates = + [ + Path.Combine(home, "Android", "Sdk", "ndk") + ]; + } + + return candidates.Select(c => + { + if (!Directory.Exists(c)) { - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Android", "Ndk") - }; + return null; + } - return candidates.Select(c => + if (File.Exists(Path.Combine(c, "package.xml"))) { - if (!Directory.Exists(c)) - { - return null; - } - - if (File.Exists(Path.Combine(c, "package.xml"))) - { - return c; - } + return c; + } - foreach (var subDir in Directory.EnumerateDirectories(c).OrderByDescending(Path.GetFileName)) + return Directory.EnumerateDirectories(c) + .Where(subDir => File.Exists(Path.Combine(subDir, "package.xml"))) + .OrderByDescending(subDir => { - if (File.Exists(Path.Combine(subDir, "package.xml"))) - { - return subDir; - } - } - - return null; - }).FirstOrDefault(d => d != null) ?? string.Empty; - } - - return string.Empty; + var name = Path.GetFileName(subDir); + return Version.TryParse(name, out var v) ? v : new Version(0, 0); + }) + .FirstOrDefault(); + }).FirstOrDefault(d => d != null) ?? string.Empty; } } \ No newline at end of file From b8419c0d57d38c8c00c95a17f831722d9927d63a Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 13:14:33 +0100 Subject: [PATCH 03/13] build: fix sdk manager path --- .github/workflows/~native.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/~native.yml b/.github/workflows/~native.yml index 059d593..2d30f41 100644 --- a/.github/workflows/~native.yml +++ b/.github/workflows/~native.yml @@ -66,9 +66,10 @@ jobs: - name: Install Latest Android NDK if: ${{ inputs.target-os == 'android' }} run: | - LATEST_NDK=$(sdkmanager --list 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) + SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" + LATEST_NDK=$("$SDKMANAGER" --list 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) echo "Installing NDK: $LATEST_NDK" - sdkmanager "$LATEST_NDK" + "$SDKMANAGER" "$LATEST_NDK" NDK_VERSION="${LATEST_NDK#ndk;}" echo "ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/$NDK_VERSION" >> "$GITHUB_ENV" @@ -123,9 +124,10 @@ jobs: - name: Install Latest Android NDK if: ${{ inputs.target-os == 'android' }} run: | - LATEST_NDK=$(sdkmanager --list 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) + SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" + LATEST_NDK=$("$SDKMANAGER" --list 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) echo "Installing NDK: $LATEST_NDK" - sdkmanager "$LATEST_NDK" + "$SDKMANAGER" "$LATEST_NDK" NDK_VERSION="${LATEST_NDK#ndk;}" echo "ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/$NDK_VERSION" >> "$GITHUB_ENV" From e765735ed5bc827064900c9b9b11b690e80a4d38 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 13:23:34 +0100 Subject: [PATCH 04/13] build: check alignment --- .github/workflows/~native.yml | 2 +- build/Build.Android.cs | 123 ++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/.github/workflows/~native.yml b/.github/workflows/~native.yml index 2d30f41..ce996f1 100644 --- a/.github/workflows/~native.yml +++ b/.github/workflows/~native.yml @@ -132,7 +132,7 @@ jobs: echo "ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/$NDK_VERSION" >> "$GITHUB_ENV" - name: Compiling LibAlphaSkia - run: dotnet build/bin/Debug/publish/_build.dll LibAlphaSkia LibAlphaSkiaTest --target-os ${{ inputs.target-os }} --architecture ${{ matrix.architecture }} --variant ${{ matrix.variant }} + run: dotnet build/bin/Debug/publish/_build.dll LibAlphaSkia LibAlphaSkiaTest CheckAndroidAlignment --target-os ${{ inputs.target-os }} --architecture ${{ matrix.architecture }} --variant ${{ matrix.variant }} shell: bash - name: Uploading LibAlphaSkia to Artifacts diff --git a/build/Build.Android.cs b/build/Build.Android.cs index 3b2f814..05d0156 100644 --- a/build/Build.Android.cs +++ b/build/Build.Android.cs @@ -1,14 +1,137 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; +using JetBrains.Annotations; using Nuke.Common; +using Nuke.Common.IO; +using Nuke.Common.Tooling; +using Serilog; using static Nuke.Common.EnvironmentInfo; partial class Build { [Parameter] readonly string NdkPath = GetVariable("ANDROID_NDK_HOME") ?? FindNdk(); + // Google Play Store requires 16KB page alignment for Android 15+ (API 35+) + // https://developer.android.com/guide/practices/page-sizes + const int AndroidRequiredLoadSegmentAlignment = 0x4000; + + [PublicAPI] + public Target CheckAndroidAlignment => t => t + .After(LibAlphaSkia) + .OnlyWhenStatic(() => TargetOs == TargetOperatingSystem.Android) + .Requires(() => Architecture) + .Requires(() => Variant) + .Executes(() => + { + if (!Variant.IsShared) + { + Log.Information("Skipping alignment check for static variant {Variant}", Variant); + return; + } + + var llvmReadElf = GetNdkLlvmReadElf(); + if (!llvmReadElf.FileExists()) + { + throw new FileNotFoundException($"llvm-readelf not found at {llvmReadElf}"); + } + + var readElfTool = ToolResolver.GetTool(llvmReadElf); + + var libDir = GetLibDirectory(variant: Variant); + var distPath = DistBasePath / libDir; + var soFiles = distPath.GlobFiles("*.so").ToArray(); + + if (soFiles.Length == 0) + { + throw new IOException($"No .so files found in {distPath} to check alignment"); + } + + var failures = new List(); + + foreach (var soFile in soFiles) + { + Log.Information("Checking 16KB alignment of {File}", soFile.Name); + var output = readElfTool($"-l {soFile}", logOutput: false); + var stdOutput = string.Join("\n", + output.Where(o => o.Type == OutputType.Std).Select(o => o.Text)); + + var loadAlignments = ParseLoadSegmentAlignments(stdOutput); + if (loadAlignments.Count == 0) + { + failures.Add($"{soFile.Name}: no PT_LOAD segments found"); + continue; + } + + foreach (var (index, alignment) in loadAlignments) + { + if (alignment < AndroidRequiredLoadSegmentAlignment) + { + failures.Add( + $"{soFile.Name}: PT_LOAD[{index}] alignment is 0x{alignment:X}" + + $" (expected >= 0x{AndroidRequiredLoadSegmentAlignment:X})"); + } + else + { + Log.Debug(" PT_LOAD[{Index}]: 0x{Alignment:X} OK", index, alignment); + } + } + } + + if (failures.Count > 0) + { + foreach (var failure in failures) + { + Log.Error("Alignment failure: {Failure}", failure); + } + + throw new InvalidOperationException( + $"{failures.Count} Android library alignment issue(s) found. " + + $"Google Play Store requires 16KB page alignment (0x{AndroidRequiredLoadSegmentAlignment:X}) for Android 15+. " + + "See https://developer.android.com/guide/practices/page-sizes"); + } + + Log.Information("All Android libraries pass 16KB page alignment check"); + }); + + AbsolutePath GetNdkLlvmReadElf() + { + var prebuiltBase = (AbsolutePath)NdkPath / "toolchains" / "llvm" / "prebuilt"; + var hostDir = prebuiltBase.GetDirectories().FirstOrDefault() + ?? throw new DirectoryNotFoundException( + $"No prebuilt toolchain directory found under {prebuiltBase}"); + return hostDir / "bin" / $"llvm-readelf{ExeExtension}"; + } + + static List<(int Index, long Alignment)> ParseLoadSegmentAlignments(string readElfOutput) + { + var result = new List<(int Index, long Alignment)>(); + var index = 0; + foreach (var line in readElfOutput.Split('\n')) + { + var trimmed = line.Trim(); + if (!trimmed.StartsWith("LOAD", StringComparison.Ordinal)) + { + continue; + } + + // The last whitespace-separated token on a LOAD line is the alignment value (e.g. 0x4000) + var parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries); + var alignStr = parts[^1]; + if (alignStr.StartsWith("0x", StringComparison.OrdinalIgnoreCase) && + long.TryParse(alignStr[2..], NumberStyles.HexNumber, null, out var alignment)) + { + result.Add((index, alignment)); + } + + index++; + } + + return result; + } + static string FindNdk() { List candidates; From f99eddb3b2081ba01dfdd9aaf749de62de381f9a Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 13:26:54 +0100 Subject: [PATCH 05/13] build: fix android fontmgr sources --- build/Build.LibSkia.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/build/Build.LibSkia.cs b/build/Build.LibSkia.cs index a1b3a70..7357693 100644 --- a/build/Build.LibSkia.cs +++ b/build/Build.LibSkia.cs @@ -181,7 +181,6 @@ partial class Build newSources += " if (is_android) {\n"; newSources += " deps += [ \"//third_party/expat\" ]\n"; newSources += " sources += skia_ports_fontmgr_android_sources\n"; - newSources += " sources += skia_ports_fontmgr_android_parser_sources\n"; newSources += " }\n"; newSources += " frameworks = []\n"; newSources += " if (is_mac) {\n"; From 2ef57e8ba69af12277817fd82d820fece71d7ffe Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 13:44:36 +0100 Subject: [PATCH 06/13] build: update toolchain --- .github/workflows/publish.yml | 34 +++++++++++----------- .github/workflows/~dotnet.yml | 24 +++++++-------- .github/workflows/~java.yml | 28 +++++++++--------- .github/workflows/~native.yml | 28 +++++++++--------- .github/workflows/~node.yml | 26 ++++++++--------- .github/workflows/~reusable-full-build.yml | 8 ++--- build/_build.csproj | 4 +-- 7 files changed, 76 insertions(+), 76 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f7092d4..dcfe7e2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -26,15 +26,15 @@ jobs: env: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} steps: - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + dotnet-version: '10' + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: nupkgs path: dist/.organize/nupkgs @@ -52,19 +52,19 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKey: ${{secrets.SONATYPE_SIGNING_KEY}} steps: - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' - - uses: actions/setup-java@v4 + dotnet-version: '10' + - uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '17' - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: # workaround until we know how to upload existing maven packages # https://discuss.gradle.org/t/how-to-push-maven-to-ossrh-from-previous-local-publish/46875 @@ -80,19 +80,19 @@ jobs: env: NPMJS_AUTH_TOKEN: ${{ secrets.NPMJS_AUTH_TOKEN }} steps: - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + dotnet-version: '10' + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: nodetars path: dist/.organize/node - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: "lts/*" registry-url: https://registry.npmjs.org/ diff --git a/.github/workflows/~dotnet.yml b/.github/workflows/~dotnet.yml index 135fd5b..8b03909 100644 --- a/.github/workflows/~dotnet.yml +++ b/.github/workflows/~dotnet.yml @@ -13,20 +13,20 @@ jobs: dotnet: runs-on: ubuntu-latest steps: - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + dotnet-version: '10' + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: path: dist/.organize - run: dotnet build/bin/Debug/publish/_build.dll DotNet shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: nupkgs path: | @@ -51,21 +51,21 @@ jobs: runs-on: ${{ matrix.runs-on }} needs: [dotnet] steps: - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + dotnet-version: '10' + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: nupkgs path: dist/.organize/nupkgs - run: dotnet build/bin/Debug/publish/_build.dll DotNetTest --architecture ${{ matrix.architecture }} --framework ${{ matrix.framework }} shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: always() with: name: dotnettest-${{ matrix.runs-on }}-${{ matrix.architecture }}--${{ matrix.framework }} diff --git a/.github/workflows/~java.yml b/.github/workflows/~java.yml index 90f2216..3f82844 100644 --- a/.github/workflows/~java.yml +++ b/.github/workflows/~java.yml @@ -19,24 +19,24 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{secrets.SONATYPE_SIGNING_PASSWORD}} ORG_GRADLE_PROJECT_signingInMemoryKey: ${{secrets.SONATYPE_SIGNING_KEY}} steps: - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '17' - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + dotnet-version: '10' + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: path: dist/.organize - run: dotnet build/bin/Debug/publish/_build.dll Java shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: maven path: | @@ -52,25 +52,25 @@ jobs: runs-on: ${{ matrix.runs-on }} needs: [java] steps: - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '17' - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + dotnet-version: '10' + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: maven path: dist/.organize/maven - run: dotnet build/bin/Debug/publish/_build.dll JavaTest shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: always() with: name: javatest-${{ matrix.runs-on }} diff --git a/.github/workflows/~native.yml b/.github/workflows/~native.yml index ce996f1..069d832 100644 --- a/.github/workflows/~native.yml +++ b/.github/workflows/~native.yml @@ -43,20 +43,20 @@ jobs: matrix: architecture: ${{ fromJson( inputs.architectures ) }} steps: - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' + dotnet-version: '10' - name: Cloning Repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Fetching Pre-Built Nuke - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - name: Fetch Cached LibSkia - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 if: ${{ inputs.use-skia-cache }} with: path: dist @@ -79,13 +79,13 @@ jobs: id: build - name: Uploading LibSkia to Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static path: artifacts - name: Uploading LibSkia to Build Cache - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: ${{ steps.build.outputs.build-skipped != 'true' }} with: path: dist @@ -101,22 +101,22 @@ jobs: architecture: ${{ fromJson( inputs.architectures ) }} variant: ${{ fromJson( inputs.variants ) }} steps: - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' + dotnet-version: '10' - name: Cloning Repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: true - name: Fetching Pre-Built Nuke - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - name: Downloading LibSkia - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: dist/.organize/libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static name: libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static @@ -136,14 +136,14 @@ jobs: shell: bash - name: Uploading LibAlphaSkia to Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: ${{ always() }} with: name: libalphaskia-${{ inputs.target-os }}-${{ matrix.architecture }}-${{ matrix.variant }} path: artifacts - name: Uploading LibAlphaSkiaTest to Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: ${{ always() }} with: name: libalphaskiatest-${{ inputs.target-os }}-${{ matrix.architecture }}-${{ matrix.variant }} diff --git a/.github/workflows/~node.yml b/.github/workflows/~node.yml index 841e13e..59c4650 100644 --- a/.github/workflows/~node.yml +++ b/.github/workflows/~node.yml @@ -14,20 +14,20 @@ jobs: node: runs-on: ubuntu-latest steps: - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + dotnet-version: '10' + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: path: dist/.organize - run: dotnet build/bin/Debug/publish/_build.dll Node shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: nodetars path: | @@ -41,19 +41,19 @@ jobs: runs-on: ${{ matrix.runs-on }} needs: [node] steps: - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + dotnet-version: '10' + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v8 with: name: nuke path: build/bin/Debug/publish/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: nodetars path: dist/.organize/node - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node }} - run: dotnet build/bin/Debug/publish/_build.dll NodeTest @@ -62,7 +62,7 @@ jobs: if: always() run: echo "NODE_VERSION=${{ matrix.node }}" | sed 's/[*\/]//g' >> "$GITHUB_OUTPUT" shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: always() with: name: nodetest-${{ matrix.runs-on }}-${{ steps.sanitize-node-version.outputs.NODE_VERSION }} diff --git a/.github/workflows/~reusable-full-build.yml b/.github/workflows/~reusable-full-build.yml index 87458d9..5449e44 100644 --- a/.github/workflows/~reusable-full-build.yml +++ b/.github/workflows/~reusable-full-build.yml @@ -20,13 +20,13 @@ jobs: nuke: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-dotnet@v5 with: - dotnet-version: '9' + dotnet-version: '10' - run: dotnet publish --configuration Debug ./build/_build.csproj shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: nuke path: build/bin/Debug/publish/ diff --git a/build/_build.csproj b/build/_build.csproj index e1a98e3..99fb31c 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 CS0649;CS0169;CA1050;CA1822;CA2211;IDE1006 .. @@ -12,7 +12,7 @@ - + From 58d329d25b8878bd0560f3c34b33ce7caedd77e4 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 14:16:54 +0100 Subject: [PATCH 07/13] build: fix cpu_features compilation --- .github/workflows/~native.yml | 4 +-- .gitmodules | 3 ++ build/Build.LibSkia.cs | 53 +++++++++++++++++++++++++++++++++++ externals/cpu_features | 1 + 4 files changed, 59 insertions(+), 2 deletions(-) create mode 160000 externals/cpu_features diff --git a/.github/workflows/~native.yml b/.github/workflows/~native.yml index 069d832..89a1c04 100644 --- a/.github/workflows/~native.yml +++ b/.github/workflows/~native.yml @@ -67,7 +67,7 @@ jobs: if: ${{ inputs.target-os == 'android' }} run: | SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" - LATEST_NDK=$("$SDKMANAGER" --list 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) + LATEST_NDK=$("$SDKMANAGER" --list --channel=0 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) echo "Installing NDK: $LATEST_NDK" "$SDKMANAGER" "$LATEST_NDK" NDK_VERSION="${LATEST_NDK#ndk;}" @@ -125,7 +125,7 @@ jobs: if: ${{ inputs.target-os == 'android' }} run: | SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" - LATEST_NDK=$("$SDKMANAGER" --list 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) + LATEST_NDK=$("$SDKMANAGER" --list --channel=0 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) echo "Installing NDK: $LATEST_NDK" "$SDKMANAGER" "$LATEST_NDK" NDK_VERSION="${LATEST_NDK#ndk;}" diff --git a/.gitmodules b/.gitmodules index 79e21ae..2dd8bec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,6 +6,9 @@ path = externals/skia url = https://skia.googlesource.com/skia.git branch = chrome/m147 +[submodule "externals/cpu_features"] + path = externals/cpu_features + url = https://github.com/google/cpu_features.git [submodule "externals/node-api-headers"] path = externals/node-api-headers url = https://github.com/nodejs/node-api-headers.git diff --git a/build/Build.LibSkia.cs b/build/Build.LibSkia.cs index 7357693..fbada3e 100644 --- a/build/Build.LibSkia.cs +++ b/build/Build.LibSkia.cs @@ -262,9 +262,56 @@ partial class Build PatchSkiaToolchain(); PatchVulcanAllocatorIncludes(); + PatchCpuFeaturesTarget(); PatchLlvm(); }); + /// + /// NDK r26+ removed sources/android/cpufeatures/. Skia's third_party/cpu-features/BUILD.gn + /// still hardcodes that path. We patch it to accept a GN variable pointing at the + /// google/cpu_features ndk_compat shim (the official API-compatible replacement). + /// + void PatchCpuFeaturesTarget() + { + var buildFile = SkiaPath / "third_party" / "cpu-features" / "BUILD.gn"; + var content = buildFile.ReadAllText(); + + // Idempotent: skip if already patched + if (content.Contains("cpu_features_ndk_compat")) + { + return; + } + + buildFile.WriteAllText( + """ + # Copyright 2016 Google Inc. + # + # Use of this source code is governed by a BSD-style license that can be + # found in the LICENSE file. + # + # Patched by alphaSkia: NDK r26+ removed sources/android/cpufeatures/. + # cpu_features_ndk_compat points at the ndk_compat shim from google/cpu_features + # (https://github.com/google/cpu_features), the official API-compatible replacement. + + declare_args() { + cpu_features_ndk_compat = "" + } + + import("../third_party.gni") + + third_party("cpu-features") { + if (cpu_features_ndk_compat != "") { + public_include_dirs = [ cpu_features_ndk_compat ] + sources = [ cpu_features_ndk_compat + "/cpu-features.c" ] + } else { + public_include_dirs = [ "$ndk/sources/android/cpufeatures" ] + sources = [ "$ndk/sources/android/cpufeatures/cpu-features.c" ] + } + } + """ + ); + } + void PatchVulcanAllocatorIncludes() { // https://github.com/microsoft/vcpkg/issues/31875 @@ -332,6 +379,12 @@ void BuildSkia() gnArgs["skia_enable_ganesh"] = "true"; gnArgs["skia_use_vulkan"] = "true"; + if (TargetOs == TargetOperatingSystem.Android) + { + gnArgs["cpu_features_ndk_compat"] = + (RootDirectory / "externals" / "cpu_features" / "ndk_compat").ToString().Replace('\\', '/'); + } + GnNinja($"out/{libDir}", "skia", gnArgs, gnFlags, SkiaPath); void CopyBuildOutputTo(AbsolutePath path) diff --git a/externals/cpu_features b/externals/cpu_features new file mode 160000 index 0000000..d4e1ac2 --- /dev/null +++ b/externals/cpu_features @@ -0,0 +1 @@ +Subproject commit d4e1ac223fac22e8b6bf2378b02c3aba6fe26617 From f407448bbb809787cc4ee17318992febaa681f13 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 18:13:05 +0100 Subject: [PATCH 08/13] build: NDK fixes --- .github/workflows/~native.yml | 24 ++++++++++++++---------- build/Build.Native.cs | 5 +++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/~native.yml b/.github/workflows/~native.yml index 89a1c04..e4479f2 100644 --- a/.github/workflows/~native.yml +++ b/.github/workflows/~native.yml @@ -63,14 +63,16 @@ jobs: key: libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static-${{ github.run_id }} restore-keys: libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static - - name: Install Latest Android NDK + - name: Install Latest Stable Android NDK if: ${{ inputs.target-os == 'android' }} run: | SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" - LATEST_NDK=$("$SDKMANAGER" --list --channel=0 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) - echo "Installing NDK: $LATEST_NDK" - "$SDKMANAGER" "$LATEST_NDK" - NDK_VERSION="${LATEST_NDK#ndk;}" + ALL_NDKS=$("$SDKMANAGER" --list --channel=0 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V) + LATEST_MAJOR=$(echo "$ALL_NDKS" | awk -F'[.;]' '{print $2}' | sort -Vu | tail -1) + STABLE_NDK=$(echo "$ALL_NDKS" | awk -F'[.;]' -v max="$LATEST_MAJOR" '$2+0 < max+0' | sort -V | tail -1) + echo "Installing NDK: $STABLE_NDK" + "$SDKMANAGER" "$STABLE_NDK" + NDK_VERSION="${STABLE_NDK#ndk;}" echo "ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/$NDK_VERSION" >> "$GITHUB_ENV" - name: Compiling LibSkia @@ -121,14 +123,16 @@ jobs: path: dist/.organize/libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static name: libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static - - name: Install Latest Android NDK + - name: Install Latest Stable Android NDK if: ${{ inputs.target-os == 'android' }} run: | SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" - LATEST_NDK=$("$SDKMANAGER" --list --channel=0 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V | tail -1) - echo "Installing NDK: $LATEST_NDK" - "$SDKMANAGER" "$LATEST_NDK" - NDK_VERSION="${LATEST_NDK#ndk;}" + ALL_NDKS=$("$SDKMANAGER" --list --channel=0 2>/dev/null | grep -E "^\s+ndk;" | awk '{print $1}' | sort -V) + LATEST_MAJOR=$(echo "$ALL_NDKS" | awk -F'[.;]' '{print $2}' | sort -Vu | tail -1) + STABLE_NDK=$(echo "$ALL_NDKS" | awk -F'[.;]' -v max="$LATEST_MAJOR" '$2+0 < max+0' | sort -V | tail -1) + echo "Installing NDK: $STABLE_NDK" + "$SDKMANAGER" "$STABLE_NDK" + NDK_VERSION="${STABLE_NDK#ndk;}" echo "ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/$NDK_VERSION" >> "$GITHUB_ENV" - name: Compiling LibAlphaSkia diff --git a/build/Build.Native.cs b/build/Build.Native.cs index 9adf935..d5f39b3 100644 --- a/build/Build.Native.cs +++ b/build/Build.Native.cs @@ -398,6 +398,11 @@ void PatchSkiaToolchain() ).Replace( " env_setup = \"$shell set \\\"PATH=%PATH%;$win_vc\\\\Tools\\\\MSVC\\\\$win_toolchain_version\\\\bin\\\\HostX64\\\\x64\\\" && \"", " # env_setup = \"$shell set \\\"PATH=%PATH%;$win_vc\\\\Tools\\\\MSVC\\\\$win_toolchain_version\\\\bin\\\\HostX64\\\\x64\\\" && \"" + ).Replace( + // NDK r23+ removed arch-specific compiler wrappers (e.g. armv7a-linux-androideabi21-clang). + // Use clang --target= instead, matching the existing Windows-host pattern. + " target_cc = \"$_prefix/$ndk_target$ndk_api-clang\"\n target_cxx = \"$_prefix/$ndk_target$ndk_api-clang++\"", + " target_cc = \"$_prefix/clang --target=$ndk_target$ndk_api -fno-addrsig\"\n target_cxx = \"$_prefix/clang++ --target=$ndk_target$ndk_api -fno-addrsig\"" )); } From bbde0c2e8199d1fa94ec4b3a2f457ebbe0b2c8cc Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 18:37:42 +0100 Subject: [PATCH 09/13] build: more ndk fixes --- build/Build.LibSkia.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build/Build.LibSkia.cs b/build/Build.LibSkia.cs index fbada3e..3df5f05 100644 --- a/build/Build.LibSkia.cs +++ b/build/Build.LibSkia.cs @@ -292,16 +292,19 @@ void PatchCpuFeaturesTarget() # Patched by alphaSkia: NDK r26+ removed sources/android/cpufeatures/. # cpu_features_ndk_compat points at the ndk_compat shim from google/cpu_features # (https://github.com/google/cpu_features), the official API-compatible replacement. + # cpu_features_include points at the include/ directory of that library, which + # ndk_compat/cpu-features.c needs for cpu_features_macros.h and related headers. declare_args() { cpu_features_ndk_compat = "" + cpu_features_include = "" } import("../third_party.gni") third_party("cpu-features") { if (cpu_features_ndk_compat != "") { - public_include_dirs = [ cpu_features_ndk_compat ] + public_include_dirs = [ cpu_features_ndk_compat, cpu_features_include ] sources = [ cpu_features_ndk_compat + "/cpu-features.c" ] } else { public_include_dirs = [ "$ndk/sources/android/cpufeatures" ] @@ -383,6 +386,8 @@ void BuildSkia() { gnArgs["cpu_features_ndk_compat"] = (RootDirectory / "externals" / "cpu_features" / "ndk_compat").ToString().Replace('\\', '/'); + gnArgs["cpu_features_include"] = + (RootDirectory / "externals" / "cpu_features" / "include").ToString().Replace('\\', '/'); } GnNinja($"out/{libDir}", "skia", gnArgs, gnFlags, SkiaPath); From 76d63c8b3e37b89f0c24097da5c93492f1beac8f Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 19:03:17 +0100 Subject: [PATCH 10/13] build: more NDK adjustments --- build/Build.LibSkia.cs | 56 ++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/build/Build.LibSkia.cs b/build/Build.LibSkia.cs index 3df5f05..e023045 100644 --- a/build/Build.LibSkia.cs +++ b/build/Build.LibSkia.cs @@ -268,8 +268,14 @@ partial class Build /// /// NDK r26+ removed sources/android/cpufeatures/. Skia's third_party/cpu-features/BUILD.gn - /// still hardcodes that path. We patch it to accept a GN variable pointing at the - /// google/cpu_features ndk_compat shim (the official API-compatible replacement). + /// still hardcodes that path. We patch it to build the google/cpu_features ndk_compat shim + /// (the official API-compatible replacement) as a proper multi-file library. + /// + /// The old NDK cpufeatures was a monolithic single .c file. The ndk_compat shim is a full + /// library that requires: utils (filesystem, stack_line_reader, string_view), hardware + /// detection (hwcaps, hwcaps_linux_or_android), and an architecture-specific impl_*.c. + /// It also requires STACK_LINE_READER_BUFFER_SIZE=1024 injected as a compile definition + /// (normally done by CMake via setup_include_and_definitions()). /// void PatchCpuFeaturesTarget() { @@ -277,7 +283,7 @@ void PatchCpuFeaturesTarget() var content = buildFile.ReadAllText(); // Idempotent: skip if already patched - if (content.Contains("cpu_features_ndk_compat")) + if (content.Contains("cpu_features_dir")) { return; } @@ -290,22 +296,42 @@ void PatchCpuFeaturesTarget() # found in the LICENSE file. # # Patched by alphaSkia: NDK r26+ removed sources/android/cpufeatures/. - # cpu_features_ndk_compat points at the ndk_compat shim from google/cpu_features - # (https://github.com/google/cpu_features), the official API-compatible replacement. - # cpu_features_include points at the include/ directory of that library, which - # ndk_compat/cpu-features.c needs for cpu_features_macros.h and related headers. + # cpu_features_dir points at the google/cpu_features repo root + # (https://github.com/google/cpu_features), the official replacement. + # ndk_compat/ provides the API-compatible header; src/ provides the + # implementation objects (utils + hardware detection + arch-specific). + # STACK_LINE_READER_BUFFER_SIZE must be injected at compile time (value 1024 + # matches the default in cpu_features' CMakeLists.txt). declare_args() { - cpu_features_ndk_compat = "" - cpu_features_include = "" + cpu_features_dir = "" } import("../third_party.gni") third_party("cpu-features") { - if (cpu_features_ndk_compat != "") { - public_include_dirs = [ cpu_features_ndk_compat, cpu_features_include ] - sources = [ cpu_features_ndk_compat + "/cpu-features.c" ] + if (cpu_features_dir != "") { + public_include_dirs = [ cpu_features_dir + "/ndk_compat" ] + include_dirs = [ cpu_features_dir + "/include" ] + defines = [ "STACK_LINE_READER_BUFFER_SIZE=1024" ] + sources = [ + cpu_features_dir + "/ndk_compat/cpu-features.c", + cpu_features_dir + "/src/filesystem.c", + cpu_features_dir + "/src/stack_line_reader.c", + cpu_features_dir + "/src/string_view.c", + cpu_features_dir + "/src/hwcaps.c", + cpu_features_dir + "/src/hwcaps_linux_or_android.c", + ] + if (target_cpu == "arm") { + sources += [ cpu_features_dir + "/src/impl_arm_linux_or_android.c" ] + } else if (target_cpu == "arm64") { + sources += [ + cpu_features_dir + "/src/impl_aarch64_cpuid.c", + cpu_features_dir + "/src/impl_aarch64_linux_or_android.c", + ] + } else if (target_cpu == "x86" || target_cpu == "x64") { + sources += [ cpu_features_dir + "/src/impl_x86_linux_or_android.c" ] + } } else { public_include_dirs = [ "$ndk/sources/android/cpufeatures" ] sources = [ "$ndk/sources/android/cpufeatures/cpu-features.c" ] @@ -384,10 +410,8 @@ void BuildSkia() if (TargetOs == TargetOperatingSystem.Android) { - gnArgs["cpu_features_ndk_compat"] = - (RootDirectory / "externals" / "cpu_features" / "ndk_compat").ToString().Replace('\\', '/'); - gnArgs["cpu_features_include"] = - (RootDirectory / "externals" / "cpu_features" / "include").ToString().Replace('\\', '/'); + gnArgs["cpu_features_dir"] = + (RootDirectory / "externals" / "cpu_features").ToString().Replace('\\', '/'); } GnNinja($"out/{libDir}", "skia", gnArgs, gnFlags, SkiaPath); From 3d6915ca1027d64d0189f032bb4c9a6a15b197c5 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 19:13:12 +0100 Subject: [PATCH 11/13] build: more NDK updates for android --- build/Build.LibSkia.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/build/Build.LibSkia.cs b/build/Build.LibSkia.cs index e023045..4b013cc 100644 --- a/build/Build.LibSkia.cs +++ b/build/Build.LibSkia.cs @@ -313,7 +313,14 @@ void PatchCpuFeaturesTarget() if (cpu_features_dir != "") { public_include_dirs = [ cpu_features_dir + "/ndk_compat" ] include_dirs = [ cpu_features_dir + "/include" ] - defines = [ "STACK_LINE_READER_BUFFER_SIZE=1024" ] + defines = [ + "STACK_LINE_READER_BUFFER_SIZE=1024", + # Android API 21+ always provides getauxval in sys/auxv.h. + # CMake detects this via check_symbol_exists(getauxval "sys/auxv.h" ...) + # and injects HAVE_STRONG_GETAUXVAL into unix_based_hardware_detection. + # Without it hwcaps_linux_or_android.c hits #error at the else branch. + "HAVE_STRONG_GETAUXVAL", + ] sources = [ cpu_features_dir + "/ndk_compat/cpu-features.c", cpu_features_dir + "/src/filesystem.c", From 7a1c8d11127324c19a8d16309a3d1cbcd8395180 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 20:25:17 +0100 Subject: [PATCH 12/13] build: fix alignment checker --- build/Build.Android.cs | 2 +- build/Build.LibAlphaSkia.cs | 19 ++----------------- build/Build.Native.cs | 4 ++++ 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/build/Build.Android.cs b/build/Build.Android.cs index 05d0156..07388fb 100644 --- a/build/Build.Android.cs +++ b/build/Build.Android.cs @@ -40,7 +40,7 @@ partial class Build var readElfTool = ToolResolver.GetTool(llvmReadElf); - var libDir = GetLibDirectory(variant: Variant); + var libDir = GetLibDirectory(GetAlphaSkiaLibName(Variant), variant: Variant); var distPath = DistBasePath / libDir; var soFiles = distPath.GlobFiles("*.so").ToArray(); diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index 05df893..79744e0 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -248,18 +248,9 @@ void BuildAlphaSkia() var staticLibPath = DistBasePath / GetLibDirectory(variant: Variant.Static); var gnFlags = new Dictionary(); - string buildTarget; - if (Variant == Variant.Static) + var buildTarget = GetAlphaSkiaLibName(Variant); + if (Variant == Variant.Jni) { - buildTarget = "libalphaskia"; - } - else if (Variant == Variant.Shared) - { - buildTarget = "libalphaskia"; - } - else if (Variant == Variant.Jni) - { - buildTarget = "libalphaskiajni"; var alphaSkiaInclude = DistBasePath / "include"; var jniInclude = JavaHome / "include"; @@ -293,8 +284,6 @@ void BuildAlphaSkia() } else if (Variant == Variant.Node) { - buildTarget = "libalphaskianode"; - if (OperatingSystem.IsWindows()) { // windows requires a lib to link against, fetch it from the node downloads @@ -309,10 +298,6 @@ void BuildAlphaSkia() AppendToFlagList(gnArgs, "extra_ldflags", "'-undefined', 'dynamic_lookup'"); } } - else - { - throw new ArgumentException("Unknown variant: " + Variant); - } if (TargetOs == TargetOperatingSystem.Windows) { diff --git a/build/Build.Native.cs b/build/Build.Native.cs index d5f39b3..659a664 100644 --- a/build/Build.Native.cs +++ b/build/Build.Native.cs @@ -70,6 +70,10 @@ string GetLibDirectory(string libName = "libskia", TargetOperatingSystem targetO return $"{libName.ToLowerInvariant()}-{targetOs?.RuntimeIdentifier}-{arch}-{variant}"; } + static string GetAlphaSkiaLibName(Variant variant) => variant == Variant.Jni ? "libalphaskiajni" + : variant == Variant.Node ? "libalphaskianode" + : "libalphaskia"; + Task GitSyncDepsCustom(string[] requiredDependencies) { var depsFile = SkiaPath / "DEPS"; From 9980881735c29604aa62ebc0eb2c30e3f97300a2 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 28 Mar 2026 20:32:58 +0100 Subject: [PATCH 13/13] build: fix alignment check --- build/Build.Android.cs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/build/Build.Android.cs b/build/Build.Android.cs index 07388fb..431c7d6 100644 --- a/build/Build.Android.cs +++ b/build/Build.Android.cs @@ -14,9 +14,11 @@ partial class Build { [Parameter] readonly string NdkPath = GetVariable("ANDROID_NDK_HOME") ?? FindNdk(); - // Google Play Store requires 16KB page alignment for Android 15+ (API 35+) + // Google Play Store requires 16KB page alignment for Android 15+ (API 35+) on 64-bit architectures only. + // 32-bit architectures (x86, arm) use 4KB page alignment and are exempt from this requirement. // https://developer.android.com/guide/practices/page-sizes - const int AndroidRequiredLoadSegmentAlignment = 0x4000; + const int AndroidRequiredLoadSegmentAlignment64Bit = 0x4000; + const int AndroidRequiredLoadSegmentAlignment32Bit = 0x1000; [PublicAPI] public Target CheckAndroidAlignment => t => t @@ -32,6 +34,17 @@ partial class Build return; } + // 32-bit architectures (x86, arm) only support 4KB page alignment + var is64Bit = Architecture == Architecture.Arm64 || Architecture == Architecture.X64; + var requiredAlignment = is64Bit + ? AndroidRequiredLoadSegmentAlignment64Bit + : AndroidRequiredLoadSegmentAlignment32Bit; + + if (!is64Bit) + { + Log.Information("Using 4KB alignment requirement for 32-bit architecture {Architecture}", Architecture); + } + var llvmReadElf = GetNdkLlvmReadElf(); if (!llvmReadElf.FileExists()) { @@ -53,7 +66,7 @@ partial class Build foreach (var soFile in soFiles) { - Log.Information("Checking 16KB alignment of {File}", soFile.Name); + Log.Information("Checking alignment (0x{Required:X}) of {File}", requiredAlignment, soFile.Name); var output = readElfTool($"-l {soFile}", logOutput: false); var stdOutput = string.Join("\n", output.Where(o => o.Type == OutputType.Std).Select(o => o.Text)); @@ -67,11 +80,11 @@ partial class Build foreach (var (index, alignment) in loadAlignments) { - if (alignment < AndroidRequiredLoadSegmentAlignment) + if (alignment < requiredAlignment) { failures.Add( $"{soFile.Name}: PT_LOAD[{index}] alignment is 0x{alignment:X}" + - $" (expected >= 0x{AndroidRequiredLoadSegmentAlignment:X})"); + $" (expected >= 0x{requiredAlignment:X})"); } else { @@ -87,13 +100,14 @@ partial class Build Log.Error("Alignment failure: {Failure}", failure); } + var alignmentKb = requiredAlignment / 1024; throw new InvalidOperationException( $"{failures.Count} Android library alignment issue(s) found. " + - $"Google Play Store requires 16KB page alignment (0x{AndroidRequiredLoadSegmentAlignment:X}) for Android 15+. " + + $"Required page alignment is 0x{requiredAlignment:X} ({alignmentKb}KB) for {Architecture}. " + "See https://developer.android.com/guide/practices/page-sizes"); } - Log.Information("All Android libraries pass 16KB page alignment check"); + Log.Information("All Android libraries pass page alignment check (0x{Required:X})", requiredAlignment); }); AbsolutePath GetNdkLlvmReadElf()