commit ff4458d08937238366de41b6a77f5e377bfc87e8 Author: geruipu Date: Sun Jun 21 12:37:51 2026 +0800 StaticLink.Avalonia baseline diff --git a/.github/workflows/macos-static-avalonia-native.yml b/.github/workflows/macos-static-avalonia-native.yml new file mode 100644 index 0000000..10abc25 --- /dev/null +++ b/.github/workflows/macos-static-avalonia-native.yml @@ -0,0 +1,256 @@ +name: macOS static AvaloniaNative experiment + +on: + workflow_dispatch: + inputs: + publish_to_release: + description: Publish experimental artifacts to GitHub Release + required: true + default: true + type: boolean + +permissions: + contents: write + +jobs: + build-macos-native: + strategy: + fail-fast: false + matrix: + include: + - rid: osx-x64 + runner: macos-14 + target_cpu: x64 + - rid: osx-arm64 + runner: macos-latest + target_cpu: arm64 + runs-on: ${{ matrix.runner }} + env: + RID: ${{ matrix.rid }} + TARGET_CPU: ${{ matrix.target_cpu }} + WORK_DIR: ${{ github.workspace }}/External/NativeStatic/.work + OUTPUT_DIR: ${{ github.workspace }}/External/NativeStatic/${{ matrix.rid }}/native + AVALONIA_VERSION: 11.3.14 + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET for Avalonia header generation + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "8.0.x" + + - name: Build static AvaloniaNative + run: bash ./scripts/build-macos-avalonia-native-static.sh + + - name: Build macOS static graphics libraries + run: bash ./scripts/build-macos-static-graphics.sh all + + - name: Build macOS font preinit object + run: bash ./scripts/build-macos-font-preinit.sh + + - name: List native outputs + run: find "External/NativeStatic/${{ matrix.rid }}/native" -maxdepth 1 -type f -print + + - name: Upload macOS native libraries + uses: actions/upload-artifact@v4 + with: + name: macos-static-native-${{ matrix.rid }} + path: External/NativeStatic/${{ matrix.rid }}/native/* + if-no-files-found: error + + pack-experimental: + runs-on: ubuntu-latest + needs: build-macos-native + outputs: + package_version: ${{ steps.version.outputs.package_version }} + steps: + - uses: actions/checkout@v4 + + - name: Download macOS native libraries + uses: actions/download-artifact@v4 + with: + pattern: macos-static-native-* + path: artifacts/native + + - name: Stage native libraries + run: | + mkdir -p External/NativeStatic/osx-x64/native External/NativeStatic/osx-arm64/native + cp artifacts/native/macos-static-native-osx-x64/* External/NativeStatic/osx-x64/native/ + cp artifacts/native/macos-static-native-osx-arm64/* External/NativeStatic/osx-arm64/native/ + find External/NativeStatic/osx-x64/native External/NativeStatic/osx-arm64/native -maxdepth 1 -type f -print + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + - name: Compute experimental package version + id: version + run: | + version="3.119.2-7151.8-macavn.${GITHUB_RUN_NUMBER}" + echo "package_version=$version" >> "$GITHUB_OUTPUT" + echo "$version" + + - name: Pack experimental NuGet package + run: | + dotnet pack NuGet/StaticGraphics/StaticLink.Avalonia.csproj \ + -c Release \ + -o artifacts/nuget \ + /p:PackageVersion=${{ steps.version.outputs.package_version }} \ + /p:Version=${{ steps.version.outputs.package_version }} + dotnet pack NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj \ + -c Release \ + -o artifacts/nuget \ + /p:PackageVersion=11.3.14-macavn.${{ github.run_number }} \ + /p:Version=11.3.14-macavn.${{ github.run_number }} + + - name: Upload experimental NuGet package + uses: actions/upload-artifact@v4 + with: + name: macos-static-avalonia-native-nuget + path: artifacts/nuget/*.nupkg + if-no-files-found: error + + smoke-macos: + strategy: + fail-fast: false + matrix: + include: + - rid: osx-x64 + runner: macos-14 + - rid: osx-arm64 + runner: macos-latest + runs-on: ${{ matrix.runner }} + needs: pack-experimental + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + - name: Download experimental NuGet package + uses: actions/download-artifact@v4 + with: + name: macos-static-avalonia-native-nuget + path: artifacts/nuget + + - name: Publish Avalonia smoke app + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $projectDir = Join-Path $PWD "artifacts/avalonia-smoke-src" + $nativePackage = Get-ChildItem -Path "artifacts/nuget" -Filter "StaticLink.Avalonia.Native.*.nupkg" | Select-Object -First 1 + if (-not $nativePackage) { throw "StaticLink AvaloniaNative NuGet package was not downloaded." } + if ($nativePackage.BaseName -notmatch '^StaticLink\.Avalonia\.Native\.(.+)$') { throw "Unexpected native package name: $($nativePackage.Name)" } + $nativeVersion = $Matches[1] + pwsh ./scripts/create-avalonia-smoke.ps1 ` + -ProjectDir $projectDir ` + -NuGetSource (Resolve-Path "artifacts/nuget") ` + -StaticLinkVersion "${{ needs.pack-experimental.outputs.package_version }}" ` + -StaticLinkNativeVersion $nativeVersion + dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") ` + -c Release ` + -r "${{ matrix.rid }}" ` + --configfile (Join-Path $projectDir "NuGet.config") ` + /p:PublishAot=true ` + /p:SelfContained=true ` + /p:PublishSingleFile=true ` + /p:StripSymbols=true ` + /p:PublishDir="$PWD/artifacts/smoke-${{ matrix.rid }}/" + + - name: Verify static macOS output + shell: pwsh + run: | + $publishDir = "artifacts/smoke-${{ matrix.rid }}" + $blocked = Get-ChildItem -Path $publishDir -Recurse -File | Where-Object { + $_.Name -in @("libAvaloniaNative.dylib", "libHarfBuzzSharp.dylib", "libSkiaSharp.dylib") + } + if ($blocked) { + $blocked | ForEach-Object { Write-Error "Unexpected dynamic library: $($_.FullName)" } + exit 1 + } + Get-ChildItem -Path $publishDir -Recurse -File | ForEach-Object { $_.FullName } + + - name: Run Avalonia smoke app + shell: bash + run: | + set -euo pipefail + cd "artifacts/smoke-${{ matrix.rid }}" + chmod +x ./AvaloniaStaticLinkSmoke + python3 - <<'PY' + import pathlib + import subprocess + import sys + + log = pathlib.Path("avalonia-smoke-run.log") + proc = subprocess.Popen( + ["./AvaloniaStaticLinkSmoke"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + + try: + output, _ = proc.communicate(timeout=15) + except subprocess.TimeoutExpired: + proc.kill() + output, _ = proc.communicate() + log.write_text(output or "") + print(output or "") + print("Avalonia smoke app stayed alive for 15 seconds.") + sys.exit(0) + + log.write_text(output or "") + print(output or "") + if proc.returncode == 0: + print("Avalonia smoke app exited normally.") + sys.exit(0) + + print(f"Avalonia smoke app failed with exit code {proc.returncode}.", file=sys.stderr) + sys.exit(proc.returncode if proc.returncode > 0 else 1) + PY + + - name: Upload macOS smoke app + uses: actions/upload-artifact@v4 + with: + name: macos-static-avalonia-native-smoke-${{ matrix.rid }} + path: artifacts/smoke-${{ matrix.rid }}/** + if-no-files-found: error + + release: + runs-on: ubuntu-latest + needs: + - pack-experimental + - smoke-macos + if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_to_release == true }} + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: release-artifacts + + - name: Prepare release files + run: | + mkdir -p release-files + find release-artifacts/macos-static-avalonia-native-nuget -type f -name '*.nupkg' -exec cp {} release-files/ \; + for dir in release-artifacts/macos-static-avalonia-native-smoke-*; do + [ -d "$dir" ] || continue + name="$(basename "$dir")" + (cd "$dir" && zip -r "../../release-files/$name.zip" .) + done + ls -la release-files + + - name: Publish experimental GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + release_tag="macos-static-avalonia-native-${GITHUB_RUN_NUMBER}" + if gh release view "$release_tag" >/dev/null 2>&1; then + gh release upload "$release_tag" release-files/* --clobber + else + gh release create "$release_tag" release-files/* \ + --title "macOS static AvaloniaNative ${GITHUB_RUN_NUMBER}" \ + --notes "Experimental macOS NativeAOT package with SkiaSharp, HarfBuzzSharp, and AvaloniaNative statically linked." + fi diff --git a/.github/workflows/nuget-static-graphics.yml b/.github/workflows/nuget-static-graphics.yml new file mode 100644 index 0000000..404c8eb --- /dev/null +++ b/.github/workflows/nuget-static-graphics.yml @@ -0,0 +1,493 @@ +name: staticlink-avalonia + +on: + workflow_dispatch: + inputs: + publish_to_nuget: + description: Publish package to nuget.org + required: true + default: false + type: boolean + publish_to_release: + description: Publish NuGet package and smoke apps to GitHub Release + required: true + default: true + type: boolean + push: + tags: + - "staticlink-avalonia-v*" + +env: + SKIASHARP_VERSION: "3.119.2" + ANGLE_BRANCH: "7151" + +jobs: + build-linux: + strategy: + fail-fast: false + matrix: + include: + - rid: linux-x64 + target_cpu: x64 + runner: ubuntu-latest + - rid: linux-arm64 + target_cpu: arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v4 + + - name: Install native build dependencies + run: | + sudo apt-get update + sudo apt-get install -y clang lld llvm g++ ninja-build pkg-config libfontconfig1-dev libglib2.0-dev libgl1-mesa-dev libpci-dev libx11-dev libxcb1-dev libxext-dev + + - name: Build Linux static graphics libraries + env: + RID: ${{ matrix.rid }} + TARGET_CPU: ${{ matrix.target_cpu }} + LLVM_AR: llvm-ar + run: scripts/build-linux-static-graphics.sh all + + - name: Upload Linux static archives + uses: actions/upload-artifact@v4 + with: + name: native-static-${{ matrix.rid }} + path: External/NativeStatic/${{ matrix.rid }}/* + if-no-files-found: error + + build-windows: + strategy: + fail-fast: false + matrix: + include: + - rid: win-x64 + target_cpu: x64 + msvc_arch: x64 + - rid: win-arm64 + target_cpu: arm64 + msvc_arch: amd64_arm64 + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + + - name: Setup MSVC environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.msvc_arch }} + + - name: Configure Chromium Visual Studio environment + shell: pwsh + run: | + "DEPOT_TOOLS_WIN_TOOLCHAIN=0" >> $env:GITHUB_ENV + "GYP_MSVS_VERSION=17.0" >> $env:GITHUB_ENV + "GYP_MSVS_OVERRIDE_PATH=$($env:VSINSTALLDIR.TrimEnd('\', '/'))" >> $env:GITHUB_ENV + + - name: Install native build dependencies + shell: pwsh + run: | + git config --global core.longpaths true + choco install ninja llvm -y + + - name: Validate Windows ANGLE inputs + shell: pwsh + env: + RID: ${{ matrix.rid }} + TARGET_CPU: ${{ matrix.target_cpu }} + DEPOT_TOOLS_WIN_TOOLCHAIN: 0 + GYP_MSVS_VERSION: 17.0 + run: .\scripts\build-windows-static-graphics.ps1 angle-preflight + + - name: Build Windows static graphics libraries + shell: pwsh + env: + RID: ${{ matrix.rid }} + TARGET_CPU: ${{ matrix.target_cpu }} + DEPOT_TOOLS_WIN_TOOLCHAIN: 0 + GYP_MSVS_VERSION: 17.0 + run: .\scripts\build-windows-static-graphics.ps1 all + + - name: Upload Windows static archives + uses: actions/upload-artifact@v4 + with: + name: native-static-${{ matrix.rid }} + path: External/NativeStatic/${{ matrix.rid }}/* + if-no-files-found: error + + build-macos: + strategy: + fail-fast: false + matrix: + include: + - rid: osx-x64 + target_cpu: x64 + runner: macos-14 + - rid: osx-arm64 + target_cpu: arm64 + runner: macos-latest + runs-on: ${{ matrix.runner }} + env: + RID: ${{ matrix.rid }} + TARGET_CPU: ${{ matrix.target_cpu }} + WORK_DIR: ${{ github.workspace }}/External/NativeStatic/.work + OUTPUT_DIR: ${{ github.workspace }}/External/NativeStatic/${{ matrix.rid }}/native + steps: + - uses: actions/checkout@v4 + + - name: Install native build dependencies + run: brew install ninja + + - name: Build macOS static graphics libraries + run: bash ./scripts/build-macos-static-graphics.sh all + + - name: Build macOS font preinit object + run: bash scripts/build-macos-font-preinit.sh + + - name: Upload macOS static archives + uses: actions/upload-artifact@v4 + with: + name: native-static-${{ matrix.rid }} + path: External/NativeStatic/${{ matrix.rid }}/* + if-no-files-found: error + + build-macos-avalonia-native: + strategy: + fail-fast: false + matrix: + include: + - rid: osx-x64 + runner: macos-14 + avalonia_version: 11.3.14 + - rid: osx-arm64 + runner: macos-latest + avalonia_version: 11.3.14 + - rid: osx-x64 + runner: macos-14 + avalonia_version: 12.0.3 + - rid: osx-arm64 + runner: macos-latest + avalonia_version: 12.0.3 + runs-on: ${{ matrix.runner }} + env: + RID: ${{ matrix.rid }} + WORK_DIR: ${{ github.workspace }}/External/NativeStatic/.work + OUTPUT_DIR: ${{ github.workspace }}/External/NativeStatic/${{ matrix.rid }}/native + AVALONIA_VERSION: ${{ matrix.avalonia_version }} + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET for Avalonia header generation + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 10.0.x + + - name: Build static AvaloniaNative + run: bash ./scripts/build-macos-avalonia-native-static.sh + + - name: Build macOS font preinit object + run: bash scripts/build-macos-font-preinit.sh + + - name: Upload static AvaloniaNative + uses: actions/upload-artifact@v4 + with: + name: native-avalonia-${{ matrix.avalonia_version }}-${{ matrix.rid }} + path: | + External/NativeStatic/${{ matrix.rid }}/native/libAvaloniaNative.a + External/NativeStatic/${{ matrix.rid }}/native/macos_font_preinit.o + if-no-files-found: error + + pack: + runs-on: ubuntu-latest + needs: + - build-linux + - build-windows + - build-macos + - build-macos-avalonia-native + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + - name: Download static archives + uses: actions/download-artifact@v4 + with: + pattern: native-static-* + path: External/NativeStatic + merge-multiple: false + + - name: Download static AvaloniaNative archives + uses: actions/download-artifact@v4 + with: + pattern: native-avalonia-* + path: External/AvaloniaNative + merge-multiple: false + + - name: Move static archives into RID directories + run: | + for rid in win-x64 win-arm64 linux-x64 linux-arm64 osx-x64 osx-arm64; do + mkdir -p "External/NativeStatic/$rid" + mv "External/NativeStatic/native-static-$rid"/* "External/NativeStatic/$rid/" + rmdir "External/NativeStatic/native-static-$rid" + if [ ! -d "External/NativeStatic/$rid/native" ]; then + mkdir -p "External/NativeStatic/$rid/native" + find "External/NativeStatic/$rid" -maxdepth 1 -type f -exec mv {} "External/NativeStatic/$rid/native/" \; + fi + done + + - name: Verify static archives + run: | + for rid in linux-x64 linux-arm64 osx-x64 osx-arm64; do + test -f External/NativeStatic/$rid/native/libskia.a + test -f External/NativeStatic/$rid/native/libSkiaSharp.a + test -f External/NativeStatic/$rid/native/libHarfBuzzSharp.a + test -f External/NativeStatic/$rid/native/libANGLE_static.a + test -f External/NativeStatic/$rid/native/libGLESv2_static.a + done + + for rid in win-x64 win-arm64; do + test -f External/NativeStatic/$rid/native/skia.lib + test -f External/NativeStatic/$rid/native/SkiaSharp.lib + test -f External/NativeStatic/$rid/native/libHarfBuzzSharp.lib + test -f External/NativeStatic/$rid/native/libANGLE_static.lib + test -f External/NativeStatic/$rid/native/libGLESv2_static.lib + done + + - name: Pack NuGet package + run: | + dotnet pack NuGet/StaticGraphics/StaticLink.Avalonia.csproj -c Release -o artifacts/nuget + for version in 11.3.14 12.0.3; do + mkdir -p External/NativeStatic/osx-x64/native External/NativeStatic/osx-arm64/native + cp External/AvaloniaNative/native-avalonia-$version-osx-x64/* External/NativeStatic/osx-x64/native/ + cp External/AvaloniaNative/native-avalonia-$version-osx-arm64/* External/NativeStatic/osx-arm64/native/ + dotnet pack NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj \ + -c Release \ + -o artifacts/nuget \ + /p:PackageVersion=$version.1 \ + /p:Version=$version.1 + done + + - name: Upload NuGet artifact + uses: actions/upload-artifact@v4 + with: + name: staticlink-avalonia-nuget + path: | + artifacts/nuget/*.nupkg + artifacts/nuget/*.snupkg + if-no-files-found: error + + - name: Publish to nuget.org + if: ${{ startsWith(github.ref, 'refs/tags/staticlink-avalonia-v') || (github.event_name == 'workflow_dispatch' && inputs.publish_to_nuget == true) }} + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: | + if [ -z "$NUGET_API_KEY" ]; then + echo "NUGET_API_KEY secret is not configured. Skipping publish." + exit 0 + fi + + for package in artifacts/nuget/*.nupkg; do + dotnet nuget push "$package" \ + --source "https://api.nuget.org/v3/index.json" \ + --api-key "$NUGET_API_KEY" \ + --skip-duplicate + done + + smoke-avalonia: + needs: pack + strategy: + fail-fast: false + matrix: + include: + - rid: win-x64 + runner: windows-2022 + msvc_arch: x64 + - rid: win-arm64 + runner: windows-2022 + msvc_arch: amd64_arm64 + - rid: linux-x64 + runner: ubuntu-latest + - rid: linux-arm64 + runner: ubuntu-24.04-arm + - rid: osx-x64 + runner: macos-14 + avalonia_version: 11.3.14 + native_version: 11.3.14.1 + - rid: osx-arm64 + runner: macos-latest + avalonia_version: 11.3.14 + native_version: 11.3.14.1 + - rid: osx-x64 + runner: macos-14 + avalonia_version: 12.0.3 + native_version: 12.0.3.1 + - rid: osx-arm64 + runner: macos-latest + avalonia_version: 12.0.3 + native_version: 12.0.3.1 + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v4 + + - name: Setup MSVC environment + if: ${{ startsWith(matrix.rid, 'win-') }} + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.msvc_arch }} + + - name: Install Linux publish dependencies + if: ${{ startsWith(matrix.rid, 'linux-') }} + run: | + sudo apt-get update + sudo apt-get install -y clang lld zlib1g-dev libfontconfig1-dev + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + - name: Download NuGet package + uses: actions/download-artifact@v4 + with: + name: staticlink-avalonia-nuget + path: artifacts/nuget + + - name: Publish Avalonia single-file smoke app + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $package = Get-ChildItem -Path "artifacts/nuget" -Filter "StaticLink.Avalonia.*.nupkg" | Where-Object { $_.Name -notlike "StaticLink.Avalonia.Native.*" } | Select-Object -First 1 + if (-not $package) { throw "StaticLink NuGet package was not downloaded." } + if ($package.BaseName -notmatch '^StaticLink\.Avalonia\.(.+)$') { throw "Unexpected package name: $($package.Name)" } + $version = $Matches[1] + $nativeVersion = "${{ matrix.native_version }}" + $avaloniaVersion = "${{ matrix.avalonia_version }}" + if (-not $avaloniaVersion) { $avaloniaVersion = "11.3.14" } + if ($nativeVersion) { + $nativePackage = Get-ChildItem -Path "artifacts/nuget" -Filter "StaticLink.Avalonia.Native.$nativeVersion.nupkg" | Select-Object -First 1 + if (-not $nativePackage) { throw "StaticLink AvaloniaNative NuGet package $nativeVersion was not downloaded." } + } + $projectDir = Join-Path $PWD "artifacts/avalonia-smoke-src" + pwsh ./scripts/create-avalonia-smoke.ps1 -ProjectDir $projectDir -NuGetSource (Resolve-Path "artifacts/nuget") -StaticLinkVersion $version -AvaloniaVersion $avaloniaVersion -StaticLinkNativeVersion $nativeVersion + dotnet publish (Join-Path $projectDir "AvaloniaStaticLinkSmoke.csproj") ` + -c Release ` + -r "${{ matrix.rid }}" ` + --configfile (Join-Path $projectDir "NuGet.config") ` + /p:PublishAot=true ` + /p:SelfContained=true ` + /p:PublishSingleFile=true ` + /p:StripSymbols=true ` + /p:PublishDir="$PWD/artifacts/smoke-${{ matrix.rid }}/" + + - name: Verify no dynamic graphics libraries remain + shell: pwsh + run: | + $publishDir = "artifacts/smoke-${{ matrix.rid }}" + $blocked = Get-ChildItem -Path $publishDir -Recurse -File | Where-Object { + $_.Name -in @("libSkiaSharp.dll", "libHarfBuzzSharp.dll", "av_libglesv2.dll", "libSkiaSharp.so", "libHarfBuzzSharp.so", "libAvaloniaNative.dylib", "libSkiaSharp.dylib", "libHarfBuzzSharp.dylib") + } + if ($blocked) { + $blocked | ForEach-Object { Write-Error "Unexpected dynamic graphics library: $($_.FullName)" } + exit 1 + } + Get-ChildItem -Path $publishDir -Recurse -File | ForEach-Object { $_.FullName } + + - name: Run macOS Avalonia smoke app + if: ${{ startsWith(matrix.rid, 'osx-') }} + shell: bash + run: | + set -euo pipefail + cd "artifacts/smoke-${{ matrix.rid }}" + chmod +x ./AvaloniaStaticLinkSmoke + python3 - <<'PY' + import pathlib + import subprocess + import sys + + log = pathlib.Path("avalonia-smoke-run.log") + proc = subprocess.Popen( + ["./AvaloniaStaticLinkSmoke"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + + try: + output, _ = proc.communicate(timeout=15) + except subprocess.TimeoutExpired: + proc.kill() + output, _ = proc.communicate() + log.write_text(output or "") + print(output or "") + print("Avalonia smoke app stayed alive for 15 seconds.") + sys.exit(0) + + log.write_text(output or "") + print(output or "") + if proc.returncode == 0: + print("Avalonia smoke app exited normally.") + sys.exit(0) + + print(f"Avalonia smoke app failed with exit code {proc.returncode}.", file=sys.stderr) + sys.exit(proc.returncode if proc.returncode > 0 else 1) + PY + + - name: Upload Avalonia smoke app + uses: actions/upload-artifact@v4 + with: + name: avalonia-smoke-${{ matrix.rid }} + path: artifacts/smoke-${{ matrix.rid }}/** + if-no-files-found: error + + release: + runs-on: ubuntu-latest + needs: + - pack + - smoke-avalonia + if: ${{ startsWith(github.ref, 'refs/tags/staticlink-avalonia-v') || (github.event_name == 'workflow_dispatch' && inputs.publish_to_release == true) }} + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Download release artifacts + uses: actions/download-artifact@v4 + with: + path: release-artifacts + + - name: Prepare release files + run: | + mkdir -p release-files + find release-artifacts/staticlink-avalonia-nuget -type f \( -name '*.nupkg' -o -name '*.snupkg' \) -exec cp {} release-files/ \; + for dir in release-artifacts/avalonia-smoke-*; do + [ -d "$dir" ] || continue + name="$(basename "$dir")" + (cd "$dir" && zip -r "../../release-files/$name.zip" .) + done + ls -la release-files + + - name: Publish GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then + release_tag="${GITHUB_REF_NAME}" + title="${GITHUB_REF_NAME}" + else + release_tag="staticlink-avalonia-smoke-${GITHUB_RUN_NUMBER}" + title="StaticLink.Avalonia smoke ${GITHUB_RUN_NUMBER}" + fi + + if gh release view "$release_tag" >/dev/null 2>&1; then + gh release upload "$release_tag" release-files/* --clobber + else + gh release create "$release_tag" release-files/* \ + --title "$title" \ + --notes "NuGet package plus Avalonia 11.3.14 NativeAOT single-file smoke apps for win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, and osx-arm64." + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6f0af6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +bin/ +obj/ +artifacts/ +*.nupkg +*.snupkg +External/NativeStatic/.work/ diff --git a/External/NativeStatic/README.md b/External/NativeStatic/README.md new file mode 100644 index 0000000..2fe5b82 --- /dev/null +++ b/External/NativeStatic/README.md @@ -0,0 +1,16 @@ +# Native Static Outputs + +GitHub Actions writes platform static libraries here before packing the NuGet package. + +Expected generated layout: + +```text +External/NativeStatic/win-x64/native inputs +External/NativeStatic/win-arm64/native inputs +External/NativeStatic/linux-x64/native inputs +External/NativeStatic/linux-arm64/native inputs +External/NativeStatic/osx-x64/native inputs +External/NativeStatic/osx-arm64/native inputs +``` + +The repository keeps build patches here, but platform `.a` and `.lib` outputs are generated by the workflow and are not source files. diff --git a/External/NativeStatic/patches/angle-chromium-7151.patch b/External/NativeStatic/patches/angle-chromium-7151.patch new file mode 100644 index 0000000..b99971c --- /dev/null +++ b/External/NativeStatic/patches/angle-chromium-7151.patch @@ -0,0 +1,64 @@ +diff --git a/DEPS b/DEPS +index 9085ba513e..565840a832 100644 +--- a/DEPS ++++ b/DEPS +@@ -696,11 +696,6 @@ deps = { + 'dep_type': 'cipd', + }, + +- 'third_party/catapult': { +- 'url': Var('chromium_git') + '/catapult.git' + '@' + Var('catapult_revision'), +- 'condition': 'not build_with_chromium', +- }, +- + # Cherry is a dEQP/VK-GL-CTS management GUI written in Go. We use it for viewing test results. + 'third_party/cherry': { + 'url': Var('android_git') + '/platform/external/cherry@4f8fb08d33ca5ff05a1c638f04c85bbb8d8b52cc', +@@ -722,12 +717,6 @@ deps = { + 'condition': 'checkout_android and not build_with_chromium', + }, + +- +- 'third_party/dawn': { +- 'url': Var('dawn_git') + '/dawn.git' + '@' + Var('dawn_revision'), +- 'condition': 'not build_with_chromium' +- }, +- + 'third_party/depot_tools': { + 'url': Var('chromium_git') + '/chromium/tools/depot_tools.git@1fcc527019d786502b02f71b8b764ee674a40953', + 'condition': 'not build_with_chromium', +@@ -809,11 +798,6 @@ deps = { + 'condition': 'not build_with_chromium', + }, + +- 'third_party/llvm/src': { +- 'url': Var('chromium_git') + '/external/github.com/llvm/llvm-project@1fd8d3fea53e6e4573cdce55bd38ef0a7813a442', +- 'condition': 'not build_with_chromium', +- }, +- + 'third_party/jdk': { + 'packages': [ + { +@@ -1000,11 +984,6 @@ deps = { + 'condition': 'checkout_android and not build_with_chromium', + }, + +- 'third_party/SwiftShader': { +- 'url': Var('swiftshader_git') + '/SwiftShader@7905fa19e456df5aa8e2233a7ec5832c9c6c287b', +- 'condition': 'not build_with_chromium', +- }, +- + 'third_party/turbine/cipd': { + 'packages': [ + { +@@ -1016,10 +995,6 @@ deps = { + 'dep_type': 'cipd', + }, + +- 'third_party/VK-GL-CTS/src': { +- 'url': Var('chromium_git') + '/external/github.com/KhronosGroup/VK-GL-CTS' + '@' + Var('vk_gl_cts_revision'), +- }, +- + 'third_party/vulkan-deps': { + 'url': Var('chromium_git') + '/vulkan-deps@b9aab6c35f37e6532011ddab976c3ca71819c074', + 'condition': 'not build_with_chromium', diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e318596 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 greepar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj b/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj new file mode 100644 index 0000000..06aef8c --- /dev/null +++ b/NuGet/AvaloniaNative/StaticLink.Avalonia.Native.csproj @@ -0,0 +1,32 @@ + + + net10.0 + true + true + false + false + StaticLink.Avalonia.Native + 11.3.14.1 + greepar + greepar + Static AvaloniaNative library for macOS Avalonia NativeAOT publishing. + Avalonia;AvaloniaNative;NativeAOT;macOS;Static + MIT + README.md + git + https://github.com/greepar/StaticLink.Avalonia + https://github.com/greepar/StaticLink.Avalonia + + + + + + + + + + + + + + diff --git a/NuGet/AvaloniaNative/buildTransitive/StaticLink.Avalonia.Native.MacOSFontWarmup.cs b/NuGet/AvaloniaNative/buildTransitive/StaticLink.Avalonia.Native.MacOSFontWarmup.cs new file mode 100644 index 0000000..1b6b382 --- /dev/null +++ b/NuGet/AvaloniaNative/buildTransitive/StaticLink.Avalonia.Native.MacOSFontWarmup.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace StaticLink.Avalonia.Native; + +internal static class MacOSFontWarmup +{ + [ModuleInitializer] + internal static void Initialize() + { + if (!OperatingSystem.IsMacOS()) + { + return; + } + + try + { + var nsApplication = objc_getClass("NSApplication"); + var sharedApplication = sel_registerName("sharedApplication"); + _ = objc_msgSend(nsApplication, sharedApplication); + + var nsFont = objc_getClass("NSFont"); + var systemFontOfSize = sel_registerName("systemFontOfSize:"); + var systemFontOfSizeWidth = sel_registerName("systemFontOfSize:width:"); + + _ = objc_msgSend(nsFont, systemFontOfSize, 13.0); + _ = objc_msgSend(nsFont, systemFontOfSizeWidth, 13.0, 0.0); + } + catch + { + // Defensive AppKit font-cache warmup must never fail application startup. + } + } + + [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_getClass")] + private static extern IntPtr objc_getClass([MarshalAs(UnmanagedType.LPUTF8Str)] string name); + + [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "sel_registerName")] + private static extern IntPtr sel_registerName([MarshalAs(UnmanagedType.LPUTF8Str)] string name); + + [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_msgSend")] + private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); + + [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_msgSend")] + private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, double value); + + [DllImport("/usr/lib/libobjc.A.dylib", EntryPoint = "objc_msgSend")] + private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector, double value, double width); +} diff --git a/NuGet/AvaloniaNative/buildTransitive/StaticLink.Avalonia.Native.props b/NuGet/AvaloniaNative/buildTransitive/StaticLink.Avalonia.Native.props new file mode 100644 index 0000000..6026106 --- /dev/null +++ b/NuGet/AvaloniaNative/buildTransitive/StaticLink.Avalonia.Native.props @@ -0,0 +1,5 @@ + + + $(MSBuildThisFileDirectory)../static/ + + diff --git a/NuGet/AvaloniaNative/buildTransitive/StaticLink.Avalonia.Native.targets b/NuGet/AvaloniaNative/buildTransitive/StaticLink.Avalonia.Native.targets new file mode 100644 index 0000000..cab1d51 --- /dev/null +++ b/NuGet/AvaloniaNative/buildTransitive/StaticLink.Avalonia.Native.targets @@ -0,0 +1,61 @@ + + + <_StaticLinkAvaloniaNativePackageRid Condition="'$(RuntimeIdentifier)' == 'osx-x64'">osx-x64 + <_StaticLinkAvaloniaNativePackageRid Condition="'$(RuntimeIdentifier)' == 'osx-arm64'">osx-arm64 + <_StaticLinkAvaloniaNativePackageRoot Condition="'$(_StaticLinkAvaloniaNativePackageRid)' != ''">$(StaticLinkAvaloniaNativeRoot)$(_StaticLinkAvaloniaNativePackageRid)/native + + + + + + + + + + + + + + + + + + + + + + + <_StaticLinkAvaloniaNativeDirectPInvokeList>$(NativeIntermediateOutputPath)staticlink-avalonia-native.directpinvokes.txt + + + <_StaticLinkAvaloniaNativeDirectPInvoke Include="libAvaloniaNative" /> + <_StaticLinkAvaloniaNativeDirectPInvoke Include="libAvaloniaNative.dylib" /> + <_StaticLinkAvaloniaNativeDirectPInvoke Include="AvaloniaNative" /> + + + + + + + + + + + + + + + + + + + + <_StaticLinkAvaloniaNativeFilesToDelete Include="$(PublishDir)libAvaloniaNative.dylib" /> + + + + diff --git a/NuGet/AvaloniaNative/lib/net10.0/_._ b/NuGet/AvaloniaNative/lib/net10.0/_._ new file mode 100644 index 0000000..e69de29 diff --git a/NuGet/StaticGraphics/README.md b/NuGet/StaticGraphics/README.md new file mode 100644 index 0000000..17b9e40 --- /dev/null +++ b/NuGet/StaticGraphics/README.md @@ -0,0 +1,68 @@ +# StaticLink.Avalonia + +Static native libraries for Avalonia NativeAOT publishing. + +This package links Avalonia's graphics/native dependencies from static archives so NativeAOT publish output does not need the usual SkiaSharp, HarfBuzzSharp, ANGLE, or AvaloniaNative runtime files next to the executable. + +Repository: + +## Supported RIDs + +- `win-x64` +- `win-arm64` +- `linux-x64` +- `linux-arm64` +- `osx-x64` +- `osx-arm64` + +## Install + +```bash +dotnet add package StaticLink.Avalonia +``` + +For fully static macOS output, also reference the AvaloniaNative package that matches your Avalonia version: + +```bash +dotnet add package StaticLink.Avalonia.Native --version 11.3.14.1 +``` + +## Publish + +```bash +dotnet publish -c Release -r win-x64 \ + -p:PublishAot=true \ + -p:SelfContained=true \ + -p:PublishSingleFile=true \ + -p:StripSymbols=true +``` + +Use the RID you need, for example `linux-x64`, `linux-arm64`, `osx-arm64`, or `osx-x64`. + +For Linux, `lld` is recommended: + +```bash +dotnet publish -c Release -r linux-x64 \ + -p:PublishAot=true \ + -p:SelfContained=true \ + -p:PublishSingleFile=true \ + -p:StripSymbols=true \ + -p:LinkerFlavor=lld +``` + +For macOS, avoid the Metal renderer for fully static output. Use OpenGL or Software: + +```csharp +.With(new AvaloniaNativePlatformOptions +{ + RenderingMode = [AvaloniaNativeRenderingMode.OpenGl, AvaloniaNativeRenderingMode.Software] +}) +``` + +## Notes + +- The package is intended for Avalonia `11.3.x` NativeAOT apps. +- `StaticLink.Avalonia.Native` is version-specific because `libAvaloniaNative.a` must match Avalonia's native ABI. +- The CI builds and smoke-publishes all six supported RIDs. +- macOS smoke tests run the published app on GitHub Actions and require it to stay alive for 15 seconds. +- Other native dependencies used by your app, such as SQLite or audio libraries, are outside this package's scope. diff --git a/NuGet/StaticGraphics/StaticLink.Avalonia.csproj b/NuGet/StaticGraphics/StaticLink.Avalonia.csproj new file mode 100644 index 0000000..1c55e70 --- /dev/null +++ b/NuGet/StaticGraphics/StaticLink.Avalonia.csproj @@ -0,0 +1,37 @@ + + + net10.0 + true + true + false + false + StaticLink.Avalonia + 3.119.2-7151.10 + greepar + greepar + Static SkiaSharp, HarfBuzzSharp, and ANGLE libraries for Avalonia NativeAOT single-file publishing. + Avalonia;SkiaSharp;ANGLE;NativeAOT;Windows;Linux;macOS;Static + MIT + README.md + git + https://github.com/greepar/StaticLink.Avalonia + https://github.com/greepar/StaticLink.Avalonia + + + + + + + + + + + + + + + + + + + diff --git a/NuGet/StaticGraphics/buildTransitive/StaticLink.Avalonia.props b/NuGet/StaticGraphics/buildTransitive/StaticLink.Avalonia.props new file mode 100644 index 0000000..61a7c36 --- /dev/null +++ b/NuGet/StaticGraphics/buildTransitive/StaticLink.Avalonia.props @@ -0,0 +1,5 @@ + + + $(MSBuildThisFileDirectory)../static/ + + diff --git a/NuGet/StaticGraphics/buildTransitive/StaticLink.Avalonia.targets b/NuGet/StaticGraphics/buildTransitive/StaticLink.Avalonia.targets new file mode 100644 index 0000000..5024f27 --- /dev/null +++ b/NuGet/StaticGraphics/buildTransitive/StaticLink.Avalonia.targets @@ -0,0 +1,118 @@ + + + <_StaticLinkAvaloniaRid Condition="'$(RuntimeIdentifier)' == 'win-x64'">win-x64 + <_StaticLinkAvaloniaRid Condition="'$(RuntimeIdentifier)' == 'win-arm64'">win-arm64 + <_StaticLinkAvaloniaRid Condition="'$(RuntimeIdentifier)' == 'linux-x64'">linux-x64 + <_StaticLinkAvaloniaRid Condition="'$(RuntimeIdentifier)' == 'linux-arm64'">linux-arm64 + <_StaticLinkAvaloniaRid Condition="'$(RuntimeIdentifier)' == 'osx-x64'">osx-x64 + <_StaticLinkAvaloniaRid Condition="'$(RuntimeIdentifier)' == 'osx-arm64'">osx-arm64 + <_StaticLinkAvaloniaNativeRoot Condition="'$(_StaticLinkAvaloniaRid)' != ''">$(StaticLinkAvaloniaRoot)$(_StaticLinkAvaloniaRid)/native + lld + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + <_StaticLinkAvaloniaFilesToDelete Include="$(PublishDir)av_libglesv2.dll" /> + <_StaticLinkAvaloniaFilesToDelete Include="$(PublishDir)libHarfBuzzSharp.dll" /> + <_StaticLinkAvaloniaFilesToDelete Include="$(PublishDir)libSkiaSharp.dll" /> + <_StaticLinkAvaloniaFilesToDelete Include="$(PublishDir)libHarfBuzzSharp.so" /> + <_StaticLinkAvaloniaFilesToDelete Include="$(PublishDir)libSkiaSharp.so" /> + <_StaticLinkAvaloniaFilesToDelete Include="$(PublishDir)libHarfBuzzSharp.dylib" /> + <_StaticLinkAvaloniaFilesToDelete Include="$(PublishDir)libSkiaSharp.dylib" /> + + + + diff --git a/NuGet/StaticGraphics/lib/net10.0/_._ b/NuGet/StaticGraphics/lib/net10.0/_._ new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..17b9e40 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# StaticLink.Avalonia + +Static native libraries for Avalonia NativeAOT publishing. + +This package links Avalonia's graphics/native dependencies from static archives so NativeAOT publish output does not need the usual SkiaSharp, HarfBuzzSharp, ANGLE, or AvaloniaNative runtime files next to the executable. + +Repository: + +## Supported RIDs + +- `win-x64` +- `win-arm64` +- `linux-x64` +- `linux-arm64` +- `osx-x64` +- `osx-arm64` + +## Install + +```bash +dotnet add package StaticLink.Avalonia +``` + +For fully static macOS output, also reference the AvaloniaNative package that matches your Avalonia version: + +```bash +dotnet add package StaticLink.Avalonia.Native --version 11.3.14.1 +``` + +## Publish + +```bash +dotnet publish -c Release -r win-x64 \ + -p:PublishAot=true \ + -p:SelfContained=true \ + -p:PublishSingleFile=true \ + -p:StripSymbols=true +``` + +Use the RID you need, for example `linux-x64`, `linux-arm64`, `osx-arm64`, or `osx-x64`. + +For Linux, `lld` is recommended: + +```bash +dotnet publish -c Release -r linux-x64 \ + -p:PublishAot=true \ + -p:SelfContained=true \ + -p:PublishSingleFile=true \ + -p:StripSymbols=true \ + -p:LinkerFlavor=lld +``` + +For macOS, avoid the Metal renderer for fully static output. Use OpenGL or Software: + +```csharp +.With(new AvaloniaNativePlatformOptions +{ + RenderingMode = [AvaloniaNativeRenderingMode.OpenGl, AvaloniaNativeRenderingMode.Software] +}) +``` + +## Notes + +- The package is intended for Avalonia `11.3.x` NativeAOT apps. +- `StaticLink.Avalonia.Native` is version-specific because `libAvaloniaNative.a` must match Avalonia's native ABI. +- The CI builds and smoke-publishes all six supported RIDs. +- macOS smoke tests run the published app on GitHub Actions and require it to stay alive for 15 seconds. +- Other native dependencies used by your app, such as SQLite or audio libraries, are outside this package's scope. diff --git a/scripts/build-linux-static-graphics.sh b/scripts/build-linux-static-graphics.sh new file mode 100755 index 0000000..d05e333 --- /dev/null +++ b/scripts/build-linux-static-graphics.sh @@ -0,0 +1,329 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WORK_DIR="${WORK_DIR:-$ROOT_DIR/External/NativeStatic/.work}" +TARGET_CPU="${TARGET_CPU:-x64}" +RID="${RID:-linux-$TARGET_CPU}" +OUTPUT_DIR="${OUTPUT_DIR:-$ROOT_DIR/External/NativeStatic/$RID}" +SKIASHARP_VERSION="${SKIASHARP_VERSION:-3.119.2}" +ANGLE_BRANCH="${ANGLE_BRANCH:-7151}" +BUILD_JOBS="${BUILD_JOBS:-$(nproc)}" +ANGLE_PATCH_DIR="${ANGLE_PATCH_DIR:-$ROOT_DIR/External/NativeStatic/patches}" +LLVM_AR="${LLVM_AR:-llvm-ar-19}" +SKIA_DEPS_RETRIES="${SKIA_DEPS_RETRIES:-3}" + +usage() { + cat <<'USAGE' +Usage: scripts/build-linux-static-graphics.sh [skia|angle|all] + +Environment: + WORK_DIR Source/build cache directory. Default: External/NativeStatic/.work + OUTPUT_DIR Final static library directory. Default: External/NativeStatic/linux-$TARGET_CPU + SKIASHARP_VERSION SkiaSharp release branch version. Default: 3.119.2 + ANGLE_BRANCH ANGLE chromium branch. Default: 7151 + TARGET_CPU GN target_cpu. Default: x64. Supported: x64, arm64 + RID Output RID. Default: linux-$TARGET_CPU + BUILD_JOBS Ninja parallelism. Default: nproc + ANGLE_PATCH_DIR ANGLE patch directory. Default: External/NativeStatic/patches + LLVM_AR llvm-ar command used to expand thin archives. Default: llvm-ar-19 +USAGE +} + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required command: $1" >&2 + exit 1 + fi +} + +ensure_tools() { + require_cmd git + require_cmd python3 + require_cmd clang + require_cmd clang++ + require_cmd ar + require_cmd "$LLVM_AR" + require_cmd ninja + require_cmd pkg-config +} + +ensure_depot_tools() { + local depot_dir="$WORK_DIR/depot_tools" + if [[ ! -d "$depot_dir/.git" ]]; then + git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git "$depot_dir" + else + git -C "$depot_dir" pull --ff-only + fi + export PATH="$depot_dir:$PATH" +} + +sync_skiasharp() { + local src="$WORK_DIR/SkiaSharp-$SKIASHARP_VERSION" + if [[ ! -d "$src/.git" ]]; then + git clone --depth 1 --branch "release/$SKIASHARP_VERSION" https://github.com/mono/SkiaSharp.git "$src" + else + git -C "$src" fetch --depth 1 origin "release/$SKIASHARP_VERSION" + git -C "$src" checkout -q FETCH_HEAD + fi + git -C "$src" submodule update --init --depth 1 externals/skia >&2 + echo "$src" +} + +prepare_skia_git_sync_deps() { + local sync_deps="$1/tools/git-sync-deps" + python3 - "$sync_deps" <<'PY' +import pathlib +import sys + +path = pathlib.Path(sys.argv[1]) +text = path.read_text() +old = " multithread(git_checkout_to_directory, list_of_arg_lists)" +new = " for args in list_of_arg_lists:\n git_checkout_to_directory(*args)" +if old in text: + path.write_text(text.replace(old, new)) +PY +} + +sync_skia_deps() { + local skia_dir="$1/externals/skia" + if [[ ! -x "$skia_dir/bin/gn" ]]; then + prepare_skia_git_sync_deps "$skia_dir" + + local attempt + for attempt in $(seq 1 "$SKIA_DEPS_RETRIES"); do + if python3 "$skia_dir/tools/git-sync-deps"; then + return 0 + fi + if [[ "$attempt" == "$SKIA_DEPS_RETRIES" ]]; then + return 1 + fi + echo "git-sync-deps failed; retrying ($attempt/$SKIA_DEPS_RETRIES)..." >&2 + sleep 10 + done + fi +} + +build_skia() { + ensure_tools + ensure_depot_tools + local src + src="$(sync_skiasharp)" + sync_skia_deps "$src" + + local skia_dir="$src/externals/skia" + local out_dir="$skia_dir/out/linux-static-$TARGET_CPU" + mkdir -p "$out_dir" "$OUTPUT_DIR" + + cat >"$out_dir/args.gn" <"$out_dir/args.gn" </dev/null 2>&1; then + : + elif [[ -f "$deps_patch" ]]; then + git -C "$src" apply "$deps_patch" + fi +} + +copy_first_existing() { + local dest="$1" + shift + for src in "$@"; do + if [[ -f "$src" ]]; then + cp "$src" "$dest" + echo "Wrote $dest" + return 0 + fi + done + echo "None of the expected files exist for $dest:" >&2 + printf ' %s\n' "$@" >&2 + return 1 +} + +copy_full_archive_from_thin() { + local base_dir="$1" + local dest="$2" + shift 2 + + local src + for src in "$@"; do + if [[ -f "$src" ]]; then + if [[ "$(head -c 7 "$src")" == "!" ]]; then + local tmp="$dest.tmp" + local rel_src="${src#$base_dir/}" + ( + cd "$base_dir" + mapfile -t members < <("$LLVM_AR" t "$rel_src") + "$LLVM_AR" rcs "$tmp" "${members[@]}" + ) + mv "$tmp" "$dest" + else + cp "$src" "$dest" + fi + echo "Wrote $dest" + return 0 + fi + done + + echo "None of the expected files exist for $dest:" >&2 + printf ' %s\n' "$@" >&2 + return 1 +} + +main() { + mkdir -p "$WORK_DIR" "$OUTPUT_DIR" + case "${1:-all}" in + skia) build_skia ;; + angle) build_angle ;; + all) + build_skia + build_angle + ;; + -h|--help|help) + usage + ;; + *) + usage >&2 + exit 1 + ;; + esac +} + +main "$@" diff --git a/scripts/build-macos-avalonia-native-static.sh b/scripts/build-macos-avalonia-native-static.sh new file mode 100644 index 0000000..d32833d --- /dev/null +++ b/scripts/build-macos-avalonia-native-static.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +RID="${RID:-osx-arm64}" +AVALONIA_VERSION="${AVALONIA_VERSION:-11.3.14}" +WORK_DIR="${WORK_DIR:-$PWD/External/NativeStatic/.work}" +OUTPUT_DIR="${OUTPUT_DIR:-$PWD/External/NativeStatic/$RID/native}" +BUILD_JOBS="${BUILD_JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || echo 4)}" + +case "$RID" in + osx-arm64) ARCH="arm64" ;; + osx-x64) ARCH="x86_64" ;; + *) echo "Unsupported RID: $RID" >&2; exit 2 ;; +esac + +mkdir -p "$WORK_DIR" "$OUTPUT_DIR" + +src="$WORK_DIR/Avalonia-$AVALONIA_VERSION" +if [[ ! -d "$src/.git" ]]; then + rm -rf "$src" + git clone --depth 1 --branch "$AVALONIA_VERSION" https://github.com/AvaloniaUI/Avalonia.git "$src" +fi + +if git -C "$src" config -f .gitmodules --get-regexp path | grep -q 'external/Numerge' && [[ ! -d "$src/external/Numerge/.git" ]]; then + git -C "$src" submodule update --init --depth 1 external/Numerge +fi + +if [[ ! -f "$src/native/Avalonia.Native/inc/avalonia-native.h" ]]; then + bash "$src/native/Avalonia.Native/generate-headers.sh" +fi + +project="$src/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj" +include_dir="$src/native/Avalonia.Native/inc" +build_dir="$WORK_DIR/avalonia-native-$RID" +rm -rf "$build_dir" +mkdir -p "$build_dir" + +xcodebuild \ + -project "$project" \ + -target Avalonia.Native.OSX \ + -configuration Release \ + -sdk macosx \ + -arch "$ARCH" \ + CONFIGURATION_BUILD_DIR="$build_dir" \ + ONLY_ACTIVE_ARCH=NO \ + CODE_SIGNING_ALLOWED=NO \ + MACH_O_TYPE=staticlib \ + EXECUTABLE_PREFIX=lib \ + EXECUTABLE_EXTENSION=a \ + PRODUCT_NAME=AvaloniaNative \ + HEADER_SEARCH_PATHS="$include_dir" \ + CLANG_ENABLE_MODULES=YES \ + GCC_GENERATE_DEBUGGING_SYMBOLS=NO \ + -jobs "$BUILD_JOBS" + +candidate="$(find "$build_dir" -maxdepth 2 -type f -name 'libAvaloniaNative.a' -print -quit)" +if [[ -z "$candidate" ]]; then + echo "Failed to find libAvaloniaNative.a in $build_dir" >&2 + find "$build_dir" -maxdepth 3 -type f -print >&2 + exit 1 +fi + +cp "$candidate" "$OUTPUT_DIR/libAvaloniaNative.a" +echo "Wrote $OUTPUT_DIR/libAvaloniaNative.a" diff --git a/scripts/build-macos-font-preinit.sh b/scripts/build-macos-font-preinit.sh new file mode 100644 index 0000000..36abbd3 --- /dev/null +++ b/scripts/build-macos-font-preinit.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +RID="${RID:-osx-arm64}" +OUTPUT_DIR="${OUTPUT_DIR:-$PWD/External/NativeStatic/$RID/native}" + +case "$RID" in + osx-arm64) ARCH="arm64" ;; + osx-x64) ARCH="x86_64" ;; + *) echo "Unsupported RID: $RID" >&2; exit 2 ;; +esac + +mkdir -p "$OUTPUT_DIR" + +src="$OUTPUT_DIR/macos_font_preinit.m" +cat > "$src" <<'OBJC' +#import +#import +#import + +__attribute__((constructor(101))) +static void StaticLinkAvaloniaPreinitializeAppKitFonts(void) +{ + @autoreleasepool + { + [NSApplication sharedApplication]; + [NSFont systemFontOfSize:13.0]; + ((id (*)(id, SEL, CGFloat, CGFloat))objc_msgSend)([NSFont class], sel_registerName("systemFontOfSize:width:"), 13.0, 0.0); + } +} +OBJC + +clang -arch "$ARCH" \ + -mmacosx-version-min=12.0 \ + -fobjc-arc \ + -c "$src" \ + -o "$OUTPUT_DIR/macos_font_preinit.o" + +rm -f "$src" +echo "Wrote $OUTPUT_DIR/macos_font_preinit.o" diff --git a/scripts/build-macos-static-graphics.sh b/scripts/build-macos-static-graphics.sh new file mode 100755 index 0000000..fbee727 --- /dev/null +++ b/scripts/build-macos-static-graphics.sh @@ -0,0 +1,240 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WORK_DIR="${WORK_DIR:-$ROOT_DIR/External/NativeStatic/.work}" +TARGET_CPU="${TARGET_CPU:-arm64}" +RID="${RID:-osx-$TARGET_CPU}" +OUTPUT_DIR="${OUTPUT_DIR:-$ROOT_DIR/External/NativeStatic/$RID}" +SKIASHARP_VERSION="${SKIASHARP_VERSION:-3.119.2}" +ANGLE_BRANCH="${ANGLE_BRANCH:-7151}" +BUILD_JOBS="${BUILD_JOBS:-$(sysctl -n hw.ncpu)}" +ANGLE_PATCH_DIR="${ANGLE_PATCH_DIR:-$ROOT_DIR/External/NativeStatic/patches}" +SKIA_DEPS_RETRIES="${SKIA_DEPS_RETRIES:-3}" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required command: $1" >&2 + exit 1 + fi +} + +ensure_tools() { + require_cmd git + require_cmd python3 + require_cmd clang + require_cmd clang++ + require_cmd ar + require_cmd ninja +} + +ensure_depot_tools() { + local depot_dir="$WORK_DIR/depot_tools" + if [[ ! -d "$depot_dir/.git" ]]; then + git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git "$depot_dir" + else + git -C "$depot_dir" pull --ff-only + fi + export PATH="$depot_dir:$PATH" +} + +copy_first_existing() { + local dest="$1" + shift + for src in "$@"; do + if [[ -f "$src" ]]; then + cp "$src" "$dest" + echo "Wrote $dest" + return 0 + fi + done + echo "None of the expected files exist for $dest:" >&2 + printf ' %s\n' "$@" >&2 + return 1 +} + +sync_skiasharp() { + local src="$WORK_DIR/SkiaSharp-$SKIASHARP_VERSION" + if [[ ! -d "$src/.git" ]]; then + git clone --depth 1 --branch "release/$SKIASHARP_VERSION" https://github.com/mono/SkiaSharp.git "$src" + else + git -C "$src" fetch --depth 1 origin "release/$SKIASHARP_VERSION" + git -C "$src" checkout -q FETCH_HEAD + fi + git -C "$src" submodule update --init --depth 1 externals/skia >&2 + echo "$src" +} + +prepare_skia_git_sync_deps() { + local sync_deps="$1/tools/git-sync-deps" + python3 - "$sync_deps" <<'PY' +import pathlib +import sys + +path = pathlib.Path(sys.argv[1]) +text = path.read_text() +old = " multithread(git_checkout_to_directory, list_of_arg_lists)" +new = " for args in list_of_arg_lists:\n git_checkout_to_directory(*args)" +if old in text: + path.write_text(text.replace(old, new)) +PY +} + +sync_skia_deps() { + local skia_dir="$1" + prepare_skia_git_sync_deps "$skia_dir" + + local attempt + for attempt in $(seq 1 "$SKIA_DEPS_RETRIES"); do + if python3 "$skia_dir/tools/git-sync-deps"; then + return 0 + fi + if [[ "$attempt" == "$SKIA_DEPS_RETRIES" ]]; then + return 1 + fi + echo "git-sync-deps failed; retrying ($attempt/$SKIA_DEPS_RETRIES)..." >&2 + sleep 10 + done +} + +build_skia() { + ensure_tools + ensure_depot_tools + local src + src="$(sync_skiasharp)" + local skia_dir="$src/externals/skia" + if [[ ! -x "$skia_dir/bin/gn" ]]; then + sync_skia_deps "$skia_dir" + fi + + local out_dir="$skia_dir/out/mac-static-$TARGET_CPU" + mkdir -p "$out_dir" "$OUTPUT_DIR" + cat >"$out_dir/args.gn" <"$out_dir/args.gn" <&2; exit 1 ;; + esac +} + +main "$@" diff --git a/scripts/build-windows-static-graphics.ps1 b/scripts/build-windows-static-graphics.ps1 new file mode 100644 index 0000000..120e86d --- /dev/null +++ b/scripts/build-windows-static-graphics.ps1 @@ -0,0 +1,290 @@ +param( + [ValidateSet("skia", "angle", "angle-preflight", "all")] + [string]$Target = "all" +) + +$ErrorActionPreference = "Stop" + +$RootDir = Split-Path -Parent $PSScriptRoot +$WorkDir = if ($env:WORK_DIR) { $env:WORK_DIR } else { Join-Path $RootDir "External\NativeStatic\.work" } +$SkiaSharpVersion = if ($env:SKIASHARP_VERSION) { $env:SKIASHARP_VERSION } else { "3.119.2" } +$AngleBranch = if ($env:ANGLE_BRANCH) { $env:ANGLE_BRANCH } else { "7151" } +$TargetCpu = if ($env:TARGET_CPU) { $env:TARGET_CPU } else { "x64" } +$Rid = if ($env:RID) { $env:RID } else { "win-$TargetCpu" } +$OutputDir = if ($env:OUTPUT_DIR) { $env:OUTPUT_DIR } else { Join-Path $RootDir "External\NativeStatic\$Rid" } +$BuildJobs = if ($env:BUILD_JOBS) { $env:BUILD_JOBS } else { [Environment]::ProcessorCount } +$AnglePatchDir = if ($env:ANGLE_PATCH_DIR) { $env:ANGLE_PATCH_DIR } else { Join-Path $RootDir "External\NativeStatic\patches" } +$SkiaDepsRetries = if ($env:SKIA_DEPS_RETRIES) { [int]$env:SKIA_DEPS_RETRIES } else { 3 } + +if (-not $env:DEPOT_TOOLS_WIN_TOOLCHAIN) { + $env:DEPOT_TOOLS_WIN_TOOLCHAIN = "0" +} +if ($env:DEPOT_TOOLS_WIN_TOOLCHAIN -eq "0") { + if (-not $env:GYP_MSVS_VERSION) { + $env:GYP_MSVS_VERSION = "17.0" + } + if (-not $env:GYP_MSVS_OVERRIDE_PATH) { + if ($env:VSINSTALLDIR) { + $env:GYP_MSVS_OVERRIDE_PATH = $env:VSINSTALLDIR.TrimEnd("\", "/") + } else { + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + if (Test-Path $vswhere) { + $vsPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if ($vsPath) { + $env:GYP_MSVS_OVERRIDE_PATH = $vsPath.TrimEnd("\", "/") + } + } + } + } +} + +function Require-Command($Name) { + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + throw "Missing required command: $Name" + } +} + +function Ensure-Tools { + Require-Command git + Require-Command python + Require-Command ninja + git config --global core.longpaths true +} + +function Ensure-DepotTools { + $depotDir = Join-Path $WorkDir "depot_tools" + if (-not (Test-Path (Join-Path $depotDir ".git"))) { + $null = git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git $depotDir + } else { + $null = git -C $depotDir pull --ff-only + } + $env:PATH = "$depotDir;$env:PATH" +} + +function Copy-FirstExisting($Destination, [string[]]$Candidates) { + foreach ($candidate in $Candidates) { + if (Test-Path $candidate) { + Copy-Item $candidate $Destination -Force + Write-Host "Wrote $Destination" + return + } + } + throw "None of the expected files exist for ${Destination}: $($Candidates -join ', ')" +} + +function Sync-SkiaSharp { + $src = Join-Path $WorkDir "SkiaSharp-$SkiaSharpVersion" + if (-not (Test-Path (Join-Path $src ".git"))) { + $null = git -c core.longpaths=true clone --depth 1 --branch "release/$SkiaSharpVersion" https://github.com/mono/SkiaSharp.git $src + } else { + $null = git -C $src fetch --depth 1 origin "release/$SkiaSharpVersion" + $null = git -C $src checkout -q FETCH_HEAD + } + $null = git -C $src submodule update --init --depth 1 externals/skia + return $src +} + +function Prepare-SkiaGitSyncDeps($SkiaDir) { + $syncDeps = Join-Path $SkiaDir "tools\git-sync-deps" + $text = Get-Content -Path $syncDeps -Raw + $old = " multithread(git_checkout_to_directory, list_of_arg_lists)" + $new = " for args in list_of_arg_lists:`n git_checkout_to_directory(*args)" + if ($text.Contains($old)) { + Set-Content -Path $syncDeps -Value $text.Replace($old, $new) -NoNewline -Encoding UTF8 + } +} + +function Invoke-SkiaGitSyncDeps($SkiaDir) { + Prepare-SkiaGitSyncDeps $SkiaDir + + for ($attempt = 1; $attempt -le $SkiaDepsRetries; $attempt++) { + python (Join-Path $SkiaDir "tools\git-sync-deps") + if ($LASTEXITCODE -eq 0) { + return + } + if ($attempt -eq $SkiaDepsRetries) { + throw "git-sync-deps failed after $SkiaDepsRetries attempts" + } + Write-Warning "git-sync-deps failed; retrying ($attempt/$SkiaDepsRetries)..." + Start-Sleep -Seconds 10 + } +} + +function Build-Skia { + Ensure-Tools + Ensure-DepotTools + $src = Sync-SkiaSharp + $skiaDir = Join-Path $src "externals\skia" + if (-not (Test-Path (Join-Path $skiaDir "bin\gn.exe"))) { + Invoke-SkiaGitSyncDeps $skiaDir + } + + $outDir = Join-Path $skiaDir "out\win-static-$TargetCpu" + New-Item -ItemType Directory -Path $outDir -Force | Out-Null + New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null + @" +target_os = "win" +target_cpu = "$TargetCpu" +is_official_build = true +is_static_skiasharp = true +is_clang = true +skia_enable_tools = false +skia_enable_ganesh = true +skia_enable_pdf = false +skia_enable_skottie = false +skia_use_dng_sdk = false +skia_use_fontconfig = false +skia_use_freetype = false +skia_use_harfbuzz = false +skia_use_icu = false +skia_use_piex = false +skia_use_sfntly = false +skia_use_system_expat = false +skia_use_system_freetype2 = false +skia_use_system_libjpeg_turbo = false +skia_use_system_libpng = false +skia_use_system_libwebp = false +skia_use_system_zlib = false +skia_use_vulkan = false +skia_use_xps = false +extra_cflags = [ "-DSKIA_C_DLL" ] +extra_cflags_cc = [ "/GR" ] +"@ | Set-Content -Path (Join-Path $outDir "args.gn") -Encoding ASCII + + Push-Location $skiaDir + try { + & (Join-Path $skiaDir "bin\gn.exe") gen $outDir + ninja -C $outDir -j $BuildJobs skia SkiaSharp HarfBuzzSharp + } finally { + Pop-Location + } + + Copy-FirstExisting (Join-Path $OutputDir "skia.lib") @((Join-Path $outDir "skia.lib"), (Join-Path $outDir "obj\skia.lib")) + Copy-FirstExisting (Join-Path $OutputDir "SkiaSharp.lib") @((Join-Path $outDir "SkiaSharp.lib"), (Join-Path $outDir "obj\SkiaSharp.lib")) + Copy-FirstExisting (Join-Path $OutputDir "libHarfBuzzSharp.lib") @( + (Join-Path $outDir "libHarfBuzzSharp.lib"), + (Join-Path $outDir "HarfBuzzSharp.lib"), + (Join-Path $outDir "obj\libHarfBuzzSharp.lib"), + (Join-Path $outDir "obj\HarfBuzzSharp.lib"), + (Join-Path $outDir "obj\HarfBuzzSharp\libHarfBuzzSharp.lib"), + (Join-Path $outDir "obj\HarfBuzzSharp\HarfBuzzSharp.lib") + ) +} + +function Sync-Angle { + $src = Join-Path $WorkDir "ANGLE-$AngleBranch" + if (-not (Test-Path (Join-Path $src ".git"))) { + $null = git -c core.longpaths=true clone --depth 1 --branch "chromium/$AngleBranch" https://github.com/google/angle.git $src + } else { + $null = git -C $src fetch --depth 1 origin "chromium/$AngleBranch" + $null = git -C $src checkout -q FETCH_HEAD + } + return $src +} + +function Apply-AnglePatches($Src) { + $buildFile = Join-Path $Src "BUILD.gn" + $buildText = Get-Content -Path $buildFile -Raw + if (-not $buildText.Contains('angle_static_library("libANGLE_static")')) { + $libAngleTargets = @' +angle_static_library("libANGLE_static") { + complete_static_lib = true + public_deps = [ ":libANGLE" ] +} + +angle_static_library("libANGLE_with_capture_static") { + complete_static_lib = true + public_deps = [ ":libANGLE_with_capture" ] +} + +angle_static_library("libGLESv2_static") { +'@ + $buildText = [regex]::Replace($buildText, '(?m)^angle_static_library\("libGLESv2_static"\) \{', $libAngleTargets) + $buildText = [regex]::Replace($buildText, '(?m)^angle_static_library\("libGLESv2_static"\) \{\r?\n sources = libglesv2_sources', "angle_static_library(`"libGLESv2_static`") {`n complete_static_lib = true`n sources = libglesv2_sources") + Set-Content -Path $buildFile -Value $buildText -NoNewline -Encoding UTF8 + } + + $patch = Join-Path $AnglePatchDir "angle-chromium-$AngleBranch.patch" + $depsFile = Join-Path $Src "DEPS" + if ((Test-Path $patch) -and (Select-String -Path $depsFile -Pattern "'third_party/catapult'" -Quiet)) { + $null = git -C $Src apply $patch + if ($LASTEXITCODE -ne 0) { + throw "Failed to apply ANGLE patch: $patch" + } + } +} + +function Assert-AngleVisualStudioVersion { + if ($env:VisualStudioVersion -and -not ($env:VisualStudioVersion -in @("17.0", "16.0", "15.0"))) { + throw "ANGLE $AngleBranch requires Visual Studio 2022/2019/2017, but VisualStudioVersion is $env:VisualStudioVersion. Use windows-2022 or a VS 2022 developer prompt." + } + if ($env:GYP_MSVS_OVERRIDE_PATH -and ($env:GYP_MSVS_OVERRIDE_PATH -match '\\Microsoft Visual Studio\\18\\')) { + throw "ANGLE $AngleBranch requires Visual Studio 2022/2019/2017, but GYP_MSVS_OVERRIDE_PATH points to $env:GYP_MSVS_OVERRIDE_PATH. Use windows-2022 or a VS 2022 developer prompt." + } +} + +function Test-AnglePatch { + Require-Command git + Assert-AngleVisualStudioVersion + + $src = Sync-Angle + Apply-AnglePatches $src + if (-not (Select-String -Path (Join-Path $src "BUILD.gn") -Pattern 'angle_static_library\("libANGLE_static"\)' -Quiet)) { + throw "ANGLE BUILD.gn preflight failed." + } + Write-Host "ANGLE preflight passed." +} + +function Build-Angle { + Ensure-Tools + Assert-AngleVisualStudioVersion + Ensure-DepotTools + $src = Sync-Angle + New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null + Apply-AnglePatches $src + Push-Location $src + try { + python scripts/bootstrap.py + gclient sync -f -D -R + $outDir = Join-Path $src "out\win-static-$TargetCpu" + New-Item -ItemType Directory -Path $outDir -Force | Out-Null + @" +target_os = "win" +target_cpu = "$TargetCpu" +is_debug = false +is_component_build = false +is_clang = true +use_lld = false +use_custom_libcxx = false +use_thin_lto = false +symbol_level = 0 +angle_build_tests = false +build_angle_deqp_tests = false +angle_enable_swiftshader = false +angle_enable_vulkan = false +angle_enable_wgpu = false +"@ | Set-Content -Path (Join-Path $outDir "args.gn") -Encoding ASCII + gn gen $outDir + ninja -C $outDir -j $BuildJobs libANGLE_static libGLESv2_static + Copy-FirstExisting (Join-Path $OutputDir "libANGLE_static.lib") @((Join-Path $outDir "libANGLE_static.lib"), (Join-Path $outDir "obj\libANGLE_static.lib"), (Join-Path $outDir "obj\libANGLE_static\libANGLE_static.lib")) + Copy-FirstExisting (Join-Path $OutputDir "libGLESv2_static.lib") @((Join-Path $outDir "libGLESv2_static.lib"), (Join-Path $outDir "obj\libGLESv2_static.lib"), (Join-Path $outDir "obj\libGLESv2_static\libGLESv2_static.lib")) + foreach ($libcxxName in @("libc++.lib", "libc++abi.lib")) { + $libcxxPath = Join-Path $src "third_party\llvm-build\Release+Asserts\lib\$libcxxName" + if (Test-Path $libcxxPath) { + Copy-Item $libcxxPath (Join-Path $OutputDir $libcxxName) -Force + Write-Host "Wrote $(Join-Path $OutputDir $libcxxName)" + } + } + } finally { + Pop-Location + } +} + +New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null +New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null +switch ($Target) { + "skia" { Build-Skia } + "angle-preflight" { Test-AnglePatch } + "angle" { Build-Angle } + "all" { Build-Skia; Build-Angle } +} diff --git a/scripts/create-avalonia-smoke.ps1 b/scripts/create-avalonia-smoke.ps1 new file mode 100644 index 0000000..e9eb5f8 --- /dev/null +++ b/scripts/create-avalonia-smoke.ps1 @@ -0,0 +1,154 @@ +param( + [Parameter(Mandatory = $true)] + [string]$ProjectDir, + + [Parameter(Mandatory = $true)] + [string]$NuGetSource, + + [Parameter(Mandatory = $true)] + [string]$StaticLinkVersion, + + [string]$AvaloniaVersion = "11.3.14", + + [string]$StaticLinkNativeVersion = "" +) + +$ErrorActionPreference = "Stop" + +New-Item -ItemType Directory -Path $ProjectDir -Force | Out-Null + +$nativeReference = "" +if ($StaticLinkNativeVersion) { + $nativeReference = "`n " +} + +@" + + + + + + + + +"@ | Set-Content -Path (Join-Path $ProjectDir "NuGet.config") -Encoding UTF8 + +@" + + + WinExe + net10.0 + enable + enable + true + true + true + true + true + false + true + + + + + + + $nativeReference + + +"@ | Set-Content -Path (Join-Path $ProjectDir "AvaloniaStaticLinkSmoke.csproj") -Encoding UTF8 + +@" +using Avalonia; + +namespace AvaloniaStaticLinkSmoke; + +internal static class Program +{ + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + + public static AppBuilder BuildAvaloniaApp() + { + var builder = AppBuilder.Configure() + .UsePlatformDetect() + .LogToTrace(); + + if (OperatingSystem.IsMacOS()) + { + builder = builder.With(new AvaloniaNativePlatformOptions + { + RenderingMode = [AvaloniaNativeRenderingMode.Software] + }); + } + + return builder; + } +} +"@ | Set-Content -Path (Join-Path $ProjectDir "Program.cs") -Encoding UTF8 + +@" +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +namespace AvaloniaStaticLinkSmoke; + +public sealed partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } +} +"@ | Set-Content -Path (Join-Path $ProjectDir "App.axaml.cs") -Encoding UTF8 + +@" + + + + + +"@ | Set-Content -Path (Join-Path $ProjectDir "App.axaml") -Encoding UTF8 + +@" +using Avalonia.Controls; + +namespace AvaloniaStaticLinkSmoke; + +public sealed partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} +"@ | Set-Content -Path (Join-Path $ProjectDir "MainWindow.axaml.cs") -Encoding UTF8 + +@" + + + + + + + + +"@ | Set-Content -Path (Join-Path $ProjectDir "MainWindow.axaml") -Encoding UTF8