Release #25
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: >- | |
| Release version (e.g. 0.3.1). Triggers full release: builds Swift | |
| XCFramework, updates Package.swift, creates tag, publishes all | |
| registries. Leave empty to re-trigger individual registries. | |
| type: string | |
| default: '' | |
| crates-io: | |
| description: Publish iscc-lib to crates.io | |
| type: boolean | |
| default: false | |
| pypi: | |
| description: Publish iscc-lib to PyPI | |
| type: boolean | |
| default: false | |
| npm: | |
| description: Publish @iscc/lib and @iscc/wasm to npm | |
| type: boolean | |
| default: false | |
| maven: | |
| description: Publish iscc-lib to Maven Central | |
| type: boolean | |
| default: false | |
| ffi: | |
| description: Build and publish FFI tarballs to GitHub Releases | |
| type: boolean | |
| default: false | |
| rubygems: | |
| description: Publish iscc-lib to RubyGems | |
| type: boolean | |
| default: false | |
| nuget: | |
| description: Publish Iscc.Lib to NuGet | |
| type: boolean | |
| default: false | |
| maven-kotlin: | |
| description: Publish iscc-lib-kotlin to Maven Central | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false # Never cancel a release in progress | |
| jobs: | |
| # Build Swift XCFramework, update Package.swift checksum, commit to main, | |
| # create release tag and GitHub Release. Runs only for full releases (version | |
| # input provided). All other publish jobs wait for this to complete so the tag | |
| # exists before they start. | |
| prepare-release: | |
| name: Prepare release | |
| if: inputs.version != '' | |
| runs-on: macos-14 | |
| permissions: | |
| contents: write | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: main | |
| - name: Validate version | |
| id: version | |
| run: | | |
| CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| if [ "$CARGO_VERSION" != "${{ inputs.version }}" ]; then | |
| echo "::error::Cargo.toml version ($CARGO_VERSION) != input version (${{ inputs.version }})" | |
| exit 1 | |
| fi | |
| echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT" | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: >- | |
| aarch64-apple-darwin,x86_64-apple-darwin, | |
| aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios | |
| - uses: Swatinem/rust-cache@v2 | |
| - uses: actions/cache@v4 | |
| id: xcf-cache | |
| with: | |
| path: target/ios/IsccLib.xcframework.zip | |
| key: xcf-${{ hashFiles( 'crates/iscc-*/src/**', 'crates/iscc-*/Cargo.toml', | |
| 'Cargo.lock', 'Cargo.toml', 'scripts/build_xcframework.sh', 'packages/swift/Sources/iscc_uniffiFFI/**' | |
| ) }} | |
| - name: Build XCFramework | |
| if: steps.xcf-cache.outputs.cache-hit != 'true' | |
| run: ./scripts/build_xcframework.sh --release | |
| - name: Update Package.swift checksum | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| CHECKSUM=$(swift package compute-checksum target/ios/IsccLib.xcframework.zip) | |
| sed -E -i '' \ | |
| "s/(let releaseTag = \")[^\"]+/\1$VERSION/" Package.swift | |
| sed -E -i '' \ | |
| "s/(let releaseChecksum = \")[^\"]+/\1$CHECKSUM/" Package.swift | |
| - name: Commit Package.swift | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Package.swift | |
| if git diff --cached --quiet; then | |
| echo "Package.swift unchanged, skipping commit" | |
| else | |
| git commit -m "chore: update Swift XCFramework checksum for v${{ inputs.version }}" | |
| git push origin main | |
| fi | |
| - name: Create tags | |
| run: | | |
| git tag "v${{ inputs.version }}" 2>/dev/null || echo "Tag v${{ inputs.version }} already exists" | |
| git tag "packages/go/v${{ inputs.version }}" 2>/dev/null || echo "Tag packages/go/v${{ inputs.version }} already exists" | |
| git push origin "v${{ inputs.version }}" "packages/go/v${{ inputs.version }}" --force | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ inputs.version }} | |
| files: target/ios/IsccLib.xcframework.zip | |
| generate_release_notes: true | |
| # Publish Rust core crate to crates.io via OIDC trusted publishing | |
| publish-crates-io: | |
| name: Publish to crates.io | |
| needs: [prepare-release] | |
| if: ${{ !cancelled() && !failure() && (inputs.version != '' || inputs.crates-io) }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Get workspace version | |
| id: version | |
| run: | | |
| VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Check version on registry | |
| id: check | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| if cargo info iscc-lib 2>/dev/null | grep -q "version: $VERSION"; then | |
| echo "Version $VERSION already published to crates.io, skipping" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Run tests | |
| if: steps.check.outputs.skip != 'true' | |
| run: cargo test --workspace --exclude iscc-rb | |
| - name: Authenticate with crates.io | |
| if: steps.check.outputs.skip != 'true' | |
| id: crates-auth | |
| uses: rust-lang/crates-io-auth-action@v1 | |
| - name: Publish iscc-lib | |
| if: steps.check.outputs.skip != 'true' | |
| run: cargo publish -p iscc-lib | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }} | |
| # Build cross-platform Python wheels using maturin | |
| build-wheels: | |
| name: Build wheels (${{ matrix.target }}) | |
| needs: [prepare-release] | |
| if: ${{ !cancelled() && !failure() && (inputs.version != '' || inputs.pypi) }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64 | |
| interpreter: python3.10 | |
| - os: macos-14 | |
| target: universal2-apple-darwin | |
| interpreter: python3.10 | |
| - os: windows-latest | |
| target: x64 | |
| interpreter: python | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.10' | |
| - name: Build wheel | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| target: ${{ matrix.target }} | |
| args: --release --out dist --manifest-path crates/iscc-py/Cargo.toml -i | |
| ${{ matrix.interpreter }} | |
| manylinux: auto | |
| before-script-linux: | | |
| # Put Python 3.10 first in PATH so maturin uses it for abi3 wheel tagging. | |
| # Without this, the manylinux container defaults to Python 3.8 which is below | |
| # the abi3-py310 minimum and produces a version-specific cp38 wheel instead. | |
| export PATH="/opt/python/cp310-cp310/bin:$PATH" | |
| - name: Upload wheels | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: wheels-${{ matrix.os }}-${{ matrix.target }} | |
| path: dist | |
| # Build source distribution | |
| build-sdist: | |
| name: Build sdist | |
| needs: [prepare-release] | |
| if: ${{ !cancelled() && !failure() && (inputs.version != '' || inputs.pypi) }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build sdist | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| command: sdist | |
| args: --out dist --manifest-path crates/iscc-py/Cargo.toml | |
| - name: Upload sdist | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: wheels-sdist | |
| path: dist | |
| # Smoke test Python wheel on Linux before publishing | |
| test-wheels: | |
| name: Test Python wheel | |
| needs: [build-wheels] | |
| if: inputs.version != '' || inputs.pypi | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.10' | |
| - name: Download wheel | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: wheels-ubuntu-latest-x86_64 | |
| path: dist | |
| - name: Install and test | |
| run: | | |
| pip install dist/*.whl | |
| python -c "from iscc_lib import conformance_selftest; assert conformance_selftest()" | |
| # Publish all wheels and sdist to PyPI via OIDC trusted publishing | |
| publish-pypi: | |
| name: Publish to PyPI | |
| needs: [build-wheels, build-sdist, test-wheels] | |
| if: inputs.version != '' || inputs.pypi | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Get workspace version | |
| id: version | |
| run: | | |
| VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Check version on registry | |
| id: check | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| if curl -sf "https://pypi.org/pypi/iscc-lib/$VERSION/json" > /dev/null 2>&1; then | |
| echo "Version $VERSION already published to PyPI, skipping" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Download all artifacts | |
| if: steps.check.outputs.skip != 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: wheels-* | |
| path: dist | |
| merge-multiple: true | |
| - name: Publish to PyPI | |
| if: steps.check.outputs.skip != 'true' | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: dist | |
| # Build napi-rs native addons for cross-platform npm publishing | |
| build-napi: | |
| name: Build napi (${{ matrix.target }}) | |
| needs: [prepare-release] | |
| if: ${{ !cancelled() && !failure() && (inputs.version != '' || inputs.npm) }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| - os: ubuntu-latest | |
| target: aarch64-unknown-linux-gnu | |
| - os: macos-14 | |
| target: aarch64-apple-darwin | |
| - os: macos-14 | |
| target: x86_64-apple-darwin | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Install npm dependencies | |
| run: npm install | |
| working-directory: crates/iscc-napi | |
| - name: Install cross-compiler | |
| if: matrix.target == 'aarch64-unknown-linux-gnu' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu | |
| - name: Build native addon | |
| run: npx napi build --platform --release --target ${{ matrix.target }} | |
| working-directory: crates/iscc-napi | |
| env: | |
| CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: napi-${{ matrix.target }} | |
| path: | | |
| crates/iscc-napi/*.node | |
| crates/iscc-napi/index.js | |
| crates/iscc-napi/index.d.ts | |
| # Smoke test NAPI native addon on Linux before publishing | |
| test-napi: | |
| name: Test NAPI addon | |
| needs: [build-napi] | |
| if: inputs.version != '' || inputs.npm | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Download addon | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: napi-x86_64-unknown-linux-gnu | |
| path: addon | |
| - name: Test native addon | |
| run: node -e "const m = require('./addon/iscc-lib.linux-x64-gnu.node'); if | |
| (!m.conformance_selftest()) { throw new Error('conformance failed'); }" | |
| # Publish @iscc/lib to npm | |
| publish-npm-lib: | |
| name: Publish @iscc/lib to npm | |
| needs: [build-napi, test-napi] | |
| if: inputs.version != '' || inputs.npm | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| registry-url: https://registry.npmjs.org | |
| - name: Install npm dependencies | |
| run: npm install | |
| working-directory: crates/iscc-napi | |
| - name: Download all napi artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: napi-* | |
| path: crates/iscc-napi | |
| merge-multiple: true | |
| - name: Prepare npm packages | |
| run: npx napi prepublish -t npm | |
| working-directory: crates/iscc-napi | |
| - name: Get workspace version | |
| id: version | |
| run: | | |
| VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Check version on registry | |
| id: check | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| if npm view "@iscc/lib@$VERSION" version 2>/dev/null; then | |
| echo "Version $VERSION already published to npm, skipping" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Publish to npm | |
| if: steps.check.outputs.skip != 'true' | |
| run: npm publish --provenance --access public | |
| working-directory: crates/iscc-napi | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| # Build WASM package for npm publishing | |
| build-wasm: | |
| name: Build WASM package | |
| needs: [prepare-release] | |
| if: ${{ !cancelled() && !failure() && (inputs.version != '' || inputs.npm) }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Install wasm-pack | |
| run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh | |
| - name: Build WASM package | |
| run: wasm-pack build --target web --release crates/iscc-wasm --features conformance | |
| - name: Set npm package name and version | |
| run: | | |
| node <<'SCRIPT' | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('crates/iscc-wasm/pkg/package.json', 'utf8')); | |
| pkg.name = '@iscc/wasm'; | |
| pkg.repository = { type: 'git', url: 'https://github.com/iscc/iscc-lib' }; | |
| const toml = fs.readFileSync('Cargo.toml', 'utf8'); | |
| const m = toml.match(/^version\s*=\s*"(.+?)"/m); | |
| if (m) pkg.version = m[1]; | |
| fs.writeFileSync('crates/iscc-wasm/pkg/package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| SCRIPT | |
| - name: Upload WASM package | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: wasm-pkg | |
| path: crates/iscc-wasm/pkg/ | |
| # Smoke test WASM package on Linux before publishing | |
| test-wasm: | |
| name: Test WASM package | |
| needs: [build-wasm] | |
| if: inputs.version != '' || inputs.npm | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Download WASM package | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: wasm-pkg | |
| path: pkg | |
| - name: Test WASM module | |
| run: | | |
| cat > smoke.mjs << 'SCRIPT' | |
| import { readFileSync } from 'fs'; | |
| import init, { conformance_selftest } from './pkg/iscc_wasm.js'; | |
| const wasmBytes = readFileSync('./pkg/iscc_wasm_bg.wasm'); | |
| await init(wasmBytes); | |
| if (!conformance_selftest()) { throw new Error('conformance failed'); } | |
| SCRIPT | |
| node smoke.mjs | |
| # Publish @iscc/wasm to npm | |
| publish-npm-wasm: | |
| name: Publish @iscc/wasm to npm | |
| needs: [build-wasm, test-wasm] | |
| if: inputs.version != '' || inputs.npm | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| registry-url: https://registry.npmjs.org | |
| - name: Download WASM package | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: wasm-pkg | |
| path: pkg | |
| - name: Get package version | |
| id: version | |
| run: | | |
| VERSION=$(node -p "require('./pkg/package.json').version") | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Check version on registry | |
| id: check | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| if npm view "@iscc/wasm@$VERSION" version 2>/dev/null; then | |
| echo "Version $VERSION already published to npm, skipping" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Publish to npm | |
| if: steps.check.outputs.skip != 'true' | |
| run: npm publish --provenance --access public | |
| working-directory: pkg | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| # Build JNI native libraries for cross-platform JAR packaging | |
| build-jni: | |
| name: Build JNI (${{ matrix.native-dir }}) | |
| needs: [prepare-release] | |
| if: ${{ !cancelled() && !failure() && (inputs.version != '' || inputs.maven) }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| native-dir: linux-x86_64 | |
| lib-name: libiscc_jni.so | |
| - os: ubuntu-latest | |
| target: aarch64-unknown-linux-gnu | |
| native-dir: linux-aarch64 | |
| lib-name: libiscc_jni.so | |
| - os: macos-14 | |
| target: aarch64-apple-darwin | |
| native-dir: macos-aarch64 | |
| lib-name: libiscc_jni.dylib | |
| - os: macos-14 | |
| target: x86_64-apple-darwin | |
| native-dir: macos-x86_64 | |
| lib-name: libiscc_jni.dylib | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| native-dir: windows-x86_64 | |
| lib-name: iscc_jni.dll | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Install cross-compiler | |
| if: matrix.target == 'aarch64-unknown-linux-gnu' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu | |
| - name: Build JNI library | |
| run: cargo build -p iscc-jni --release --target ${{ matrix.target }} | |
| env: | |
| CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: jni-${{ matrix.native-dir }} | |
| path: target/${{ matrix.target }}/release/${{ matrix.lib-name }} | |
| # Assemble cross-platform JAR with bundled native libraries | |
| assemble-jar: | |
| name: Assemble JAR | |
| needs: [build-jni] | |
| if: inputs.version != '' || inputs.maven | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Download JNI artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: jni-* | |
| path: jni-staging | |
| - name: Copy native libraries to resources | |
| run: | | |
| for dir in jni-staging/jni-*/; do | |
| native_dir=$(basename "$dir" | sed 's/^jni-//') | |
| dest="crates/iscc-jni/java/src/main/resources/META-INF/native/$native_dir" | |
| mkdir -p "$dest" | |
| cp "$dir"* "$dest/" | |
| done | |
| - name: Build JAR | |
| run: mvn package -DskipTests -f crates/iscc-jni/java/pom.xml | |
| - name: Upload JAR | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: iscc-lib-jar | |
| path: crates/iscc-jni/java/target/*.jar | |
| # Smoke test JNI native libraries on Linux before publishing | |
| test-jni: | |
| name: Test JNI libraries | |
| needs: [build-jni] | |
| if: inputs.version != '' || inputs.maven | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Download JNI artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: jni-* | |
| path: jni-staging | |
| - name: Copy native libraries to resources | |
| run: | | |
| for dir in jni-staging/jni-*/; do | |
| native_dir=$(basename "$dir" | sed 's/^jni-//') | |
| dest="crates/iscc-jni/java/src/main/resources/META-INF/native/$native_dir" | |
| mkdir -p "$dest" | |
| cp "$dir"* "$dest/" | |
| done | |
| - name: Run tests | |
| run: mvn test -f crates/iscc-jni/java/pom.xml | |
| # Publish Java JAR to Maven Central via Sonatype Central Portal | |
| publish-maven: | |
| name: Publish to Maven Central | |
| needs: [build-jni, test-jni] | |
| if: inputs.version != '' || inputs.maven | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| server-id: central | |
| server-username: MAVEN_USERNAME | |
| server-password: MAVEN_PASSWORD | |
| - name: Get workspace version | |
| id: version | |
| run: | | |
| VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Check version on Maven Central | |
| id: check | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| if curl -sf "https://search.maven.org/solrsearch/select?q=g:io.iscc+AND+a:iscc-lib+AND+v:$VERSION&rows=1&wt=json" | grep -q '"numFound":1'; then | |
| echo "Version $VERSION already published to Maven Central, skipping" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Download JNI artifacts | |
| if: steps.check.outputs.skip != 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: jni-* | |
| path: jni-staging | |
| - name: Copy native libraries to resources | |
| if: steps.check.outputs.skip != 'true' | |
| run: | | |
| for dir in jni-staging/jni-*/; do | |
| native_dir=$(basename "$dir" | sed 's/^jni-//') | |
| dest="crates/iscc-jni/java/src/main/resources/META-INF/native/$native_dir" | |
| mkdir -p "$dest" | |
| cp "$dir"* "$dest/" | |
| done | |
| - name: Import GPG key | |
| if: steps.check.outputs.skip != 'true' | |
| run: | | |
| echo "${{ secrets.MAVEN_GPG_PRIVATE_KEY }}" | gpg --batch --import || true | |
| gpg --list-secret-keys --keyid-format LONG | grep -q "sec" || { echo "GPG key import failed"; exit 1; } | |
| - name: Publish to Maven Central | |
| if: steps.check.outputs.skip != 'true' | |
| run: mvn deploy -Prelease -DskipTests -f crates/iscc-jni/java/pom.xml | |
| env: | |
| MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} | |
| MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} | |
| MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} | |
| # Build FFI shared/static libraries for C/C++ consumers | |
| build-ffi: | |
| name: Build FFI (${{ matrix.target }}) | |
| needs: [prepare-release] | |
| if: ${{ !cancelled() && !failure() && (inputs.version != '' || inputs.ffi || inputs.nuget) }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| lib-shared: libiscc_ffi.so | |
| lib-static: libiscc_ffi.a | |
| - os: ubuntu-latest | |
| target: aarch64-unknown-linux-gnu | |
| lib-shared: libiscc_ffi.so | |
| lib-static: libiscc_ffi.a | |
| - os: macos-14 | |
| target: aarch64-apple-darwin | |
| lib-shared: libiscc_ffi.dylib | |
| lib-static: libiscc_ffi.a | |
| - os: macos-14 | |
| target: x86_64-apple-darwin | |
| lib-shared: libiscc_ffi.dylib | |
| lib-static: libiscc_ffi.a | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| lib-shared: iscc_ffi.dll | |
| lib-static: iscc_ffi.lib | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Install cross-compiler | |
| if: matrix.target == 'aarch64-unknown-linux-gnu' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu | |
| - name: Build FFI library | |
| run: cargo build -p iscc-ffi --release --target ${{ matrix.target }} | |
| env: | |
| CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc | |
| - name: Get workspace version | |
| id: version | |
| shell: bash | |
| run: | | |
| VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Stage artifacts (Unix) | |
| if: runner.os != 'Windows' | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| TARGET="${{ matrix.target }}" | |
| DIR="iscc-ffi-v${VERSION}-${TARGET}" | |
| mkdir -p "$DIR" | |
| cp "target/${TARGET}/release/${{ matrix.lib-shared }}" "$DIR/" | |
| cp "target/${TARGET}/release/${{ matrix.lib-static }}" "$DIR/" | |
| cp crates/iscc-ffi/include/iscc.h "$DIR/" | |
| cp packages/cpp/include/iscc/iscc.hpp "$DIR/" | |
| cp LICENSE "$DIR/" | |
| tar czf "${DIR}.tar.gz" "$DIR" | |
| - name: Stage artifacts (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $VERSION = "${{ steps.version.outputs.version }}" | |
| $TARGET = "${{ matrix.target }}" | |
| $DIR = "iscc-ffi-v${VERSION}-${TARGET}" | |
| New-Item -ItemType Directory -Path $DIR -Force | |
| Copy-Item "target/${TARGET}/release/iscc_ffi.dll" "$DIR/" | |
| Copy-Item "target/${TARGET}/release/iscc_ffi.dll.lib" "$DIR/" | |
| Copy-Item "target/${TARGET}/release/iscc_ffi.lib" "$DIR/" | |
| Copy-Item "crates/iscc-ffi/include/iscc.h" "$DIR/" | |
| Copy-Item "packages/cpp/include/iscc/iscc.hpp" "$DIR/" | |
| Copy-Item "LICENSE" "$DIR/" | |
| Compress-Archive -Path "$DIR" -DestinationPath "${DIR}.zip" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ffi-${{ matrix.target }} | |
| path: iscc-ffi-v*.* | |
| # Smoke test FFI shared library on Linux before publishing | |
| test-ffi: | |
| name: Test FFI library | |
| needs: [build-ffi] | |
| if: inputs.version != '' || inputs.ffi | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download FFI artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ffi-x86_64-unknown-linux-gnu | |
| path: ffi-staging | |
| - name: Extract and test | |
| run: | | |
| tar xzf ffi-staging/*.tar.gz | |
| LIB_DIR=$(find . -maxdepth 1 -name 'iscc-ffi-v*' -type d) | |
| cc -o test_iscc crates/iscc-ffi/tests/test_iscc.c \ | |
| -I"$LIB_DIR" -L"$LIB_DIR" -liscc_ffi -lpthread -ldl -lm | |
| LD_LIBRARY_PATH="$LIB_DIR" ./test_iscc | |
| # Publish FFI tarballs to GitHub Releases | |
| publish-ffi: | |
| name: Publish FFI to GitHub Releases | |
| needs: [build-ffi, test-ffi] | |
| if: inputs.version != '' || inputs.ffi | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Get workspace version | |
| id: version | |
| run: | | |
| VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Download all FFI artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: ffi-* | |
| path: ffi-staging | |
| merge-multiple: true | |
| - name: List artifacts | |
| run: ls -lR ffi-staging/ | |
| - name: Upload release assets | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ steps.version.outputs.version }} | |
| files: ffi-staging/iscc-ffi-v*.* | |
| # Build cross-platform precompiled Ruby gems via rb-sys-dock | |
| build-gem: | |
| name: Build gem (${{ matrix.platform }}) | |
| needs: [prepare-release] | |
| if: ${{ !cancelled() && !failure() && (inputs.version != '' || inputs.rubygems) }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| - x86_64-linux | |
| - aarch64-linux | |
| - x86_64-darwin | |
| - arm64-darwin | |
| - x64-mingw-ucrt | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: '3.3' | |
| bundler-cache: true | |
| working-directory: crates/iscc-rb | |
| - name: Link Gemfile.lock for rb_sys version detection | |
| # The cross-gem action's configure step runs `grep rb_sys Gemfile.lock` | |
| # in the repo root (not working-directory). Without this symlink it | |
| # falls back to `gem info rb_sys --remote` -> 0.9.124, which installs | |
| # rake-compiler-dock 1.11.0 (maps "3.3" -> "3.3.10" -- missing from | |
| # the 0.9.123 Docker image that only has rbconfig for 3.3.9). | |
| run: ln -sf crates/iscc-rb/Gemfile.lock Gemfile.lock | |
| - name: Cross-compile gem | |
| uses: oxidize-rb/actions/cross-gem@v1 | |
| with: | |
| platform: ${{ matrix.platform }} | |
| ruby-versions: 3.1, 3.2, 3.3 | |
| working-directory: crates/iscc-rb | |
| # Pin to 0.9.123 Docker image (Ruby 3.4 build tool, rbconfigs for | |
| # 3.1.7/3.2.9/3.3.9). The 0.9.124 image has Ruby 4.0 which breaks | |
| # RbSys::ExtensionTask. Must match rb_sys in Gemfile.lock. | |
| tag: 0.9.123 | |
| - name: Upload gem | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: gem-${{ matrix.platform }} | |
| path: crates/iscc-rb/pkg/*-${{ matrix.platform }}.gem | |
| # Smoke test Ruby gem on Linux before publishing | |
| test-gem: | |
| name: Test Ruby gem | |
| needs: [build-gem] | |
| if: inputs.version != '' || inputs.rubygems | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: '3.3' | |
| - name: Download gem | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: gem-x86_64-linux | |
| path: gems | |
| - name: Install and test | |
| run: | | |
| gem install gems/*.gem | |
| ruby -e "require 'iscc_lib'; raise 'Conformance failed' unless IsccLib.conformance_selftest" | |
| # Publish precompiled Ruby gems and source gem to RubyGems.org | |
| publish-rubygems: | |
| name: Publish to RubyGems | |
| needs: [build-gem, test-gem] | |
| if: inputs.version != '' || inputs.rubygems | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: '3.3' | |
| - name: Configure RubyGems credentials (OIDC trusted publishing) | |
| uses: rubygems/configure-rubygems-credentials@main | |
| - name: Get workspace version | |
| id: version | |
| run: | | |
| VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Check version on RubyGems | |
| id: check | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| if curl -sf "https://rubygems.org/api/v1/versions/iscc-lib.json" | grep -q "\"number\":\"$VERSION\""; then | |
| echo "Version $VERSION already published to RubyGems, skipping" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Download gem artifacts | |
| if: steps.check.outputs.skip != 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: gem-* | |
| path: gems | |
| merge-multiple: true | |
| - name: Build source gem | |
| if: steps.check.outputs.skip != 'true' | |
| run: gem build iscc-lib.gemspec | |
| working-directory: crates/iscc-rb | |
| - name: Publish gems | |
| if: steps.check.outputs.skip != 'true' | |
| run: |- | |
| # Publish precompiled platform gems | |
| for gem in gems/*.gem; do | |
| echo "Publishing $gem" | |
| gem push "$gem" | |
| done | |
| # Publish source gem (fallback for platforms without precompiled gems) | |
| for gem in crates/iscc-rb/*.gem; do | |
| echo "Publishing $gem" | |
| gem push "$gem" | |
| done | |
| # Pack NuGet package with bundled native libraries for 5 platforms | |
| pack-nuget: | |
| name: Pack NuGet package | |
| needs: [build-ffi] | |
| if: inputs.version != '' || inputs.nuget | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0' | |
| - name: Download FFI artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: ffi-* | |
| path: ffi-staging | |
| - name: Extract and map native libraries to NuGet RIDs | |
| run: | | |
| PROJ_DIR="packages/dotnet/Iscc.Lib" | |
| declare -A TARGET_RID=( | |
| ["x86_64-unknown-linux-gnu"]="linux-x64" | |
| ["aarch64-unknown-linux-gnu"]="linux-arm64" | |
| ["aarch64-apple-darwin"]="osx-arm64" | |
| ["x86_64-apple-darwin"]="osx-x64" | |
| ["x86_64-pc-windows-msvc"]="win-x64" | |
| ) | |
| declare -A TARGET_LIB=( | |
| ["x86_64-unknown-linux-gnu"]="libiscc_ffi.so" | |
| ["aarch64-unknown-linux-gnu"]="libiscc_ffi.so" | |
| ["aarch64-apple-darwin"]="libiscc_ffi.dylib" | |
| ["x86_64-apple-darwin"]="libiscc_ffi.dylib" | |
| ["x86_64-pc-windows-msvc"]="iscc_ffi.dll" | |
| ) | |
| for target in "${!TARGET_RID[@]}"; do | |
| rid="${TARGET_RID[$target]}" | |
| lib="${TARGET_LIB[$target]}" | |
| dest="$PROJ_DIR/runtimes/$rid/native" | |
| mkdir -p "$dest" | |
| # Extract archive (tar.gz for Unix, zip for Windows) | |
| artifact_dir="ffi-staging/ffi-$target" | |
| if ls "$artifact_dir"/*.tar.gz 1>/dev/null 2>&1; then | |
| tar xzf "$artifact_dir"/*.tar.gz | |
| elif ls "$artifact_dir"/*.zip 1>/dev/null 2>&1; then | |
| unzip -o "$artifact_dir"/*.zip | |
| fi | |
| # Find and copy the shared library (scope by target to avoid cross-arch matches) | |
| find . -maxdepth 2 -name "$lib" -path "*-${target}/*" -exec cp {} "$dest/" \; | |
| echo "Copied $lib -> $dest/" | |
| done | |
| echo "Runtime layout:" | |
| find "$PROJ_DIR/runtimes" -type f | |
| - name: Pack NuGet package | |
| run: dotnet pack -c Release -o nupkg | |
| working-directory: packages/dotnet/Iscc.Lib | |
| - name: Upload NuGet package | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: nuget-package | |
| path: packages/dotnet/Iscc.Lib/nupkg/*.nupkg | |
| # Smoke test NuGet package on all platforms before publishing | |
| test-nuget: | |
| name: Test NuGet (${{ matrix.os }}) | |
| needs: [pack-nuget] | |
| if: inputs.version != '' || inputs.nuget | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0' | |
| - name: Download NuGet package | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: nuget-package | |
| path: nupkg | |
| - name: Create and run smoke test (Unix) | |
| if: runner.os != 'Windows' | |
| run: | | |
| dotnet new console -o smoke | |
| cd smoke | |
| dotnet nuget add source "$PWD/../nupkg" -n local | |
| dotnet add package Iscc.Lib | |
| cat > Program.cs << 'EOF' | |
| using Iscc.Lib; | |
| if (!IsccLib.ConformanceSelftest()) | |
| throw new Exception("Conformance selftest failed"); | |
| Console.WriteLine("NuGet smoke test passed"); | |
| EOF | |
| dotnet run | |
| - name: Create and run smoke test (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| dotnet new console -o smoke | |
| cd smoke | |
| dotnet nuget add source "$PWD\..\nupkg" -n local | |
| dotnet add package Iscc.Lib | |
| @" | |
| using Iscc.Lib; | |
| if (!IsccLib.ConformanceSelftest()) | |
| throw new Exception("Conformance selftest failed"); | |
| Console.WriteLine("NuGet smoke test passed"); | |
| "@ | Set-Content Program.cs | |
| dotnet run | |
| # Publish Iscc.Lib to NuGet.org | |
| publish-nuget: | |
| name: Publish to NuGet | |
| needs: [pack-nuget, test-nuget] | |
| if: inputs.version != '' || inputs.nuget | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0' | |
| - name: Get workspace version | |
| id: version | |
| run: | | |
| VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Check version on NuGet | |
| id: check | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| if curl -sf "https://api.nuget.org/v3-flatcontainer/iscc.lib/$VERSION/iscc.lib.nuspec" > /dev/null 2>&1; then | |
| echo "Version $VERSION already published to NuGet, skipping" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Download NuGet package | |
| if: steps.check.outputs.skip != 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: nuget-package | |
| path: nupkg | |
| - name: Publish to NuGet | |
| if: steps.check.outputs.skip != 'true' | |
| run: dotnet nuget push nupkg/*.nupkg --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json | |
| env: | |
| NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} | |
| # Build UniFFI native libraries for Kotlin JVM JAR packaging | |
| build-kotlin-native: | |
| name: Build Kotlin native (${{ matrix.native-dir }}) | |
| needs: [prepare-release] | |
| if: ${{ !cancelled() && !failure() && (inputs.version != '' || inputs.maven-kotlin) }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| native-dir: linux-x86-64 | |
| lib-name: libiscc_uniffi.so | |
| - os: ubuntu-latest | |
| target: aarch64-unknown-linux-gnu | |
| native-dir: linux-aarch64 | |
| lib-name: libiscc_uniffi.so | |
| - os: macos-14 | |
| target: aarch64-apple-darwin | |
| native-dir: darwin-aarch64 | |
| lib-name: libiscc_uniffi.dylib | |
| - os: macos-14 | |
| target: x86_64-apple-darwin | |
| native-dir: darwin-x86-64 | |
| lib-name: libiscc_uniffi.dylib | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| native-dir: win32-x86-64 | |
| lib-name: iscc_uniffi.dll | |
| - os: ubuntu-latest | |
| target: aarch64-linux-android | |
| native-dir: android-aarch64 | |
| lib-name: libiscc_uniffi.so | |
| android-abi: arm64-v8a | |
| - os: ubuntu-latest | |
| target: armv7-linux-androideabi | |
| native-dir: android-arm | |
| lib-name: libiscc_uniffi.so | |
| android-abi: armeabi-v7a | |
| - os: ubuntu-latest | |
| target: x86_64-linux-android | |
| native-dir: android-x86-64 | |
| lib-name: libiscc_uniffi.so | |
| android-abi: x86_64 | |
| - os: ubuntu-latest | |
| target: i686-linux-android | |
| native-dir: android-x86 | |
| lib-name: libiscc_uniffi.so | |
| android-abi: x86 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Install cross-compiler | |
| if: matrix.target == 'aarch64-unknown-linux-gnu' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu | |
| - name: Setup Android NDK | |
| if: contains(matrix.target, 'android') | |
| uses: nttld/setup-ndk@v1 | |
| id: setup-ndk | |
| with: | |
| ndk-version: r27c | |
| add-to-path: false | |
| - name: Install cargo-ndk | |
| if: contains(matrix.target, 'android') | |
| run: cargo install cargo-ndk | |
| - name: Build UniFFI library | |
| if: "!contains(matrix.target, 'android')" | |
| run: cargo build -p iscc-uniffi --release --target ${{ matrix.target }} | |
| env: | |
| CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc | |
| - name: Build UniFFI library (Android) | |
| if: contains(matrix.target, 'android') | |
| run: cargo ndk --target ${{ matrix.android-abi }} build -p iscc-uniffi --release | |
| env: | |
| ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: kotlin-native-${{ matrix.native-dir }} | |
| path: target/${{ matrix.target }}/release/${{ matrix.lib-name }} | |
| # Assemble Kotlin JAR with bundled native libraries for JNA | |
| assemble-kotlin: | |
| name: Assemble Kotlin JAR | |
| needs: [build-kotlin-native] | |
| if: inputs.version != '' || inputs.maven-kotlin | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Download native artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: kotlin-native-* | |
| path: kotlin-staging | |
| - name: Copy native libraries to JNA resource paths | |
| run: | | |
| for dir in kotlin-staging/kotlin-native-*/; do | |
| native_dir=$(basename "$dir" | sed 's/^kotlin-native-//') | |
| dest="packages/kotlin/src/main/resources/$native_dir" | |
| mkdir -p "$dest" | |
| cp "$dir"* "$dest/" | |
| done | |
| echo "JNA resource layout:" | |
| find packages/kotlin/src/main/resources -type f | |
| - name: Build JAR | |
| run: ./gradlew build | |
| working-directory: packages/kotlin | |
| - name: Upload JAR | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: kotlin-jar | |
| path: packages/kotlin/build/libs/*.jar | |
| # Smoke test Kotlin native libraries on Linux before publishing | |
| test-kotlin-release: | |
| name: Test Kotlin libraries | |
| needs: [build-kotlin-native, assemble-kotlin] | |
| if: inputs.version != '' || inputs.maven-kotlin | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Download linux-x86-64 native artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: kotlin-native-linux-x86-64 | |
| path: native-lib | |
| - name: Copy native library to JNA resource path | |
| run: | | |
| mkdir -p packages/kotlin/src/main/resources/linux-x86-64 | |
| cp native-lib/* packages/kotlin/src/main/resources/linux-x86-64/ | |
| - name: Run tests | |
| run: ./gradlew test | |
| working-directory: packages/kotlin | |
| - name: Download assembled JAR | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: kotlin-jar | |
| path: kotlin-jar | |
| - name: Validate JAR contains all native libraries | |
| run: | | |
| # Select the runtime JAR (exclude -sources and -javadoc classifier JARs) | |
| JAR=$(ls kotlin-jar/*.jar | grep -v '\-sources\.jar$\|\-javadoc\.jar$' | head -1) | |
| echo "Inspecting JAR: $JAR" | |
| JAR_CONTENTS=$(jar tf "$JAR") | |
| EXPECTED_PATHS=( | |
| "linux-x86-64/libiscc_uniffi.so" | |
| "linux-aarch64/libiscc_uniffi.so" | |
| "darwin-aarch64/libiscc_uniffi.dylib" | |
| "darwin-x86-64/libiscc_uniffi.dylib" | |
| "win32-x86-64/iscc_uniffi.dll" | |
| "android-aarch64/libiscc_uniffi.so" | |
| "android-arm/libiscc_uniffi.so" | |
| "android-x86-64/libiscc_uniffi.so" | |
| "android-x86/libiscc_uniffi.so" | |
| ) | |
| MISSING=() | |
| for path in "${EXPECTED_PATHS[@]}"; do | |
| if echo "$JAR_CONTENTS" | grep -q "$path"; then | |
| echo "OK: $path" | |
| else | |
| echo "MISSING: $path" | |
| MISSING+=("$path") | |
| fi | |
| done | |
| if [ ${#MISSING[@]} -ne 0 ]; then | |
| echo "" | |
| echo "ERROR: JAR is missing ${#MISSING[@]} native library path(s):" | |
| for m in "${MISSING[@]}"; do | |
| echo " - $m" | |
| done | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "All 9 native library resource paths present in JAR" | |
| # Publish Kotlin JAR to Maven Central via Sonatype Central Portal | |
| publish-maven-kotlin: | |
| name: Publish Kotlin to Maven Central | |
| needs: [assemble-kotlin, test-kotlin-release] | |
| if: inputs.version != '' || inputs.maven-kotlin | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Get workspace version | |
| id: version | |
| run: | | |
| VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Check version on Maven Central | |
| id: check | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| if curl -sf "https://search.maven.org/solrsearch/select?q=g:io.iscc+AND+a:iscc-lib-kotlin+AND+v:$VERSION&rows=1&wt=json" | grep -q '"numFound":1'; then | |
| echo "Version $VERSION already published to Maven Central, skipping" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Download native artifacts | |
| if: steps.check.outputs.skip != 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: kotlin-native-* | |
| path: kotlin-staging | |
| - name: Copy native libraries to JNA resource paths | |
| if: steps.check.outputs.skip != 'true' | |
| run: | | |
| for dir in kotlin-staging/kotlin-native-*/; do | |
| native_dir=$(basename "$dir" | sed 's/^kotlin-native-//') | |
| dest="packages/kotlin/src/main/resources/$native_dir" | |
| mkdir -p "$dest" | |
| cp "$dir"* "$dest/" | |
| done | |
| - name: Publish to staging with signatures | |
| if: steps.check.outputs.skip != 'true' | |
| run: ./gradlew publishMavenPublicationToStagingRepository | |
| working-directory: packages/kotlin | |
| env: | |
| SIGNING_KEY: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} | |
| SIGNING_PASSWORD: ${{ secrets.MAVEN_GPG_PASSPHRASE }} | |
| - name: Upload bundle to Central Portal | |
| if: steps.check.outputs.skip != 'true' | |
| run: | | |
| cd packages/kotlin/build/staging-deploy | |
| zip -r "$GITHUB_WORKSPACE/bundle.zip" . | |
| cd "$GITHUB_WORKSPACE" | |
| HTTP_CODE=$(curl -s -o response.txt -w "%{http_code}" \ | |
| -X POST "https://central.sonatype.com/api/v1/publisher/upload?publishingType=AUTOMATIC" \ | |
| -u "$MAVEN_USERNAME:$MAVEN_PASSWORD" \ | |
| -F "bundle=@bundle.zip") | |
| if [ "$HTTP_CODE" -ne 201 ]; then | |
| echo "Upload failed with HTTP $HTTP_CODE" | |
| cat response.txt | |
| exit 1 | |
| fi | |
| echo "Upload successful: $(cat response.txt)" | |
| env: | |
| MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} | |
| MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} |