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 158bdf0..e4479f2 100644 --- a/.github/workflows/~native.yml +++ b/.github/workflows/~native.yml @@ -43,39 +43,51 @@ 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 key: libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static-${{ github.run_id }} restore-keys: libskia-${{ inputs.target-os }}-${{ matrix.architecture }}-static + - name: Install Latest Stable Android NDK + if: ${{ inputs.target-os == 'android' }} + run: | + SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" + 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 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 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 @@ -91,39 +103,51 @@ 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 + - name: Install Latest Stable Android NDK + if: ${{ inputs.target-os == 'android' }} + run: | + SDKMANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" + 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 - 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 - 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/.gitmodules b/.gitmodules index 412c801..2dd8bec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,7 +5,10 @@ [submodule "externals/skia"] path = externals/skia url = https://skia.googlesource.com/skia.git - branch = chrome/m135 + 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/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.Android.cs b/build/Build.Android.cs index e7d99fe..431c7d6 100644 --- a/build/Build.Android.cs +++ b/build/Build.Android.cs @@ -1,47 +1,200 @@ 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(); - static string FindNdk() - { - if (OperatingSystem.IsWindows()) + // 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 AndroidRequiredLoadSegmentAlignment64Bit = 0x4000; + const int AndroidRequiredLoadSegmentAlignment32Bit = 0x1000; + + [PublicAPI] + public Target CheckAndroidAlignment => t => t + .After(LibAlphaSkia) + .OnlyWhenStatic(() => TargetOs == TargetOperatingSystem.Android) + .Requires(() => Architecture) + .Requires(() => Variant) + .Executes(() => { - var candidates = new List() + if (!Variant.IsShared) { - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Android", "Ndk") - }; + Log.Information("Skipping alignment check for static variant {Variant}", Variant); + return; + } + + // 32-bit architectures (x86, arm) only support 4KB page alignment + var is64Bit = Architecture == Architecture.Arm64 || Architecture == Architecture.X64; + var requiredAlignment = is64Bit + ? AndroidRequiredLoadSegmentAlignment64Bit + : AndroidRequiredLoadSegmentAlignment32Bit; - return candidates.Select(c => + if (!is64Bit) { - if (!Directory.Exists(c)) - { - return null; - } - - if (File.Exists(Path.Combine(c, "package.xml"))) + Log.Information("Using 4KB alignment requirement for 32-bit architecture {Architecture}", Architecture); + } + + var llvmReadElf = GetNdkLlvmReadElf(); + if (!llvmReadElf.FileExists()) + { + throw new FileNotFoundException($"llvm-readelf not found at {llvmReadElf}"); + } + + var readElfTool = ToolResolver.GetTool(llvmReadElf); + + var libDir = GetLibDirectory(GetAlphaSkiaLibName(Variant), 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 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)); + + var loadAlignments = ParseLoadSegmentAlignments(stdOutput); + if (loadAlignments.Count == 0) { - return c; + failures.Add($"{soFile.Name}: no PT_LOAD segments found"); + continue; } - foreach (var subDir in Directory.EnumerateDirectories(c).OrderByDescending(Path.GetFileName)) + foreach (var (index, alignment) in loadAlignments) { - if (File.Exists(Path.Combine(subDir, "package.xml"))) + if (alignment < requiredAlignment) { - return subDir; + failures.Add( + $"{soFile.Name}: PT_LOAD[{index}] alignment is 0x{alignment:X}" + + $" (expected >= 0x{requiredAlignment:X})"); + } + else + { + Log.Debug(" PT_LOAD[{Index}]: 0x{Alignment:X} OK", index, alignment); } } + } - return null; - }).FirstOrDefault(d => d != null) ?? string.Empty; + if (failures.Count > 0) + { + foreach (var failure in failures) + { + Log.Error("Alignment failure: {Failure}", failure); + } + + var alignmentKb = requiredAlignment / 1024; + throw new InvalidOperationException( + $"{failures.Count} Android library alignment issue(s) found. " + + $"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 page alignment check (0x{Required:X})", requiredAlignment); + }); + + 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 string.Empty; + return result; + } + + static string FindNdk() + { + List candidates; + if (OperatingSystem.IsWindows()) + { + 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)) + { + return null; + } + + if (File.Exists(Path.Combine(c, "package.xml"))) + { + return c; + } + + return Directory.EnumerateDirectories(c) + .Where(subDir => File.Exists(Path.Combine(subDir, "package.xml"))) + .OrderByDescending(subDir => + { + 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 diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index e23b010..79744e0 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(); }); @@ -249,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"; @@ -294,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 @@ -310,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.LibSkia.cs b/build/Build.LibSkia.cs index a89b70d..4b013cc 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"); @@ -261,11 +261,93 @@ partial class Build buildFile.WriteAllText(buildFileSource); PatchSkiaToolchain(); - PatchSkiaMacOsVersion(); 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 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() + { + var buildFile = SkiaPath / "third_party" / "cpu-features" / "BUILD.gn"; + var content = buildFile.ReadAllText(); + + // Idempotent: skip if already patched + if (content.Contains("cpu_features_dir")) + { + 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_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_dir = "" + } + + import("../third_party.gni") + + third_party("cpu-features") { + 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", + # 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", + 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" ] + } + } + """ + ); + } + void PatchVulcanAllocatorIncludes() { // https://github.com/microsoft/vcpkg/issues/31875 @@ -304,7 +386,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 +403,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"; @@ -336,6 +415,12 @@ void BuildSkia() gnArgs["skia_enable_ganesh"] = "true"; gnArgs["skia_use_vulkan"] = "true"; + if (TargetOs == TargetOperatingSystem.Android) + { + gnArgs["cpu_features_dir"] = + (RootDirectory / "externals" / "cpu_features").ToString().Replace('\\', '/'); + } + GnNinja($"out/{libDir}", "skia", gnArgs, gnFlags, SkiaPath); void CopyBuildOutputTo(AbsolutePath path) 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/Build.Native.cs b/build/Build.Native.cs index 9adf935..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"; @@ -398,6 +402,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\"" )); } 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", 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 @@ - + 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