name: UniFFI env: UNIFFI_DIRECTORY: bindings/uniffi on: pull_request: paths: - 'bindings/uniffi/**' - 'core/**' - 'test-data/**' - '.github/workflows/uniffi.yaml' push: tags: - 'uniffi-v*' jobs: build: permissions: contents: read id-token: write env: OUTPUT_NAME: zen_uniffi strategy: fail-fast: true matrix: settings: - host: windows-latest target: 'x86_64-pc-windows-msvc' output: '{0}.dll' lib_dir: 'win32-x86-64' - host: macos-latest target: 'x86_64-apple-darwin' output: 'lib{0}.dylib' lib_dir: 'darwin-x86-64' - host: macos-latest target: 'aarch64-apple-darwin' output: 'lib{0}.dylib' lib_dir: 'darwin-aarch64' - host: ubuntu-latest target: 'x86_64-unknown-linux-gnu' output: 'lib{0}.so' lib_dir: 'linux-x86-64' - host: ubuntu-latest target: 'aarch64-unknown-linux-gnu' output: 'lib{0}.so' lib_dir: 'linux-aarch64' - host: ubuntu-latest target: 's390x-unknown-linux-gnu' output: 'lib{0}.so' lib_dir: 'linux-s390x' extra_args: '--features bindgen' name: UniFFI - ${{ matrix.settings.target }} runs-on: ${{ matrix.settings.host }} steps: - uses: actions/checkout@v3 - name: Setup Rust Toolchain uses: dtolnay/rust-toolchain@stable with: target: ${{ matrix.settings.target }} - uses: ./.github/actions/setup-rust with: shared-key: uniffi-${{ matrix.settings.target }} zig: ${{ matrix.settings.host == 'ubuntu-latest' }} sccache-bucket: ${{ secrets.SCCACHE_S3_BUCKET }} sccache-region: ${{ secrets.SCCACHE_AWS_REGION }} sccache-role-arn: ${{ secrets.SCCACHE_AWS_ROLE_ARN }} # Linux targets (incl. s390x) build on the host via zig; mac/windows build natively. - name: Build (zig, linux) if: ${{ matrix.settings.host == 'ubuntu-latest' }} working-directory: ${{ env.UNIFFI_DIRECTORY }} run: cargo zigbuild --lib --release --locked --target ${{ matrix.settings.target }}.${{ env.GLIBC_VERSION }} ${{ matrix.settings.extra_args }} - name: Build (native) if: ${{ matrix.settings.host != 'ubuntu-latest' }} uses: houseabsolute/actions-rust-cross@v1 with: working-directory: ${{ env.UNIFFI_DIRECTORY }} target: ${{ matrix.settings.target }} args: '--lib --release --locked ${{ matrix.settings.extra_args }}' - name: Upload artifact uses: actions/upload-artifact@v4 with: name: ${{ matrix.settings.lib_dir }} path: target/${{ matrix.settings.target }}/release/${{ format(matrix.settings.output, env.OUTPUT_NAME) }} if-no-files-found: error release-java-kotlin: runs-on: ubuntu-latest environment: release needs: [ build ] if: "github.ref_type == 'tag'" defaults: run: working-directory: ${{ env.UNIFFI_DIRECTORY }} steps: - uses: actions/checkout@v3 - name: Set up JDK 21 uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' - name: Setup Rust Toolchain uses: dtolnay/rust-toolchain@stable - uses: ./.github/actions/setup-rust with: shared-key: uniffi-java-kotlin - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - name: Download all artifacts uses: actions/download-artifact@v4 with: path: build/generated/resources - name: Move artifacts run: mv ../../build ./ - name: Generate Kotlin sources run: | cargo run --locked --bin uniffi-bindgen -- generate \ --library build/generated/resources/darwin-x86-64/libzen_uniffi.dylib \ --language kotlin \ --out-dir build/generated/kotlin - name: Install uniffi-bindgen-java run: cargo install uniffi-bindgen-java - name: Generate Java sources run: | uniffi-bindgen-java generate \ --library build/generated/resources/darwin-x86-64/libzen_uniffi.dylib \ --out-dir build/generated/java - name: Print directory tree run: tree build - name: Publish Java Maven Artifact env: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} GPG_SIGNING_PASSPHRASE: ${{ secrets.GPG_SIGNING_PASSPHRASE }} run: ./gradlew publishAllPublicationsToCentralPortal -PpublishScope=java - name: Remove s390x artifact (Java-only target) run: rm -rf build/generated/resources/linux-s390x - name: Publish Kotlin Maven Artifact env: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} GPG_SIGNING_PASSPHRASE: ${{ secrets.GPG_SIGNING_PASSPHRASE }} run: ./gradlew publishAllPublicationsToCentralPortal -PpublishScope=kotlin release-csharp: runs-on: ubuntu-latest environment: release needs: [ build ] permissions: id-token: write # Required for OIDC if: "github.ref_type == 'tag'" defaults: run: working-directory: ${{ env.UNIFFI_DIRECTORY }} steps: - uses: actions/checkout@v3 - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x' - name: Setup Rust Toolchain uses: dtolnay/rust-toolchain@stable - uses: ./.github/actions/setup-rust with: shared-key: uniffi-csharp - name: Download all artifacts uses: actions/download-artifact@v4 with: path: build/generated/resources - name: Move artifacts run: mv ../../build ./ - name: Remove s390x artifact (not needed for C#) run: rm -rf build/generated/resources/linux-s390x - name: Install uniffi-bindgen-cs run: cargo install uniffi-bindgen-cs --git https://github.com/bcrevar-gorules/uniffi-bindgen-cs --branch fix/async-callback-interface-return-type - name: Generate C# bindings run: | uniffi-bindgen-cs \ --library build/generated/resources/darwin-x86-64/libzen_uniffi.dylib \ --config uniffi.toml \ --out-dir build/generated/csharp - name: Upload generated C# source uses: actions/upload-artifact@v4 with: name: csharp-generated-source path: ${{ env.UNIFFI_DIRECTORY }}/build/generated/csharp/zen_uniffi.cs if-no-files-found: error - name: Extract version from Cargo.toml id: version run: | VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/') echo "VERSION=$VERSION" >> $GITHUB_ENV echo "Detected version: $VERSION" - name: Pack NuGet package run: dotnet pack nuget/GoRules.ZenEngine.csproj -c Release -p:Version=$VERSION - name: Upload NuGet package artifact uses: actions/upload-artifact@v4 with: name: nuget-package path: ${{ env.UNIFFI_DIRECTORY }}/nuget/bin/Release/*.nupkg if-no-files-found: error - name: NuGet login (trusted publishing) uses: NuGet/login@v1 id: nuget-login with: user: ${{ secrets.NUGET_USER }} - name: Push to NuGet.org run: | dotnet nuget push nuget/bin/Release/*.nupkg \ --api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }} \ --source https://api.nuget.org/v3/index.json \ --skip-duplicate build-ios: permissions: contents: read id-token: write env: OUTPUT_NAME: zen_uniffi IPHONEOS_DEPLOYMENT_TARGET: '16.0' strategy: fail-fast: true matrix: settings: - target: 'aarch64-apple-ios' output: 'lib{0}.a' lib_dir: 'ios-aarch64' - target: 'x86_64-apple-ios' output: 'lib{0}.a' lib_dir: 'ios-x86-64' - target: 'aarch64-apple-ios-sim' output: 'lib{0}.a' lib_dir: 'ios-aarch64-sim' name: UniFFI iOS - ${{ matrix.settings.target }} runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: Setup Rust Toolchain uses: dtolnay/rust-toolchain@stable with: target: ${{ matrix.settings.target }} - uses: ./.github/actions/setup-rust with: shared-key: uniffi-ios-${{ matrix.settings.target }} sccache-bucket: ${{ secrets.SCCACHE_S3_BUCKET }} sccache-region: ${{ secrets.SCCACHE_AWS_REGION }} sccache-role-arn: ${{ secrets.SCCACHE_AWS_ROLE_ARN }} - name: Build iOS run: | if [ "${{ matrix.settings.target }}" = "aarch64-apple-ios-sim" ]; then export BINDGEN_EXTRA_CLANG_ARGS="--target=aarch64-apple-ios --sysroot=$(xcrun --sdk iphonesimulator --show-sdk-path)" fi cargo build -p zen-uniffi --target ${{ matrix.settings.target }} --release --locked --features bindgen - name: Upload artifact uses: actions/upload-artifact@v4 with: name: ${{ matrix.settings.lib_dir }} path: target/${{ matrix.settings.target }}/release/${{ format(matrix.settings.output, env.OUTPUT_NAME) }} if-no-files-found: error create-xcframework: runs-on: macos-latest needs: [ build-ios ] permissions: contents: read id-token: write steps: - uses: actions/checkout@v3 - name: Setup Rust Toolchain uses: dtolnay/rust-toolchain@stable - uses: ./.github/actions/setup-rust with: shared-key: uniffi-swift sccache-bucket: ${{ secrets.SCCACHE_S3_BUCKET }} sccache-region: ${{ secrets.SCCACHE_AWS_REGION }} sccache-role-arn: ${{ secrets.SCCACHE_AWS_ROLE_ARN }} - name: Download iOS artifacts uses: actions/download-artifact@v4 with: path: target-ios - name: Reorganize artifacts to match target structure run: | mkdir -p target/aarch64-apple-ios/release mkdir -p target/x86_64-apple-ios/release mkdir -p target/aarch64-apple-ios-sim/release cp target-ios/ios-aarch64/libzen_uniffi.a target/aarch64-apple-ios/release/ cp target-ios/ios-x86-64/libzen_uniffi.a target/x86_64-apple-ios/release/ cp target-ios/ios-aarch64-sim/libzen_uniffi.a target/aarch64-apple-ios-sim/release/ - name: Generate Swift bindings run: | cargo run --locked --bin uniffi-bindgen -- generate \ --library target/aarch64-apple-ios/release/libzen_uniffi.a \ --language swift \ --out-dir swift-bindings-temp - name: Organize Swift bindings structure run: | # Create the proper directory structure mkdir -p swift-bindings/zen_uniffiFFI mkdir -p swift-bindings/ZenUniffi # Move and rename the header file to zen_uniffiFFI.h in the zen_uniffiFFI folder if [ -f swift-bindings-temp/zen_uniffiFFI.h ]; then mv swift-bindings-temp/zen_uniffiFFI.h swift-bindings/zen_uniffiFFI/ fi # Move and rename the modulemap to module.modulemap in the zen_uniffiFFI folder if [ -f swift-bindings-temp/zen_uniffiFFI.modulemap ]; then mv swift-bindings-temp/zen_uniffiFFI.modulemap swift-bindings/zen_uniffiFFI/module.modulemap fi # Move the Swift file to zen_uniffi.swift in the ZenUniffi folder if [ -f swift-bindings-temp/zen_uniffi.swift ]; then mv swift-bindings-temp/zen_uniffi.swift swift-bindings/ZenUniffi/ fi # Clean up temp directory rm -rf swift-bindings-temp - name: Create fat simulator library run: | mkdir -p target/universal-sim/release lipo -create \ target/x86_64-apple-ios/release/libzen_uniffi.a \ target/aarch64-apple-ios-sim/release/libzen_uniffi.a \ -output target/universal-sim/release/libzen_uniffi.a - name: Create XCFramework run: | # Create XCFramework with device + universal simulator xcodebuild -create-xcframework \ -library target/aarch64-apple-ios/release/libzen_uniffi.a \ -headers swift-bindings/zen_uniffiFFI \ -library target/universal-sim/release/libzen_uniffi.a \ -headers swift-bindings/zen_uniffiFFI \ -output ZenUniffi.xcframework - name: Upload XCFramework uses: actions/upload-artifact@v4 with: name: xcframework path: ZenUniffi.xcframework/ if-no-files-found: error - name: Upload Swift bindings uses: actions/upload-artifact@v4 with: name: swift-bindings path: swift-bindings if-no-files-found: error release-ios: name: Release iOS runs-on: macos-latest environment: release needs: [ create-xcframework ] if: "github.ref_type == 'tag'" steps: - name: Checkout zen-ios uses: actions/checkout@v3 with: repository: gorules/zen-ios persist-credentials: false token: ${{ secrets.PAT }} - name: Download XCFramework uses: actions/download-artifact@v4 with: name: xcframework path: ZenUniffi.xcframework - name: Download Swift bindings uses: actions/download-artifact@v4 with: name: swift-bindings path: swift-bindings - name: Move artifacts to repo run: | # Remove existing Sources if it exists rm -rf Sources # Move swift-bindings to Sources (already has the proper structure) mv swift-bindings Sources - name: Set branch name run: | echo "BRANCH_NAME=chore/${{ github.run_id }}-${{ github.run_attempt }}" >> $GITHUB_ENV - name: Commit changes run: | BRANCH_NAME="${{ env.BRANCH_NAME }}" git checkout -b $BRANCH_NAME # Setup the committers identity git config user.email "bot@gorules.io" git config user.name "Bot GoRules" git config http.postBuffer 524288000 git config http.lowSpeedTime 600 # Show what we have ls -la # Commit the changes - add all new/modified files git add -A git status git commit -m "chore: update XCFramework" - name: Push changes uses: ad-m/github-push-action@master with: repository: gorules/zen-ios github_token: ${{ secrets.PAT }} branch: ${{ env.BRANCH_NAME }} - name: Create PR run: | BRANCH_NAME="${{ env.BRANCH_NAME }}" # Store the PAT in a file that can be accessed by the GitHub CLI echo "${{ secrets.PAT }}" > token.txt # Authorize GitHub CLI and create a pull request echo "Authorizing for PR" gh auth login --with-token < token.txt gh pr create \ --body "" \ --title "chore: update XCFramework" \ --head "$BRANCH_NAME" \ --base "master" build-android: runs-on: ubuntu-latest permissions: contents: read id-token: write env: OUTPUT_NAME: zen_uniffi steps: - uses: actions/checkout@v3 - name: Setup Rust Toolchain uses: dtolnay/rust-toolchain@stable - uses: ./.github/actions/setup-rust with: shared-key: uniffi-android sccache-bucket: ${{ secrets.SCCACHE_S3_BUCKET }} sccache-region: ${{ secrets.SCCACHE_AWS_REGION }} sccache-role-arn: ${{ secrets.SCCACHE_AWS_ROLE_ARN }} - name: Install cargo-ndk run: cargo install cargo-ndk - name: Setup Android NDK uses: nttld/setup-ndk@v1 id: setup-ndk with: ndk-version: r26d add-to-path: true - name: Add Rust Android targets (all) if: "github.ref_type == 'tag'" run: | rustup target add aarch64-linux-android rustup target add armv7-linux-androideabi rustup target add i686-linux-android rustup target add x86_64-linux-android - name: Add Rust Android targets (arm64 only) if: "github.ref_type != 'tag'" run: | rustup target add aarch64-linux-android - name: Build for Android (all targets) if: "github.ref_type == 'tag'" env: ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }} run: | cargo ndk -t arm64-v8a -t armeabi-v7a -t x86 -t x86_64 \ --platform 33 \ -o ./android-libs \ build -p zen-uniffi --release --locked --features bindgen - name: Build for Android (arm64 only) if: "github.ref_type != 'tag'" env: ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }} run: | cargo ndk -t arm64-v8a \ --platform 33 \ -o ./android-libs \ build -p zen-uniffi --release --locked --features bindgen - name: Upload Android artifacts uses: actions/upload-artifact@v4 with: name: android-libs path: android-libs/ if-no-files-found: error prepare-android-bindings: runs-on: ubuntu-latest needs: [ build ] defaults: run: working-directory: ${{ env.UNIFFI_DIRECTORY }} steps: - uses: actions/checkout@v3 - name: Download all artifacts uses: actions/download-artifact@v4 with: path: ${{ env.UNIFFI_DIRECTORY }}/build/generated/resources - name: Print directory tree run: tree build - name: Generate Kotlin sources run: | cargo run --locked --bin uniffi-bindgen -- generate \ --library build/generated/resources/darwin-x86-64/libzen_uniffi.dylib \ --language kotlin \ --config uniffi-android.toml \ --out-dir build/generated/kotlin-android - name: Upload Kotlin bindings uses: actions/upload-artifact@v4 with: name: kotlin-android-bindings path: ${{ env.UNIFFI_DIRECTORY }}/build/generated/kotlin-android if-no-files-found: error - name: Print directory tree run: tree build package-android-aar: runs-on: ubuntu-latest needs: [ build-android, prepare-android-bindings ] defaults: run: working-directory: ${{ env.UNIFFI_DIRECTORY }} env: ANDROID_BUILD_DIR: android-build steps: - uses: actions/checkout@v3 - name: Setup Java uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '17' - name: Setup Android SDK uses: android-actions/setup-android@v2 id: setup-android - name: Download Kotlin bindings uses: actions/download-artifact@v4 with: name: kotlin-android-bindings path: ${{ env.UNIFFI_DIRECTORY }}/kotlin-bindings - name: Download Android native libraries uses: actions/download-artifact@v4 with: name: android-libs path: ${{ env.UNIFFI_DIRECTORY }}/android-libs - name: Create AAR structure run: | rm -rf "${{ env.ANDROID_BUILD_DIR }}" mkdir -p "${{ env.ANDROID_BUILD_DIR }}/zen-engine-android/src/main/java" mkdir -p "${{ env.ANDROID_BUILD_DIR }}/zen-engine-android/src/main/jniLibs" mkdir -p "${{ env.ANDROID_BUILD_DIR }}/gradle/wrapper" - name: Copy native libraries run: | cp -r android-libs/* "${{ env.ANDROID_BUILD_DIR }}/zen-engine-android/src/main/jniLibs/" - name: Copy Kotlin bindings run: | cp -r kotlin-bindings/* "${{ env.ANDROID_BUILD_DIR }}/zen-engine-android/src/main/java/" - name: Copy custom Kotlin lib run: | cp -r lib/kotlin/* "${{ env.ANDROID_BUILD_DIR }}/zen-engine-android/src/main/java/" - name: Copy Gradle configuration files run: | cp gradlew "${{ env.ANDROID_BUILD_DIR }}" cp -r gradle "${{ env.ANDROID_BUILD_DIR }}" cp gradle.properties "${{ env.ANDROID_BUILD_DIR }}" - name: Copy Android Gradle files from repo run: | cp gradle.android/build.gradle.kts "${{ env.ANDROID_BUILD_DIR }}/" cp gradle.android/settings.gradle.kts "${{ env.ANDROID_BUILD_DIR }}/" cp gradle.android/zen-engine-android-build.gradle.kts "${{ env.ANDROID_BUILD_DIR }}/zen-engine-android/build.gradle.kts" cp gradle.android/AndroidManifest.xml "${{ env.ANDROID_BUILD_DIR }}" cp gradle.android/gradle.properties "${{ env.ANDROID_BUILD_DIR }}" - name: Print directory tree for ${{ env.ANDROID_BUILD_DIR }} run: tree ${{ env.ANDROID_BUILD_DIR }} - name: Build AAR with Gradle env: ANDROID_HOME: ${{ env.ANDROID_SDK_ROOT }} ANDROID_NDK_HOME: ${{ env.ANDROID_NDK_ROOT }} run: | cd "${{ env.ANDROID_BUILD_DIR }}" chmod +x gradlew ./gradlew :zen-engine-android:assembleRelease --no-daemon --stacktrace - name: Verify AAR file run: | AAR_FILE="${{ env.ANDROID_BUILD_DIR }}/zen-engine-android/build/outputs/aar/zen-engine-android-release.aar" if [ -f "$AAR_FILE" ]; then ls -lh "$AAR_FILE" unzip -l "$AAR_FILE" | grep -E "\.so|\.class|AndroidManifest" | head -20 else echo "AAR file not found!" exit 1 fi - name: Upload AAR artifact uses: actions/upload-artifact@v4 with: name: android-aar path: ${{ env.UNIFFI_DIRECTORY }}/${{ env.ANDROID_BUILD_DIR }}/zen-engine-android/build/outputs/aar/zen-engine-android-release.aar if-no-files-found: error release-android: runs-on: ubuntu-latest environment: release needs: [ package-android-aar ] if: "github.ref_type == 'tag'" defaults: run: working-directory: ${{ env.UNIFFI_DIRECTORY }} steps: - uses: actions/checkout@v3 - name: Set up JDK 21 uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - name: Download Kotlin bindings uses: actions/download-artifact@v4 with: name: kotlin-android-bindings path: ${{ env.UNIFFI_DIRECTORY }}/build/generated/kotlin-android - name: Download Android AAR artifact uses: actions/download-artifact@v4 with: name: android-aar path: ${{ env.UNIFFI_DIRECTORY }}/build-android/aar/ - name: Publish Android Kotlin package to Maven Central env: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} GPG_SIGNING_PASSPHRASE: ${{ secrets.GPG_SIGNING_PASSPHRASE }} run: ./gradlew publishAllPublicationsToCentralPortal -PpublishScope=android