diff --git a/.github/actions/android/action.yml b/.github/actions/android/action.yml deleted file mode 100644 index 5f28641..0000000 --- a/.github/actions/android/action.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: "Build Android library" -description: "Create artifact for Android library" -inputs: - sign-publication: - description: "Whether to sign the built library" - default: '1' - gpg-key: - required: false - description: "The GPG key to use when signing the publication" - gpg-password: - required: false - description: "Password for the GPG key." - -runs: - using: "composite" - steps: - - uses: actions/setup-java@v5 - with: - distribution: "temurin" - java-version: "25" - - - name: Validate Gradle wrapper - uses: gradle/actions/wrapper-validation@v4 - - - name: Setup - shell: bash - run: | - rustup toolchain install nightly-2026-04-10-x86_64-unknown-linux-gnu - rustup component add rust-src --toolchain nightly-2026-04-10-x86_64-unknown-linux-gnu - rustup target add \ - aarch64-linux-android \ - armv7-linux-androideabi \ - x86_64-linux-android \ - i686-linux-android - cargo install cargo-ndk - - - name: Build signed library - shell: bash - if: ${{ inputs.sign-publication == '1' }} - run: | - cd android - ./gradlew build zipPublication -PgpgKey=${INPUTS_GPG_KEY} -PgpgPassword=${INPUTS_GPG_PASSWORD} - ls -lh build/outputs/aar - find build/repository - env: - INPUTS_GPG_KEY: ${{ inputs.gpg-key }} - INPUTS_GPG_PASSWORD: ${{ inputs.gpg-password }} - - - name: Build library without signing - shell: bash - if: ${{ inputs.sign-publication == '0' }} - run: | - cd android - ./gradlew build zipPublication -PsignPublication=0 - ls -lh build/outputs/aar - find build/repository - - - name: Upload binary - uses: actions/upload-artifact@v4 - with: - name: android-library - retention-days: 14 - compression-level: 0 # We're uploading a zip, no need to compress again - path: android/build/distributions/powersync_android.zip - if-no-files-found: error - - - name: Build individual libraries for Dart SDK - shell: bash - run: | - cd android - ./gradlew buildRustStandalone - - - name: Copy individual libraries - shell: bash - run: | - cp android/build/standalone/arm64-v8a/libpowersync.so libpowersync_aarch64.android.so - cp android/build/standalone/armeabi-v7a/libpowersync.so libpowersync_armv7.android.so - cp android/build/standalone/x86/libpowersync.so libpowersync_x86.android.so - cp android/build/standalone/x86_64/libpowersync.so libpowersync_x64.android.so - - - name: Upload individual libraries - uses: actions/upload-artifact@v4 - with: - name: android-static - retention-days: 14 - path: | - *.so diff --git a/.github/actions/linux/action.yml b/.github/actions/linux/action.yml deleted file mode 100644 index 67da9e3..0000000 --- a/.github/actions/linux/action.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: "Build Linux libraries" -description: "Create artifact for Linux libraries" - -runs: - using: "composite" - steps: - - name: Install Rust Nightly - shell: bash - run: - rustup install --target aarch64-unknown-linux-gnu,x86_64-unknown-linux-gnu,i686-unknown-linux-gnu,riscv64gc-unknown-linux-gnu,armv7-unknown-linux-gnueabihf - - - name: Install cross-compiling GCC - shell: bash - run: | - sudo apt update - sudo apt install -y gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu gcc-arm-linux-gnueabihf gcc-i686-linux-gnu - - - name: Build binaries - shell: bash - run: | - ./tool/build_linux.sh x64 - ./tool/build_linux.sh aarch64 - ./tool/build_linux.sh x86 - ./tool/build_linux.sh armv7 - ./tool/build_linux.sh riscv64gc - - - uses: actions/upload-artifact@v4 - with: - name: linux-library - retention-days: 14 - path: | - *.so - *.linux.a diff --git a/.github/actions/macos/action.yml b/.github/actions/macos/action.yml deleted file mode 100644 index 4e07d7c..0000000 --- a/.github/actions/macos/action.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: "Build macoS libraries" -description: "Create artifact for macOS libraries" - -runs: - using: "composite" - steps: - - name: Install Rust Nightly - shell: bash - run: - rustup install --target x86_64-apple-darwin,aarch64-apple-darwin - - - name: Build binaries - shell: bash - run: | - ./tool/build_macos.sh x64 - ./tool/build_macos.sh aarch64 - - - uses: actions/upload-artifact@v4 - with: - name: macos-library - retention-days: 14 - path: | - *.dylib - *.a diff --git a/.github/actions/wasm/action.yml b/.github/actions/wasm/action.yml deleted file mode 100644 index 408f1a8..0000000 --- a/.github/actions/wasm/action.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: "Build wasm libraries" -description: "Create artifact for wasm libraries" - -runs: - using: "composite" - steps: - - name: Install Rust Nightly - shell: bash - run: rustup install - - - name: Build WASM - shell: bash - run: ./tool/build_wasm.sh - - - uses: actions/upload-artifact@v4 - with: - name: wasm-library - retention-days: 14 - path: libpowersync-wasm.a diff --git a/.github/actions/windows/action.yml b/.github/actions/windows/action.yml deleted file mode 100644 index 4ecf5f1..0000000 --- a/.github/actions/windows/action.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: "Build Windows libraries" -description: "Create artifact for Windows libraries" - -runs: - using: "composite" - steps: - - name: Install Rust Nightly - shell: powershell - run: rustup install --target x86_64-pc-windows-msvc,aarch64-pc-windows-msvc,i686-pc-windows-msvc - - - name: Build binaries - shell: bash - run: | - ./tool/build_windows.sh x64 - ./tool/build_windows.sh aarch64 - ./tool/build_windows.sh x86 - - - uses: actions/upload-artifact@v4 - with: - name: windows-library - retention-days: 14 - path: | - *.dll - *.lib diff --git a/.github/actions/xcframework/action.yml b/.github/actions/xcframework/action.yml deleted file mode 100644 index 2120784..0000000 --- a/.github/actions/xcframework/action.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: "Build xcframework" -description: "Create artifact with XCFramework for apple targets" - -runs: - using: "composite" - steps: - - name: Setup - shell: bash - run: | - rustup toolchain install nightly-2026-04-10-aarch64-apple-darwin - rustup component add rust-src --toolchain nightly-2026-04-10-aarch64-apple-darwin - rustup target add \ - x86_64-apple-darwin \ - aarch64-apple-darwin \ - aarch64-apple-ios \ - aarch64-apple-ios-sim \ - x86_64-apple-ios - - - name: setup-cocoapods - uses: maxim-lobanov/setup-cocoapods@8e97e1e98e6ccf42564fdf5622c8feec74199377 # v1.4.0 - with: - version: 1.16.2 - - - name: Set up XCode - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 - with: - xcode-version: latest-stable - - - name: List simulators - shell: bash - run: | - xcrun xctrace list devices - - - name: Build iOS & macOS xcframework - shell: bash - run: | - ./tool/build_xcframework.sh - - - name: Lint pod - shell: bash - run: | - pod lib lint - - - uses: actions/upload-artifact@v4 - with: - name: xcframework - retention-days: 14 - compression-level: 0 # We're uploading a zip archive, no need to compress agan - path: | - powersync-sqlite-core.xcframework.zip diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..4e45a80 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,383 @@ +name: Compile core extension binaries + +on: + workflow_call: + inputs: + attest: + description: "Whether to generate a signed attestation for compiled artifacts" + type: boolean + default: false + sign-publication: + description: "Whether to sign the built Android library" + default: false + type: boolean + secrets: + gpg-key: + required: false + description: "The GPG key to use when signing the publication" + gpg-password: + required: false + description: "Password for the GPG key." + outputs: + libs_linux: + description: "Artifact ID of the uploaded linux-library artifact" + value: ${{ jobs.libs_linux.outputs.artifact_id }} + libs_macos: + description: "Artifact ID of the uploaded macos-library artifact" + value: ${{ jobs.libs_macos.outputs.artifact_id }} + libs_windows: + description: "Artifact ID of the uploaded windows-library artifact" + value: ${{ jobs.libs_windows.outputs.artifact_id }} + libs_android: + description: "Artifact ID of the uploaded android-library artifact" + value: ${{ jobs.libs_android.outputs.artifact_id }} + libs_android_static: + description: "Artifact ID of the uploaded android-static artifact" + value: ${{ jobs.libs_android.outputs.static_artifact_id }} + libs_wasm: + description: "Artifact ID of the uploaded wasm-library artifact" + value: ${{ jobs.libs_wasm.outputs.artifact_id }} + libs_xcframework: + description: "Artifact ID of the uploaded xcframework artifact" + value: ${{ jobs.libs_xcframework.outputs.artifact_id }} + sbom: + description: "Artifact ID of the uploaded sbom artifact" + value: ${{ jobs.attest.outputs.artifact_id }} + +permissions: {} + +jobs: + libs_linux: + name: Building Linux libraries + runs-on: ubuntu-latest + outputs: + artifact_id: ${{ steps.upload.outputs.artifact-id }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Install Rust Nightly + shell: bash + run: + rustup install --target aarch64-unknown-linux-gnu,x86_64-unknown-linux-gnu,i686-unknown-linux-gnu,riscv64gc-unknown-linux-gnu,armv7-unknown-linux-gnueabihf + + - name: Install cross-compiling GCC + shell: bash + run: | + sudo apt update + sudo apt install -y gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu gcc-arm-linux-gnueabihf gcc-i686-linux-gnu + + - name: Build binaries + shell: bash + run: | + ./tool/build_linux.sh x64 + ./tool/build_linux.sh aarch64 + ./tool/build_linux.sh x86 + ./tool/build_linux.sh armv7 + ./tool/build_linux.sh riscv64gc + + - uses: actions/upload-artifact@v4 + id: upload + with: + name: linux-library + retention-days: 14 + path: | + *.so + *.linux.a + + libs_macos: + name: Building macOS libraries + runs-on: macos-latest + outputs: + artifact_id: ${{ steps.upload.outputs.artifact-id }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Install Rust Nightly + shell: bash + run: + rustup install --target x86_64-apple-darwin,aarch64-apple-darwin + + - name: Build binaries + shell: bash + run: | + ./tool/build_macos.sh x64 + ./tool/build_macos.sh aarch64 + + - uses: actions/upload-artifact@v4 + id: upload + with: + name: macos-library + retention-days: 14 + path: | + *.dylib + *.a + + libs_windows: + name: Building Windows libraries + runs-on: windows-latest + outputs: + artifact_id: ${{ steps.upload.outputs.artifact-id }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Install Rust Nightly + shell: powershell + run: rustup install --target x86_64-pc-windows-msvc,aarch64-pc-windows-msvc,i686-pc-windows-msvc + + - name: Build binaries + shell: bash + run: | + ./tool/build_windows.sh x64 + ./tool/build_windows.sh aarch64 + ./tool/build_windows.sh x86 + + - uses: actions/upload-artifact@v4 + id: upload + with: + name: windows-library + retention-days: 14 + path: | + *.dll + *.lib + + libs_android: + name: Building Android libraries + runs-on: ubuntu-latest + outputs: + artifact_id: ${{ steps.upload.outputs.artifact-id }} + static_artifact_id: ${{ steps.upload_static.outputs.artifact-id }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-java@v5 + with: + distribution: "temurin" + java-version: "25" + + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@v4 + + - name: Setup + shell: bash + run: | + rustup toolchain install nightly-2026-04-10-x86_64-unknown-linux-gnu + rustup component add rust-src --toolchain nightly-2026-04-10-x86_64-unknown-linux-gnu + rustup target add \ + aarch64-linux-android \ + armv7-linux-androideabi \ + x86_64-linux-android \ + i686-linux-android + cargo install cargo-ndk + + - name: Build signed library + shell: bash + if: ${{ inputs.sign-publication }} + run: | + cd android + ./gradlew build zipPublication -PgpgKey=${INPUTS_GPG_KEY} -PgpgPassword=${INPUTS_GPG_PASSWORD} + ls -lh build/outputs/aar + find build/repository + env: + INPUTS_GPG_KEY: ${{ secrets.gpg-key }} + INPUTS_GPG_PASSWORD: ${{ secrets.gpg-password }} + + - name: Build library without signing + shell: bash + if: ${{ !inputs.sign-publication }} + run: | + cd android + ./gradlew build zipPublication -PsignPublication=0 + ls -lh build/outputs/aar + find build/repository + + - name: Upload binary + uses: actions/upload-artifact@v4 + id: upload + with: + name: android-library + retention-days: 14 + compression-level: 0 # We're uploading a zip, no need to compress again + path: android/build/distributions/powersync_android.zip + if-no-files-found: error + + - name: Build individual libraries for Dart SDK + shell: bash + run: | + cd android + ./gradlew buildRustStandalone + + - name: Copy individual libraries + shell: bash + run: | + cp android/build/standalone/arm64-v8a/libpowersync.so libpowersync_aarch64.android.so + cp android/build/standalone/armeabi-v7a/libpowersync.so libpowersync_armv7.android.so + cp android/build/standalone/x86/libpowersync.so libpowersync_x86.android.so + cp android/build/standalone/x86_64/libpowersync.so libpowersync_x64.android.so + + - name: Upload individual libraries + uses: actions/upload-artifact@v4 + id: upload_static + with: + name: android-static + retention-days: 14 + path: | + *.so + + libs_wasm: + name: Basic WASM build + runs-on: ubuntu-latest + outputs: + artifact_id: ${{ steps.upload.outputs.artifact-id }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Install Rust Nightly + shell: bash + run: rustup install + + - name: Build WASM + shell: bash + run: ./tool/build_wasm.sh + + - uses: actions/upload-artifact@v4 + id: upload + with: + name: wasm-library + retention-days: 14 + path: libpowersync-wasm.a + + libs_xcframework: + name: Build XCFramework + runs-on: macos-latest + outputs: + artifact_id: ${{ steps.upload.outputs.artifact-id }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Setup + shell: bash + run: | + rustup toolchain install nightly-2026-04-10-aarch64-apple-darwin + rustup component add rust-src --toolchain nightly-2026-04-10-aarch64-apple-darwin + rustup target add \ + x86_64-apple-darwin \ + aarch64-apple-darwin \ + aarch64-apple-ios \ + aarch64-apple-ios-sim \ + x86_64-apple-ios + + - name: setup-cocoapods + uses: maxim-lobanov/setup-cocoapods@8e97e1e98e6ccf42564fdf5622c8feec74199377 # v1.4.0 + with: + version: 1.16.2 + + - name: Set up XCode + uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 + with: + xcode-version: latest-stable + + - name: List simulators + shell: bash + run: | + xcrun xctrace list devices + + - name: Build iOS & macOS xcframework + shell: bash + run: | + ./tool/build_xcframework.sh + + - name: Lint pod + shell: bash + run: | + pod lib lint + + - uses: actions/upload-artifact@v4 + id: upload + with: + name: xcframework + retention-days: 14 + compression-level: 0 # We're uploading a zip archive, no need to compress agan + path: | + powersync-sqlite-core.xcframework.zip + + attest: + name: Attest artifacts + if: ${{ inputs.attest }} + needs: + [ + libs_linux, + libs_macos, + libs_windows, + libs_android, + libs_wasm, + libs_xcframework, + ] + runs-on: ubuntu-latest + outputs: + artifact_id: ${{ steps.upload_sbom.outputs.artifact-id }} + permissions: + id-token: write # Needed to mint the OIDC token used to sign the attestation. + attestations: write # Needed to publish the attestation. + artifact-metadata: write # Create artifact storage record. + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - uses: dart-lang/setup-dart@v1 + + - name: Install Rust Nightly + shell: bash + run: rustup install + + - name: Install Dart dependencies + working-directory: dart + run: dart pub get + + - name: Generate SBOM + working-directory: dart + run: dart tool/generate_sbom.dart > ../bom.json + + - name: Upload SBOM + uses: actions/upload-artifact@v4 + id: upload_sbom + with: + name: sbom + retention-days: 14 + path: bom.json + + - name: Download build artifacts + uses: actions/download-artifact@v8 + with: + path: artifacts + merge-multiple: true + artifact-ids: >- + ${{ needs.libs_linux.outputs.artifact_id }}, + ${{ needs.libs_macos.outputs.artifact_id }}, + ${{ needs.libs_windows.outputs.artifact_id }}, + ${{ needs.libs_android.outputs.artifact_id }}, + ${{ needs.libs_android.outputs.static_artifact_id }}, + ${{ needs.libs_wasm.outputs.artifact_id }}, + ${{ needs.libs_xcframework.outputs.artifact_id }} + + - name: List downloaded artifacts + run: find artifacts + + - name: Attest artifacts + uses: actions/attest@v4 + with: + subject-path: artifacts/* + + - name: Attest SBOM + uses: actions/attest@v4 + with: + sbom-path: bom.json + subject-path: artifacts/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f209abc..95b3737 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,73 +35,23 @@ jobs: body="Release $tag" gh release create --draft "$tag" --title "$tag" --notes "$body" - libs_linux: - name: Building Linux libraries - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Build Linux - uses: ./.github/actions/linux - - libs_macos: - name: Building macOS libraries - runs-on: macos-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Build macOS - uses: ./.github/actions/macos - - libs_windows: - name: Building Windows libraries - runs-on: windows-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Build Windows - uses: ./.github/actions/windows - - libs_android: - name: Building Android libraries - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Build Android - uses: ./.github/actions/android - with: - gpg-key: ${{ secrets.GPG_PRIVATE_KEY }} - gpg-password: ${{ secrets.GPG_PASSWORD }} - - libs_wasm: - name: Basic WASM build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - - name: Build wasm - uses: ./.github/actions/wasm - - libs_xcframework: - name: Build XCFramework - runs-on: macos-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Build XCFramework - uses: ./.github/actions/xcframework + build_libs: + name: Build libraries + uses: ./.github/workflows/build.yml + permissions: + id-token: write # Needed to mint the OIDC token used to sign the attestation. + attestations: write # Needed to publish the attestation. + artifact-metadata: write # Create artifact storage record. + with: + attest: true + sign-publication: true + secrets: + gpg-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-password: ${{ secrets.GPG_PASSWORD }} publish_android: name: Publish Android - needs: [ draft_release, libs_android ] + needs: [ draft_release, build_libs ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -110,7 +60,7 @@ jobs: - uses: actions/download-artifact@v8 with: - name: android-library + artifact-ids: ${{ needs.build_libs.outputs.libs_android }} - name: Publish to Maven Central env: @@ -123,7 +73,7 @@ jobs: publish_ios_pod_and_spm_package: name: Publish iOS - needs: [ draft_release, libs_xcframework ] + needs: [ draft_release, build_libs ] runs-on: macos-latest permissions: contents: write # Needed to upload the XCFramework to the draft release. @@ -135,7 +85,7 @@ jobs: - name: Download libs uses: actions/download-artifact@v8 with: - name: xcframework + artifact-ids: ${{ needs.build_libs.outputs.libs_xcframework }} - name: Extract xcframework run: unzip powersync-sqlite-core.xcframework.zip "powersync-sqlite-core.xcframework/*" @@ -185,7 +135,7 @@ jobs: publish_desktop: name: Publish Desktop libraries - needs: [ draft_release, libs_linux, libs_macos, libs_windows ] + needs: [ draft_release, build_libs ] runs-on: ubuntu-latest permissions: contents: write # Needed to upload binaries to release. @@ -198,23 +148,23 @@ jobs: - name: Download Linux libraries uses: actions/download-artifact@v8 with: - name: linux-library + artifact-ids: ${{ needs.build_libs.outputs.libs_linux }} - name: Download macOS libraries uses: actions/download-artifact@v8 with: - name: macos-library + artifact-ids: ${{ needs.build_libs.outputs.libs_macos }} - name: Download Windows libraries uses: actions/download-artifact@v8 with: - name: windows-library - - name: Download Windows libraries + artifact-ids: ${{ needs.build_libs.outputs.libs_windows }} + - name: Download static Android libraries uses: actions/download-artifact@v8 with: - name: windows-library - - name: Download static Android libraries + artifact-ids: ${{ needs.build_libs.outputs.libs_android_static }} + - name: Download SBOM uses: actions/download-artifact@v8 with: - name: android-static + artifact-ids: ${{ needs.build_libs.outputs.sbom }} - name: Create archive of static libs for Kotlin run: zip static_libs.zip *.lib *.a @@ -232,10 +182,11 @@ jobs: gh release upload "${TAG}" *.dylib gh release upload "${TAG}" *.so gh release upload "${TAG}" static_libs.zip + gh release upload "${TAG}" bom.json publish_wasm: name: Publish WASM builds - needs: [ draft_release, libs_wasm ] + needs: [ draft_release, build_libs ] runs-on: ubuntu-latest permissions: contents: write # Needed to upload binaries to release. @@ -247,7 +198,7 @@ jobs: - name: Download wasm bundle uses: actions/download-artifact@v8 with: - name: wasm-library + artifact-ids: ${{ needs.build_libs.outputs.libs_wasm }} - name: Upload wasm binaries env: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8aea92a..3327fe1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,75 +23,16 @@ jobs: with: persona: pedantic - libs_linux: - name: Building Linux libraries - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Build Linux libraries - uses: ./.github/actions/linux - - libs_macos: - name: Building macOS libraries - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) - runs-on: macos-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Build macOS - uses: ./.github/actions/macos - - libs_windows: - name: Building Windows libraries - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) - runs-on: windows-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - - name: Build Windows - uses: ./.github/actions/windows - - libs_android: - name: Building Android libraries - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Build Android - uses: ./.github/actions/android - with: - sign-publication: '0' - - libs_wasm: - name: Basic WASM build - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - - name: Build wasm - uses: ./.github/actions/wasm - - libs_xcframework: - name: Build XCFramework + build_libs: + name: Build libraries if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) - runs-on: macos-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - name: Build XCFramework - uses: ./.github/actions/xcframework + uses: ./.github/workflows/build.yml + permissions: + # Not used here since attest is not enabled for this workflow, but the + # reusable workflow's attest job requires the caller to grant these. + id-token: write # Unused + attestations: write # Unused + artifact-metadata: write # Unused rust_unit_tests: name: Rust unit tests on ${{ matrix.os }} @@ -140,7 +81,7 @@ jobs: name: Testing on ${{ matrix.os }} if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) runs-on: ${{ matrix.os }} - needs: [libs_linux, libs_macos, libs_windows] + needs: [build_libs] strategy: fail-fast: false matrix: @@ -167,17 +108,17 @@ jobs: - name: Download libs uses: actions/download-artifact@v8 with: - name: linux-library + artifact-ids: ${{ needs.build_libs.outputs.libs_linux }} path: dart/assets - name: Download libs uses: actions/download-artifact@v8 with: - name: macos-library + artifact-ids: ${{ needs.build_libs.outputs.libs_macos }} path: dart/assets - name: Download libs uses: actions/download-artifact@v8 with: - name: windows-library + artifact-ids: ${{ needs.build_libs.outputs.libs_windows }} path: dart/assets - name: View downloaded artifacts @@ -191,6 +132,10 @@ jobs: run: | dart test -P ci + - name: Generate sbom + working-directory: dart + run: dart tool/generate_sbom.dart + build_stable_rust: runs-on: ubuntu-latest steps: diff --git a/crates/core/src/constants.rs b/crates/core/src/constants.rs index e2a2fd0..1866214 100644 --- a/crates/core/src/constants.rs +++ b/crates/core/src/constants.rs @@ -5,8 +5,7 @@ pub const FULL_GIT_HASH: &'static str = env!("GIT_HASH"); // We need 3.44 or later to use an `ORDER BY` in an aggregate function invocation. // -// When raising the minimum version requirement, also change it in download_sqlite3.dart to ensure -// we're testing with the minimum version we claim to support. +// When raising the minimum version requirement, also change it in generate_sbom.dart. pub const MIN_SQLITE_VERSION_NUMBER: c_int = 3044000; pub const SUBTYPE_JSON: u32 = 'J' as u32; diff --git a/dart/pubspec.lock b/dart/pubspec.lock index 933ed6e..7dfb4b1 100644 --- a/dart/pubspec.lock +++ b/dart/pubspec.lock @@ -458,7 +458,7 @@ packages: source: hosted version: "1.4.0" uuid: - dependency: transitive + dependency: "direct dev" description: name: uuid sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml index e222315..dc5ecfe 100644 --- a/dart/pubspec.yaml +++ b/dart/pubspec.yaml @@ -18,6 +18,7 @@ dev_dependencies: convert: ^3.1.2 meta: ^1.16.0 path: ^1.9.1 + uuid: ^4.6.0 # See https://pub.dev/documentation/sqlite3/latest/topics/hook-topic.html hooks: diff --git a/dart/tool/generate_sbom.dart b/dart/tool/generate_sbom.dart new file mode 100644 index 0000000..06eb751 --- /dev/null +++ b/dart/tool/generate_sbom.dart @@ -0,0 +1,187 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:uuid/uuid.dart'; + +/// Generates an SBOM for the core extension. +/// +/// Usage: `dart tool/generate_sbom.dart > bom.json` +void main() async { + // The first library is the core extension itself (the powersync_core crate). + // The crate we actually compile as binaries is powersync_static or + // powersync_loadable, but these contain no additional dependencies. + final [coreExtension, ...dependencies] = await findDependencies(); + if (coreExtension.name != 'powersync_core') { + throw 'Unexpected root: ${coreExtension.name}'; + } + + const journeyApps = { + 'name': 'JourneyApps', + 'url': ['https://powersync.com', 'https://journeyapps.com/'], + }; + + final sbom = { + 'bomFormat': 'CycloneDX', + 'specVersion': '1.7', + 'serialNumber': 'urn:uuid:${const Uuid().v4()}', + 'version': 1, + 'metadata': { + 'component': coreExtension.describeAsBomComponent(), + 'lifecycles': [ + {'phase': 'build'} + ], + 'timestamp': DateTime.now().toIso8601String(), + 'manufacturer': journeyApps, + 'supplier': journeyApps, + }, + 'components': [ + for (final crate in dependencies) crate.describeAsBomComponent(), + // Also declare SQLite as an external component required at runtime (since + // this is a SQLite extension). + { + 'isExternal': true, + 'versionRange': 'vers:semver/>=3.44.0|<4.0.0', + 'type': 'library', + 'name': 'SQLite', + 'purl': 'pkg:generic/sqlite', + 'bom-ref': 'external-sqlite', + 'licenses': [ + {'expression': 'blessing'}, + ], + 'externalReferences': [ + {'url': 'https://sqlite.org/', 'type': 'website'}, + ], + } + ], + 'dependencies': [ + for (final crate in [coreExtension, ...dependencies]) + if (crate.dependencies.isNotEmpty) + { + 'ref': crate.bomRef, + 'dependsOn': [ + for (final dep in crate.dependencies) dep.bomRef, + if (crate == coreExtension) 'external-sqlite' + ] + } + ], + }; + + print(JsonEncoder.withIndent(' ' * 2).convert(sbom)); +} + +final class RustCrate { + final String name; + final String version; + final String licenseExpression; + final String repository; + + final List dependencies = []; + + String get bomRef => '$name@$version'; + + RustCrate({ + required this.name, + required this.version, + required this.licenseExpression, + required this.repository, + }); + + Map describeAsBomComponent() { + return { + 'version': version, + 'type': 'library', + 'bom-ref': bomRef, + 'name': name, + 'scope': 'required', + 'licenses': [ + {'expression': licenseExpression}, + ], + 'purl': 'pkg:cargo/${name}@${version}', + 'externalReferences': [ + {'url': repository, 'type': 'vcs'}, + if (name.contains('powersync')) + { + 'url': 'https://powersync.com/', + 'type': 'website', + } + ], + }; + } +} + +// Matches a single line of `cargo tree --prefix depth` output, e.g. +// `1serde v1.0.228;MIT OR Apache-2.0;https://github.com/serde-rs/serde` or +// `0powersync_core v0.5.2 (/path/to/crates/core);Apache-2.0;https://...`. +// Cargo dedupes subtrees it has already expanded elsewhere by appending +// ` (*)` and not descending further, which the trailing group strips. +final _treeLine = RegExp( + r'^(\d+)(\S+) v(\S+)(?: \([^)]*\))?;([^;]*);(.*?)(?: \(\*\))?$', +); + +Future> findDependencies() async { + final result = await Process.run('cargo', [ + 'tree', + '-p', + 'powersync_core', + '--edges', + 'normal,no-proc-macro', + '--prefix', + 'depth', + '--format', + '{p};{l};{r}', + ]); + if (result.exitCode != 0) { + throw 'cargo tree failed with exit code ${result.exitCode}:\n${result.stderr}'; + } + + final crates = {}; + // The chain of crates from the root to the current line, indexed by depth. + final stack = []; + + for (final line in const LineSplitter().convert(result.stdout as String)) { + if (line.trim().isEmpty) { + continue; + } + + final match = _treeLine.firstMatch(line); + if (match == null) { + throw 'Could not parse cargo tree line: $line'; + } + + final depth = int.parse(match[1]!); + final name = match[2]!; + final version = match[3]!; + final license = match[4]!; + final repository = match[5]!; + + if (license.isEmpty) { + throw 'Crate $name@$version does not include a license expression.'; + } + + // A crate is only fully expanded the first time it's encountered, so + // later (deduped) occurrences reuse the same instance and its already + // populated dependencies. + final crate = crates.putIfAbsent( + '$name@$version', + () => RustCrate( + name: name, + version: version, + licenseExpression: license, + repository: repository, + ), + ); + + if (depth < stack.length) { + stack.removeRange(depth, stack.length); + } + if (depth > 0) { + final parent = stack[depth - 1]; + if (!parent.dependencies.contains(crate)) { + parent.dependencies.add(crate); + } + } + stack.add(crate); + } + + return crates.values.toList(); +}