Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 263 additions & 0 deletions .github/workflows/swift-sdk-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
name: Build Swift SDK and Example (no warnings)

on:
pull_request:
paths:
- 'packages/swift-sdk/**'
- 'packages/rs-*/**'
- '.github/workflows/swift-sdk-build.yml'
push:
branches: [ main, master ]
paths:
- 'packages/swift-sdk/**'
- 'packages/rs-*/**'
- '.github/workflows/swift-sdk-build.yml'

jobs:
swift-sdk-build:
name: Swift SDK and Example build (warnings as errors)
runs-on: macos-15
timeout-minutes: 60

Comment on lines +16 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Grant explicit permissions for PR commenting step.

Without write permissions, the github-script step updating PR comments can fail and red-mark the build.

 jobs:
   swift-sdk-build:
+    permissions:
+      contents: read
+      pull-requests: write
+      issues: write
     name: Swift SDK and Example build (warnings as errors)
     runs-on: macos-15
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
jobs:
swift-sdk-build:
name: Swift SDK and Example build (warnings as errors)
runs-on: macos-15
timeout-minutes: 60
jobs:
swift-sdk-build:
permissions:
contents: read
pull-requests: write
issues: write
name: Swift SDK and Example build (warnings as errors)
runs-on: macos-15
timeout-minutes: 60
🤖 Prompt for AI Agents
In .github/workflows/swift-sdk-build.yml around lines 14-19, the job lacks
explicit permissions so the github-script step that updates PR comments can
fail; add a permissions block to the swift-sdk-build job (e.g., permissions:
pull-requests: write) so the github-script has write access to update PR
comments, and ensure the job uses the default GITHUB_TOKEN by not overriding it.

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Select Xcode 16
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.*'

- name: Show Xcode and Swift versions
run: |
xcodebuild -version
swift --version

# Rust + Cargo cache to speed up FFI build
- name: Set up Rust toolchain (stable)
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-registry-${{ hashFiles('**/Cargo.lock') }}

- name: Add iOS Rust targets
run: |
rustup target add aarch64-apple-ios aarch64-apple-ios-sim

- name: Install cbindgen (for header generation)
run: |
brew install cbindgen || true

- name: Restore cached Protobuf (protoc)
id: cache-protoc
uses: actions/cache@v4
with:
path: |
${{ env.HOME }}/.local/protoc-32.0/bin
${{ env.HOME }}/.local/protoc-32.0/include
key: protoc/32.0/${{ runner.os }}/universal

- name: Install Protobuf (protoc) if cache miss
if: steps.cache-protoc.outputs.cache-hit != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euxo pipefail
VERSION=32.0
OS=osx-universal_binary
PROTOC_DIR="$HOME/.local/protoc-${VERSION}"
mkdir -p "$PROTOC_DIR"
curl -fsSL -H "Authorization: token ${GITHUB_TOKEN}" \
-o /tmp/protoc.zip \
"https://github.com/protocolbuffers/protobuf/releases/download/v${VERSION}/protoc-${VERSION}-${OS}.zip"
unzip -o /tmp/protoc.zip -d "$PROTOC_DIR"

- name: Save cached Protobuf (protoc)
if: steps.cache-protoc.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
${{ env.HOME }}/.local/protoc-32.0/bin
${{ env.HOME }}/.local/protoc-32.0/include
key: protoc/32.0/${{ runner.os }}/universal

- name: Verify protoc and export env
run: |
set -euxo pipefail
echo "$HOME/.local/protoc-32.0/bin" >> $GITHUB_PATH
echo "PROTOC=$HOME/.local/protoc-32.0/bin/protoc" >> "$GITHUB_ENV"
"$HOME/.local/protoc-32.0/bin/protoc" --version

- name: Determine rust-dashcore revision (from rs-dpp)
id: dashcore_rev
shell: bash
run: |
set -euo pipefail
# Use the same rust-dashcore revision as rs-dpp (parse single-line dep)
REV=$(grep -E '^[[:space:]]*dashcore[[:space:]]*=[[:space:]]*\{.*rev[[:space:]]*=' packages/rs-dpp/Cargo.toml \
| sed -E 's/.*rev[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/' \
| head -n1 || true)
if [ -z "${REV:-}" ]; then
echo "Failed to determine rust-dashcore revision from Cargo.toml" >&2
exit 1
fi
echo "rev=$REV" >> "$GITHUB_OUTPUT"

- name: Checkout rust-dashcore at required revision
shell: bash
run: |
set -euxo pipefail
BASE_DIR="$(dirname "$GITHUB_WORKSPACE")"
cd "$BASE_DIR"
if [ -d rust-dashcore/.git ]; then
echo "Updating existing rust-dashcore checkout"
cd rust-dashcore
git fetch --all --tags --prune
else
git clone --no-tags --filter=blob:none https://github.com/dashpay/rust-dashcore.git rust-dashcore
cd rust-dashcore
fi
git checkout "${{ steps.dashcore_rev.outputs.rev }}"

- name: Determine rust-dashcore toolchain channel
id: dashcore_toolchain
shell: bash
run: |
set -euo pipefail
FILE="$(dirname "$GITHUB_WORKSPACE")/rust-dashcore/rust-toolchain.toml"
if [ -f "$FILE" ]; then
CHANNEL=$(grep -E '^[[:space:]]*channel[[:space:]]*=' "$FILE" | sed -E 's/.*"([^"]+)".*/\1/' | head -n1 || true)
fi
CHANNEL=${CHANNEL:-stable}
echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT"
echo "RUST_DASHCORE_TOOLCHAIN=$CHANNEL" >> "$GITHUB_ENV"

- name: Ensure rust-dashcore toolchain has iOS targets
shell: bash
run: |
set -euxo pipefail
RUST_DASHCORE_DIR="$(dirname "$GITHUB_WORKSPACE")/rust-dashcore"
cd "$RUST_DASHCORE_DIR"
TOOLCHAIN="${{ steps.dashcore_toolchain.outputs.channel }}"
rustup toolchain install "$TOOLCHAIN" --profile minimal --no-self-update
rustup target add --toolchain "$TOOLCHAIN" aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios

- name: Build DashSDKFFI.xcframework and install into Swift package
run: |
bash packages/swift-sdk/build_ios.sh

- name: Zip XCFramework and compute checksum
run: |
cd packages/swift-sdk
ditto -c -k --sequesterRsrc --keepParent DashSDKFFI.xcframework DashSDKFFI.xcframework.zip
swift package compute-checksum DashSDKFFI.xcframework.zip > xc_checksum.txt

- name: Upload DashSDKFFI artifacts
uses: actions/upload-artifact@v4
with:
name: DashSDKFFI.xcframework
path: |
packages/swift-sdk/DashSDKFFI.xcframework
packages/swift-sdk/DashSDKFFI.xcframework.zip
packages/swift-sdk/xc_checksum.txt
retention-days: 14

- name: Comment/update PR with artifact link and usage guide (skip if unchanged)
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const fs = require('fs');
const marker = '<!-- DASHSDKFFI_ARTIFACT_COMMENT -->';
let checksum = '';
try { checksum = fs.readFileSync('packages/swift-sdk/xc_checksum.txt', 'utf8').trim(); } catch (e) {}

// Find existing marker comment on this PR
const issue_number = context.payload.pull_request.number;
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
per_page: 100
});
const existing = comments.find(c => (c.body || '').includes(marker));

// Extract old checksum if present
const extractChecksum = (text) => {
if (!text) return '';
const m = text.match(/checksum:\s*"?([a-f0-9]{64})"?/i);
return m ? m[1] : '';
};
const oldChecksum = existing ? extractChecksum(existing.body) : '';

if (oldChecksum && checksum && oldChecksum === checksum) {
core.info(`Checksum unchanged (${checksum}); skipping PR comment update.`);
return;
}

const body = `${marker}\n` +
`✅ DashSDKFFI.xcframework built for this PR.\n\n` +
`- Workflow run: ${runUrl}\n` +
`- Artifacts: DashSDKFFI.xcframework (folder), DashSDKFFI.xcframework.zip, xc_checksum.txt\n\n` +
`SwiftPM (host the zip at a stable URL, then use):\n` +
'```swift\n' +
'.binaryTarget(\n' +
' name: "DashSDKFFI",\n' +
' url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",\n' +
` checksum: "${checksum || '<paste checksum>'}"\n` +
')\n' +
'```\n\n' +
`Xcode manual integration:\n` +
`- Download 'DashSDKFFI.xcframework' artifact from the run link above.\n` +
`- Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.\n` +
`- If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.\n`;

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
body
});
}

- name: Build SwiftDashSDK package (warnings as errors)
working-directory: packages/swift-sdk
run: |
swift build -c debug -Xswiftc -warnings-as-errors

- name: Resolve ExampleApp dependencies
working-directory: packages/swift-sdk
run: |
xcodebuild -project SwiftExampleApp/SwiftExampleApp.xcodeproj -resolvePackageDependencies

- name: Build SwiftExampleApp (warnings as errors)
working-directory: packages/swift-sdk
env:
# Treat Swift warnings as errors during xcodebuild
OTHER_SWIFT_FLAGS: -warnings-as-errors
SWIFT_TREAT_WARNINGS_AS_ERRORS: YES
run: |
xcodebuild \
-project SwiftExampleApp/SwiftExampleApp.xcodeproj \
-scheme SwiftExampleApp \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-configuration Debug \
ONLY_ACTIVE_ARCH=YES \
OTHER_SWIFT_FLAGS="$OTHER_SWIFT_FLAGS" \
SWIFT_TREAT_WARNINGS_AS_ERRORS=$SWIFT_TREAT_WARNINGS_AS_ERRORS \
Comment on lines +250 to +262
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Quote environment variables to prevent word splitting

The OTHER_SWIFT_FLAGS and SWIFT_TREAT_WARNINGS_AS_ERRORS variables should be quoted in the shell command to prevent potential word splitting issues.

           xcodebuild \
             -project SwiftExampleApp/SwiftExampleApp.xcodeproj \
             -scheme SwiftExampleApp \
             -sdk iphonesimulator \
             -destination 'generic/platform=iOS Simulator' \
             -configuration Debug \
             ONLY_ACTIVE_ARCH=YES \
-            OTHER_SWIFT_FLAGS="$OTHER_SWIFT_FLAGS" \
-            SWIFT_TREAT_WARNINGS_AS_ERRORS=$SWIFT_TREAT_WARNINGS_AS_ERRORS \
+            OTHER_SWIFT_FLAGS="${OTHER_SWIFT_FLAGS}" \
+            SWIFT_TREAT_WARNINGS_AS_ERRORS="${SWIFT_TREAT_WARNINGS_AS_ERRORS}" \
             build
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Treat Swift warnings as errors during xcodebuild
OTHER_SWIFT_FLAGS: -warnings-as-errors
SWIFT_TREAT_WARNINGS_AS_ERRORS: YES
run: |
xcodebuild \
-project SwiftExampleApp/SwiftExampleApp.xcodeproj \
-scheme SwiftExampleApp \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-configuration Debug \
ONLY_ACTIVE_ARCH=YES \
OTHER_SWIFT_FLAGS="$OTHER_SWIFT_FLAGS" \
SWIFT_TREAT_WARNINGS_AS_ERRORS=$SWIFT_TREAT_WARNINGS_AS_ERRORS \
# Treat Swift warnings as errors during xcodebuild
OTHER_SWIFT_FLAGS: -warnings-as-errors
SWIFT_TREAT_WARNINGS_AS_ERRORS: YES
run: |
xcodebuild \
-project SwiftExampleApp/SwiftExampleApp.xcodeproj \
-scheme SwiftExampleApp \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-configuration Debug \
ONLY_ACTIVE_ARCH=YES \
OTHER_SWIFT_FLAGS="${OTHER_SWIFT_FLAGS}" \
SWIFT_TREAT_WARNINGS_AS_ERRORS="${SWIFT_TREAT_WARNINGS_AS_ERRORS}" \
build
🧰 Tools
🪛 actionlint (1.7.7)

50-50: shellcheck reported issue in this script: SC2086:info:9:34: Double quote to prevent globbing and word splitting

(shellcheck)

🤖 Prompt for AI Agents
.github/workflows/swift-sdk-build.yml around lines 47 to 59: the shell
invocation passes OTHER_SWIFT_FLAGS and SWIFT_TREAT_WARNINGS_AS_ERRORS unquoted
which can cause word splitting; update the xcodebuild command to wrap these
variables in double quotes (e.g. OTHER_SWIFT_FLAGS="$OTHER_SWIFT_FLAGS" and
SWIFT_TREAT_WARNINGS_AS_ERRORS="$SWIFT_TREAT_WARNINGS_AS_ERRORS") so their
values are preserved as single arguments.

build
135 changes: 135 additions & 0 deletions .github/workflows/swift-sdk-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: Release DashSDKFFI XCFramework

on:
push:
tags:
- 'ffi-*'
workflow_dispatch:
inputs:
tag:
description: 'Tag name to release (optional; defaults to current ref_name on tag push)'
required: false

Comment on lines +7 to +12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Require a tag for manual runs or guard against releasing under a branch name.

On workflow_dispatch without input, ${{ github.ref_name }} is a branch ref; you may unintentionally create a release named after a branch.

Option A (simplest) — make the input required:

-        required: false
+        required: true

Option B — keep optional, but bail out if empty on manual:

       - name: Determine tag
         id: tag
         run: |
+          set -euo pipefail
           if [ -n "${{ github.event.inputs.tag }}" ]; then
             printf 'name=%s\n' "${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
           else
-            # on tag push, ref_name is the tag
-            printf 'name=%s\n' "${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
+            if [ "${{ github.event_name }}" = "push" ]; then
+              # on tag push, ref_name is the tag
+              printf 'name=%s\n' "${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
+            else
+              echo "Tag input is required for workflow_dispatch." >&2
+              exit 1
+            fi
           fi

Also applies to: 68-76

permissions:
contents: write

jobs:
build-and-release:
name: Build and release DashSDKFFI
runs-on: macos-15

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Select Xcode 16
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.*'

- name: Show Xcode and Swift versions
run: |
xcodebuild -version
swift --version

- name: Set up Rust toolchain (stable)
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-registry-${{ hashFiles('**/Cargo.lock') }}

- name: Add iOS Rust targets
run: |
rustup target add aarch64-apple-ios aarch64-apple-ios-sim

- name: Install cbindgen (for header generation)
run: |
brew install cbindgen || true

- name: Install protoc (Protocol Buffers compiler)
uses: arduino/setup-protoc@v3
with:
version: '32.x'

- name: Build DashSDKFFI.xcframework and install into Swift package
run: |
bash packages/swift-sdk/build_ios.sh

- name: Zip XCFramework and compute checksum
id: zip
run: |
cd packages/swift-sdk
ditto -c -k --sequesterRsrc --keepParent DashSDKFFI.xcframework DashSDKFFI.xcframework.zip
swift package compute-checksum DashSDKFFI.xcframework.zip > xc_checksum.txt
echo "checksum=$(cat xc_checksum.txt)" >> $GITHUB_OUTPUT

- name: Determine tag
id: tag
run: |
if [ -n "${{ github.event.inputs.tag }}" ]; then
echo "name=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
else
# on tag push, ref_name is the tag
echo "name=${{ github.ref_name }}" >> $GITHUB_OUTPUT
fi

- name: Check existing release for changes
id: check
uses: actions/github-script@v7
env:
NAME: ${{ steps.tag.outputs.name }}
with:
script: |
const tag = process.env.NAME || '${{ steps.tag.outputs.name }}';
const checksumNew = '${{ steps.zip.outputs.checksum }}'.trim();
try {
const rel = await github.rest.repos.getReleaseByTag({ owner: context.repo.owner, repo: context.repo.repo, tag });
const assets = await github.rest.repos.listReleaseAssets({ owner: context.repo.owner, repo: context.repo.repo, release_id: rel.data.id, per_page: 100 });
const checksumAsset = assets.data.find(a => a.name === 'xc_checksum.txt');
if (!checksumAsset) {
core.setOutput('changed', 'true');
return;
}
const res = await github.request('GET {url}', { url: checksumAsset.url, headers: { Accept: 'application/octet-stream' } });
const checksumOld = (res.data || '').toString().trim();
core.info(`Old checksum: ${checksumOld}, New checksum: ${checksumNew}`);
core.setOutput('changed', checksumOld === checksumNew ? 'false' : 'true');
} catch (e) {
// No release found -> treat as changed
core.info(`No existing release for tag ${tag}. Creating new.`);
core.setOutput('changed', 'true');
}
result-encoding: string

- name: Create/Update release (only if changed)
if: steps.check.outputs.changed == 'true'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.name }}
name: DashSDKFFI ${{ steps.tag.outputs.name }}
draft: false
prerelease: false
files: |
packages/swift-sdk/DashSDKFFI.xcframework.zip
packages/swift-sdk/xc_checksum.txt
body: |
DashSDKFFI.xcframework built for tag ${{ steps.tag.outputs.name }}.

SwiftPM usage:
```swift
.binaryTarget(
name: "DashSDKFFI",
url: "https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.name }}/DashSDKFFI.xcframework.zip",
checksum: "${{ steps.zip.outputs.checksum }}"
)
```

- name: Skip release (no changes)
if: steps.check.outputs.changed != 'true'
run: |
echo "No changes detected in XCFramework; skipping release upload."
Loading
Loading