From 41c4b0c8d42714aa8b615002b08c6cfa02ef2065 Mon Sep 17 00:00:00 2001 From: QiuYe Date: Sun, 3 May 2026 12:34:48 +0800 Subject: [PATCH] Bundle native runtime libraries and notices --- .github/scripts/stage-native-runtime-unix.sh | 128 ++++++++++++++++++ .../scripts/stage-native-runtime-windows.ps1 | 112 +++++++++++++++ .github/workflows/auto-build.yml | 8 +- .github/workflows/native-build.yml | 14 +- .github/workflows/publish-on-release.yml | 8 +- THIRD_PARTY_NOTICES.md | 4 + 6 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 .github/scripts/stage-native-runtime-unix.sh create mode 100644 .github/scripts/stage-native-runtime-windows.ps1 diff --git a/.github/scripts/stage-native-runtime-unix.sh b/.github/scripts/stage-native-runtime-unix.sh new file mode 100644 index 0000000..f59b556 --- /dev/null +++ b/.github/scripts/stage-native-runtime-unix.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 3 ]]; then + echo "usage: $0 " >&2 + exit 1 +fi + +platform="$1" +classifier="$2" +main_lib="$3" + +repo_root="$(pwd)" +native_dir="$repo_root/native/$classifier" +licenses_dir="$native_dir/licenses" + +mkdir -p "$native_dir" "$licenses_dir" + +cp "$repo_root/LICENSE" "$licenses_dir/LGPL-3.0.txt" +cp "$repo_root/THIRD_PARTY_NOTICES.md" "$licenses_dir/THIRD_PARTY_NOTICES.md" + +if compgen -G "$repo_root/build/native/lib/*" > /dev/null; then + cp "$repo_root"/build/native/lib/* "$native_dir"/ +fi + +copy_if_exists() { + local source_path="$1" + local target_path="${2:-$native_dir/$(basename "$source_path")}" + if [[ -n "$source_path" && -f "$source_path" ]]; then + cp -L "$source_path" "$target_path" + fi +} + +copy_notice_if_exists() { + local source_path="$1" + local target_name="$2" + if [[ -n "$source_path" && -f "$source_path" ]]; then + cp -L "$source_path" "$licenses_dir/$target_name" + fi +} + +stage_linux_runtime() { + mapfile -t runtime_deps < <( + ldd "$native_dir/$main_lib" | + awk '/=> \// { print $3 }' | + grep -E '/lib(gmp|mpfr)\.so' + ) + + for dep in "${runtime_deps[@]}"; do + copy_if_exists "$dep" + done + + copy_notice_if_exists "/usr/share/common-licenses/LGPL-3" "LGPL-3.0-system-copy.txt" + copy_notice_if_exists "/usr/share/doc/libgmp10/copyright" "gmp-copyright.txt" + copy_notice_if_exists "/usr/share/doc/libmpfr6/copyright" "mpfr-copyright.txt" +} + +stage_macos_runtime() { + local main_lib_path="$native_dir/$main_lib" + local gmp_ref + local mpfr_ref + local gmp_name + local mpfr_name + local mpfr_lib_path + local mpfr_gmp_ref + local gmp_prefix + local mpfr_prefix + + gmp_ref="$(otool -L "$main_lib_path" | awk '/libgmp.*\.dylib/ { print $1; exit }')" + mpfr_ref="$(otool -L "$main_lib_path" | awk '/libmpfr.*\.dylib/ { print $1; exit }')" + + if [[ -z "$gmp_ref" ]]; then + gmp_prefix="$(brew --prefix gmp)" + gmp_ref="$(find -L "$gmp_prefix/lib" -maxdepth 1 -type f -name 'libgmp*.dylib' | sort | head -n1)" + fi + if [[ -z "$mpfr_ref" ]]; then + mpfr_prefix="$(brew --prefix mpfr)" + mpfr_ref="$(find -L "$mpfr_prefix/lib" -maxdepth 1 -type f -name 'libmpfr*.dylib' | sort | head -n1)" + fi + + if [[ -z "$gmp_ref" || -z "$mpfr_ref" ]]; then + echo "failed to resolve macOS GMP/MPFR runtime libraries" >&2 + exit 1 + fi + + copy_if_exists "$gmp_ref" + copy_if_exists "$mpfr_ref" + + gmp_name="$(basename "$gmp_ref")" + mpfr_name="$(basename "$mpfr_ref")" + + install_name_tool -id "@loader_path/$gmp_name" "$native_dir/$gmp_name" + install_name_tool -id "@loader_path/$mpfr_name" "$native_dir/$mpfr_name" + + if otool -L "$main_lib_path" | grep -Fq "$gmp_ref"; then + install_name_tool -change "$gmp_ref" "@loader_path/$gmp_name" "$main_lib_path" + fi + if otool -L "$main_lib_path" | grep -Fq "$mpfr_ref"; then + install_name_tool -change "$mpfr_ref" "@loader_path/$mpfr_name" "$main_lib_path" + fi + + mpfr_lib_path="$native_dir/$mpfr_name" + mpfr_gmp_ref="$(otool -L "$mpfr_lib_path" | awk '/libgmp.*\.dylib/ { print $1; exit }')" + if [[ -n "$mpfr_gmp_ref" && "$mpfr_gmp_ref" != "@loader_path/$gmp_name" ]]; then + install_name_tool -change "$mpfr_gmp_ref" "@loader_path/$gmp_name" "$mpfr_lib_path" + fi + + gmp_prefix="$(brew --prefix gmp)" + mpfr_prefix="$(brew --prefix mpfr)" + + copy_notice_if_exists "$(find "$gmp_prefix" -path '*/share/doc/*' -type f \( -iname 'COPYING*' -o -iname 'LICENSE*' \) | sort | head -n1)" "gmp-license.txt" + copy_notice_if_exists "$(find "$mpfr_prefix" -path '*/share/doc/*' -type f \( -iname 'COPYING*' -o -iname 'LICENSE*' \) | sort | head -n1)" "mpfr-license.txt" +} + +case "$platform" in + linux) + stage_linux_runtime + ;; + macos) + stage_macos_runtime + ;; + *) + echo "unsupported platform: $platform" >&2 + exit 1 + ;; +esac + +ls -la "$native_dir" diff --git a/.github/scripts/stage-native-runtime-windows.ps1 b/.github/scripts/stage-native-runtime-windows.ps1 new file mode 100644 index 0000000..1a422a7 --- /dev/null +++ b/.github/scripts/stage-native-runtime-windows.ps1 @@ -0,0 +1,112 @@ +param( + [Parameter(Mandatory = $true)] + [string]$Label, + [string]$Triplet = "" +) + +$ErrorActionPreference = "Stop" + +$repoRoot = (Get-Location).Path +$nativeDir = Join-Path $repoRoot "native\$Label" +$licensesDir = Join-Path $nativeDir "licenses" + +New-Item -ItemType Directory -Force -Path $nativeDir, $licensesDir | Out-Null + +if (Test-Path (Join-Path $repoRoot "build\native\lib")) { + Copy-Item -Path (Join-Path $repoRoot "build\native\lib\*") -Destination $nativeDir -Force +} + +Copy-Item -Path (Join-Path $repoRoot "LICENSE") -Destination (Join-Path $licensesDir "LGPL-3.0.txt") -Force +Copy-Item -Path (Join-Path $repoRoot "THIRD_PARTY_NOTICES.md") -Destination (Join-Path $licensesDir "THIRD_PARTY_NOTICES.md") -Force + +function Find-FirstFile { + param( + [string[]]$Roots, + [string[]]$Names, + [string]$ArchHint = "" + ) + + foreach ($root in $Roots) { + if ([string]::IsNullOrWhiteSpace($root) -or -not (Test-Path $root)) { + continue + } + $candidate = Get-ChildItem -Path $root -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { + $Names -contains $_.Name -and + ([string]::IsNullOrWhiteSpace($ArchHint) -or $_.FullName.ToLowerInvariant().Contains($ArchHint.ToLowerInvariant())) + } | + Select-Object -First 1 + if ($candidate) { + return $candidate.FullName + } + } + return $null +} + +if ($Label -eq "windows-x86-64") { + $compilerDir = if ($env:CXX) { Split-Path -Parent $env:CXX } else { $null } + $licenseRoots = @() + if ($compilerDir) { + $licenseRoots += Join-Path $compilerDir "..\share\licenses" + $licenseRoots += Join-Path $compilerDir "..\..\share\licenses" + } + $licenseRoots += @( + "C:\mingw64\share\licenses", + "C:\msys64\mingw64\share\licenses", + "C:\Program Files\Git\mingw64\share\licenses" + ) + + $gccRuntimeNotice = Find-FirstFile -Roots $licenseRoots -Names @("COPYING.RUNTIME") + if ($gccRuntimeNotice) { + Copy-Item -Path $gccRuntimeNotice -Destination (Join-Path $licensesDir "COPYING.RUNTIME.txt") -Force + } + + $winpthreadNotice = Get-ChildItem -Path $licenseRoots -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { + $_.FullName -match "winpthread|winpthreads" -and + ($_.Name -eq "COPYING.winpthreads" -or $_.Name -eq "COPYING") + } | + Select-Object -First 1 + if ($winpthreadNotice) { + Copy-Item -Path $winpthreadNotice.FullName -Destination (Join-Path $licensesDir "COPYING.winpthreads.txt") -Force + } +} + +if ($Label -eq "windows-aarch64") { + $redistRoots = @() + if ($env:VCToolsRedistDir) { + $redistRoots += $env:VCToolsRedistDir + } + if ($env:VCINSTALLDIR) { + $redistRoots += Join-Path $env:VCINSTALLDIR "Redist\MSVC" + } + $redistRoots += @( + "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Redist\MSVC", + "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Redist\MSVC", + "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC", + "C:\Program Files (x86)\Microsoft Visual Studio\Installer\Feedback\arm64" + ) + + foreach ($dllName in @("msvcp140.dll", "vcruntime140.dll", "vcruntime140_1.dll", "concrt140.dll")) { + $dllPath = Find-FirstFile -Roots $redistRoots -Names @($dllName) -ArchHint "arm64" + if (-not $dllPath) { + $dllPath = Find-FirstFile -Roots $redistRoots -Names @($dllName) + } + if ($dllPath) { + Copy-Item -Path $dllPath -Destination (Join-Path $nativeDir $dllName) -Force + } else { + Write-Warning "Failed to locate $dllName for $Label" + } + } + + @" +Microsoft Visual C++ runtime files in this directory were staged from a +Visual Studio 2022 redistributable location on the build machine. + +Redistribution is governed by the Visual Studio 2022 license terms and the +official distributable code list: +https://learn.microsoft.com/en-gb/visualstudio/releases/2022/redistribution +"@ | Set-Content -Path (Join-Path $licensesDir "MICROSOFT_VISUAL_CPP_REDIST_NOTICE.txt") +} + +Get-ChildItem -Recurse $nativeDir diff --git a/.github/workflows/auto-build.yml b/.github/workflows/auto-build.yml index 9be5f2e..ed7d5b5 100644 --- a/.github/workflows/auto-build.yml +++ b/.github/workflows/auto-build.yml @@ -25,12 +25,13 @@ jobs: steps: - uses: actions/checkout@v4 - run: sudo apt-get update && sudo apt-get install -y libgmp-dev libmpfr-dev cmake + - run: chmod +x .github/scripts/stage-native-runtime-unix.sh - name: Build run: | mkdir -p build/native native/${{ matrix.label }} cmake -S src/main/cpp -B build/native -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=build/native cmake --build build/native --target install -j$(nproc) - cp build/native/lib/* native/${{ matrix.label }}/ + .github/scripts/stage-native-runtime-unix.sh linux ${{ matrix.label }} ${{ matrix.lib }} echo "=== built ===" ls -la native/${{ matrix.label }}/ file native/${{ matrix.label }}/${{ matrix.lib }} @@ -55,12 +56,13 @@ jobs: steps: - uses: actions/checkout@v4 - run: brew install gmp mpfr cmake + - run: chmod +x .github/scripts/stage-native-runtime-unix.sh - name: Build run: | mkdir -p build/native native/${{ matrix.label }} cmake -S src/main/cpp -B build/native -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=build/native cmake --build build/native --target install -j$(sysctl -n hw.ncpu) - cp build/native/lib/* native/${{ matrix.label }}/ + .github/scripts/stage-native-runtime-unix.sh macos ${{ matrix.label }} ${{ matrix.lib }} echo "=== built ===" ls -la native/${{ matrix.label }}/ file native/${{ matrix.label }}/${{ matrix.lib }} @@ -132,7 +134,7 @@ jobs: New-Item -Force -ItemType Directory -Path build/native, "native/${{ matrix.label }}" cmake -S src/main/cpp -B build/native -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=build/native "-DCMAKE_PREFIX_PATH=${{ github.workspace }}/vcpkg_installed/${{ matrix.vcpkg_triplet }}" cmake --build build/native --target install - Copy-Item build/native/lib/* "native/${{ matrix.label }}/" -Force + .github/scripts/stage-native-runtime-windows.ps1 -Label '${{ matrix.label }}' -Triplet '${{ matrix.vcpkg_triplet }}' echo "=== built ===" cmd /c "dir native\${{ matrix.label }}" - uses: actions/upload-artifact@v4 diff --git a/.github/workflows/native-build.yml b/.github/workflows/native-build.yml index 95b9278..83e2831 100644 --- a/.github/workflows/native-build.yml +++ b/.github/workflows/native-build.yml @@ -66,6 +66,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Make native staging script executable + if: runner.os != 'Windows' + run: chmod +x .github/scripts/stage-native-runtime-unix.sh - name: Configure MSVC for Windows ARM64 if: matrix.vcpkg_triplet == 'arm64-windows' uses: ilammy/msvc-dev-cmd@v1 @@ -127,11 +130,18 @@ jobs: run: | chmod +x gradlew 2>/dev/null || true ./gradlew build -x test -Dbigmath.native.classifier=${{ matrix.label }} + if [ "${{ runner.os }}" = "Linux" ]; then + .github/scripts/stage-native-runtime-unix.sh linux ${{ matrix.label }} libbigmath_ffm.so + else + .github/scripts/stage-native-runtime-unix.sh macos ${{ matrix.label }} libbigmath_ffm.dylib + fi shell: bash - name: Build native if: runner.os == 'Windows' shell: pwsh - run: .\gradlew.bat --% build -x test -Dbigmath.native.classifier=${{ matrix.label }} + run: | + .\gradlew.bat --% build -x test -Dbigmath.native.classifier=${{ matrix.label }} + .github/scripts/stage-native-runtime-windows.ps1 -Label '${{ matrix.label }}' -Triplet '${{ matrix.vcpkg_triplet }}' - name: Save vcpkg binary cache if: runner.os == 'Windows' && !cancelled() @@ -143,7 +153,7 @@ jobs: - uses: actions/upload-artifact@v4 with: name: native-libs-${{ matrix.label }} - path: build/generated/local-native/${{ matrix.label }}/** + path: native/${{ matrix.label }}/** retention-days: 7 android-build: diff --git a/.github/workflows/publish-on-release.yml b/.github/workflows/publish-on-release.yml index 30c29d2..e8efa0a 100644 --- a/.github/workflows/publish-on-release.yml +++ b/.github/workflows/publish-on-release.yml @@ -45,12 +45,13 @@ jobs: steps: - uses: actions/checkout@v4 - run: sudo apt-get update && sudo apt-get install -y libgmp-dev libmpfr-dev cmake + - run: chmod +x .github/scripts/stage-native-runtime-unix.sh - name: Build run: | mkdir -p build/native native/${{ matrix.label }} cmake -S src/main/cpp -B build/native -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=build/native cmake --build build/native --target install -j$(nproc) - cp build/native/lib/* native/${{ matrix.label }}/ + .github/scripts/stage-native-runtime-unix.sh linux ${{ matrix.label }} ${{ matrix.lib }} - uses: actions/upload-artifact@v4 with: name: native-${{ matrix.label }} @@ -72,12 +73,13 @@ jobs: steps: - uses: actions/checkout@v4 - run: brew install gmp mpfr cmake + - run: chmod +x .github/scripts/stage-native-runtime-unix.sh - name: Build run: | mkdir -p build/native native/${{ matrix.label }} cmake -S src/main/cpp -B build/native -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=build/native cmake --build build/native --target install -j$(sysctl -n hw.ncpu) - cp build/native/lib/* native/${{ matrix.label }}/ + .github/scripts/stage-native-runtime-unix.sh macos ${{ matrix.label }} ${{ matrix.lib }} - uses: actions/upload-artifact@v4 with: name: native-${{ matrix.label }} @@ -146,7 +148,7 @@ jobs: New-Item -Force -ItemType Directory -Path build/native, "native/${{ matrix.label }}" cmake -S src/main/cpp -B build/native -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=build/native "-DCMAKE_PREFIX_PATH=${{ github.workspace }}/vcpkg_installed/${{ matrix.vcpkg_triplet }}" cmake --build build/native --target install - Copy-Item build/native/lib/* "native/${{ matrix.label }}/" -Force + .github/scripts/stage-native-runtime-windows.ps1 -Label '${{ matrix.label }}' -Triplet '${{ matrix.vcpkg_triplet }}' - uses: actions/upload-artifact@v4 with: name: native-${{ matrix.label }} diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 156cd5a..fe6d141 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -30,6 +30,7 @@ Relevant loading logic lives in: | GNU libstdc++ runtime | `libstdc++-6.dll` | GPL-3.0-or-later with GCC Runtime Library Exception 3.1 | https://gcc.gnu.org/onlinedocs/libstdc++/manual/license.html | | GNU libgcc runtime | `libgcc_s_*.dll` | GPL-3.0-or-later with GCC Runtime Library Exception 3.1 | https://gcc.gnu.org/onlinedocs/libstdc++/manual/license.html | | MinGW-w64 winpthreads | `libwinpthread-1.dll` | Upstream `COPYING.winpthreads` notice as shipped by MinGW-w64 | https://www.mingw-w64.org/ | +| Microsoft VC++ runtime | `msvcp140.dll`, `vcruntime140*.dll`, `concrt140.dll` | Redistributable in unmodified form by licensed Visual Studio users under the Visual Studio 2022 distributable code terms | https://learn.microsoft.com/en-gb/visualstudio/releases/2022/redistribution | ### License Texts @@ -47,6 +48,9 @@ Relevant loading logic lives in: - If you redistribute Windows runtime DLLs built from a MinGW-w64 toolchain, ship the originating toolchain's `COPYING.RUNTIME` and `COPYING.winpthreads` files alongside your binary artifact. +- When native artifacts bundle third-party runtime libraries, Bigmath-FFM also + stages a per-classifier `licenses/` directory next to those binaries so the + unpacked package carries the corresponding notices with it. ### Corresponding Source