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
212 changes: 212 additions & 0 deletions .github/workflows/release-sdk-swift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
name: Release SDK Swift

# Swift SDK releases use their own `swift-vX.Y.Z` tags so they never trigger
# release.yml (CLI, plugin, Pages), whose `v*` pattern does not match them.
on:
push:
tags:
- "swift-v[0-9]+.[0-9]+.[0-9]+"
Comment thread
setoelkahfi marked this conversation as resolved.
workflow_dispatch:
inputs:
publish:
description: "Publish demucs-swift release assets from the selected ref"
type: boolean
default: false
release_tag:
description: "Existing swift-vX.Y.Z tag to publish on manual runs"
type: string
required: false

permissions:
contents: write

# One release at a time per tag. Not cancel-in-progress: the later steps create
# a GitHub release and then push a commit and a tag to demucs-swift, and killing
# a run part-way through leaves a release with no assets on it, or demucs-swift
# pointing at a release that no longer matches.
concurrency:
group: release-sdk-swift-${{ github.ref }}
cancel-in-progress: false

jobs:
preflight:
# macOS rather than Linux: the release artifact is an XCFramework and
# demucs-ffi pulls in burn's Metal backend on Apple targets.
runs-on: macos-26
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
with:
key: sdk-swift-preflight
- name: Validate tag matches demucs-ffi version
if: startsWith(github.ref, 'refs/tags/')
run: |
version="$(cargo metadata --no-deps --format-version 1 | python3 -c 'import json,sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"] == "demucs-ffi"))')"
if [ "${GITHUB_REF_NAME#swift-v}" != "$version" ]; then
echo "::error::Tag '$GITHUB_REF_NAME' does not match demucs-ffi version '$version'."
exit 1
fi
- run: cargo fmt --all -- --check
- run: cargo clippy --locked -p demucs-ffi --all-targets -- -D warnings
- run: cargo test --locked -p demucs-ffi

build-and-release:
needs: preflight
runs-on: macos-26
steps:
- uses: actions/checkout@v6
with:
path: demucs-rs
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-ios,aarch64-apple-ios-sim,aarch64-apple-darwin
# tvOS and visionOS are tier 3: the script builds them with
# `+nightly -Z build-std`, which needs rust-src.
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- uses: Swatinem/rust-cache@v2
with:
workspaces: demucs-rs
key: sdk-swift-apple
- name: Build XCFramework
working-directory: demucs-rs
run: .github/scripts/build-swift-xcframework.sh
- id: meta
working-directory: demucs-rs
run: |
echo "version=$(cat dist/swift/version.txt)" >> "$GITHUB_OUTPUT"
echo "checksum=$(cat dist/swift/DemucsFramework.checksum.txt)" >> "$GITHUB_OUTPUT"
- id: release
env:
INPUT_RELEASE_TAG: ${{ inputs.release_tag }}
run: |
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
release_tag="${GITHUB_REF_NAME}"
else
release_tag="${INPUT_RELEASE_TAG}"
fi
echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT"
- name: Validate release tag
if: startsWith(github.ref, 'refs/tags/') || inputs.publish
env:
VERSION: ${{ steps.meta.outputs.version }}
RELEASE_TAG: ${{ steps.release.outputs.release_tag }}
HAS_PAT: ${{ secrets.DEMUCS_SWIFT_PAT != '' }}
run: |
# Fail before the GitHub release exists rather than after, when the
# push to demucs-swift would be the first thing to notice.
if [ "$HAS_PAT" != "true" ]; then
echo "::error::DEMUCS_SWIFT_PAT is not set; it needs contents:write on SplitFireAI/demucs-swift."
exit 1
fi
if [ -z "$RELEASE_TAG" ]; then
echo "::error::release_tag is required when publish=true on workflow_dispatch runs."
exit 1
fi
if [ "$RELEASE_TAG" != "swift-v$VERSION" ]; then
echo "::error::Release tag '$RELEASE_TAG' does not match 'swift-v$VERSION' (demucs-ffi version)."
exit 1
fi
- name: Checkout demucs-swift
Comment thread
setoelkahfi marked this conversation as resolved.
uses: actions/checkout@v6
with:
repository: SplitFireAI/demucs-swift
# Read-only use for dry runs; the PAT is only needed to push, but a
# private repository would need it to clone as well.
token: ${{ secrets.DEMUCS_SWIFT_PAT || github.token }}
path: demucs-swift
# Full history and tags: the push step rebases onto main, and the
# commit step compares against an existing release tag.
fetch-depth: 0
# Gate the release on the Swift package building and passing its tests
# against the framework just built. Package.swift prefers a local
# DemucsFramework.xcframework over the release URL when one is present,
# so this exercises the new binary without publishing it first.
- name: Test Swift package against the new framework
working-directory: demucs-swift
run: |
cp -R ../demucs-rs/dist/swift/DemucsFramework.xcframework DemucsFramework.xcframework
cp ../demucs-rs/dist/swift/Package/Sources/Demucs/demucs_ffi.swift Sources/Demucs/demucs_ffi.swift
swift test
rm -rf DemucsFramework.xcframework .build
- name: Upload build artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v7
with:
name: demucs-swift-xcframework
path: |
demucs-rs/dist/swift/DemucsFramework.xcframework.zip
demucs-rs/dist/swift/DemucsFramework.checksum.txt
demucs-rs/dist/swift/version.txt
demucs-rs/dist/swift/Package/Sources/Demucs/demucs_ffi.swift
- name: Update Swift package
if: startsWith(github.ref, 'refs/tags/') || inputs.publish
env:
CHECKSUM: ${{ steps.meta.outputs.checksum }}
RELEASE_URL: https://github.com/${{ github.repository }}/releases/download/${{ steps.release.outputs.release_tag }}/DemucsFramework.xcframework.zip
run: |
python3 - <<'PY'
import os, pathlib, re
package = pathlib.Path("demucs-swift/Package.swift")
source = package.read_text()
source, urls = re.subn(r'(let releaseFrameworkURL =\s*")([^"]+)(")', rf'\g<1>{os.environ["RELEASE_URL"]}\g<3>', source, count=1)
source, sums = re.subn(r'(let releaseFrameworkChecksum =\s*")([^"]+)(")', rf'\g<1>{os.environ["CHECKSUM"]}\g<3>', source, count=1)
if (urls, sums) != (1, 1):
raise RuntimeError("Package.swift release constants were not found")
package.write_text(source)
PY
# Commit locally before the GitHub release exists, so a conflicting tag
# stops the run while nothing has been published yet.
- name: Commit Swift package
if: startsWith(github.ref, 'refs/tags/') || inputs.publish
working-directory: demucs-swift
env:
# SwiftPM resolves `from: "0.1.0"` against plain semver tags, so
# demucs-swift is tagged X.Y.Z rather than swift-vX.Y.Z.
VERSION: ${{ steps.meta.outputs.version }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Package.swift Sources/Demucs/demucs_ffi.swift
git diff --cached --quiet || git commit -m "Release $VERSION"
# A rerun of the same release finds its own tag, which is fine. A tag
# pinning a different framework would leave SwiftPM users on it.
if git rev-parse --verify --quiet "refs/tags/$VERSION^{commit}" > /dev/null; then
if ! git diff --quiet "refs/tags/$VERSION" HEAD -- Package.swift Sources/Demucs/demucs_ffi.swift; then
echo "::error::Tag $VERSION already exists on demucs-swift with a different Package.swift or bindings."
exit 1
fi
fi
- name: Create GitHub release
if: startsWith(github.ref, 'refs/tags/') || inputs.publish
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ steps.release.outputs.release_tag }}
name: Swift SDK ${{ steps.meta.outputs.version }}
generate_release_notes: true
# Keep "Latest" on the CLI/plugin releases from release.yml.
make_latest: false
files: |
demucs-rs/dist/swift/DemucsFramework.xcframework.zip
demucs-rs/dist/swift/DemucsFramework.checksum.txt
- name: Push and tag Swift package
if: startsWith(github.ref, 'refs/tags/') || inputs.publish
working-directory: demucs-swift
env:
VERSION: ${{ steps.meta.outputs.version }}
run: |
# main may have moved while the Swift tests ran. If this fails, the
# release above is already up; rerunning the job replaces its assets
# and retries the push.
git pull --rebase origin main
git push origin HEAD:main
Comment thread
setoelkahfi marked this conversation as resolved.
if git rev-parse --verify --quiet "refs/tags/$VERSION^{commit}" > /dev/null; then
echo "Tag $VERSION already exists with the same release files."
else
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"
fi
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ The browser version compiles the full inference pipeline to WebAssembly and runs

Model weights are downloaded automatically from Hugging Face on first use (both CLI and web).

## Swift SDK

[demucs-swift](https://github.com/SplitFireAI/demucs-swift) packages the same
inference for iOS, macOS, tvOS, and visionOS apps, GPU-accelerated through
Metal. It is built from the `demucs-ffi` crate (UniFFI) and published by the
`Release SDK Swift` workflow whenever a `swift-vX.Y.Z` tag is pushed; the tag
must match the `demucs-ffi` crate version.

To build the XCFramework locally (macOS with Xcode, a stable toolchain with the
iOS/macOS targets, and a nightly with `rust-src` for tvOS/visionOS):

```bash
make swift-xcframework
```

## CLI

```
Expand Down Expand Up @@ -175,12 +190,14 @@ make web
| `make dev` | WASM debug + Vite dev server |
| `make dev-release` | WASM release + Vite dev server |
| `make web` | Full production web build |
| `make swift-xcframework` | Build the demucs-swift XCFramework and bindings into `dist/swift` (macOS) |
| `make clean` | Remove all build artifacts |

### Running Tests

```bash
cargo test -p demucs-core
cargo test -p demucs-ffi
```

## Project Structure
Expand All @@ -192,6 +209,8 @@ demucs-rs/
β”œβ”€β”€ demucs-cli/ Native CLI binary (clap, symphonia, indicatif)
β”œβ”€β”€ demucs-plugin/ DAW plugin β€” VST3/CLAP via nih-plug (macOS, SwiftUI editor)
β”œβ”€β”€ demucs-wasm/ Thin wasm-bindgen adapter over demucs-core
β”œβ”€β”€ demucs-ffi/ UniFFI bridge for the Swift SDK (demucs-swift)
β”œβ”€β”€ uniffi-bindgen/ Generates the Swift bindings from demucs-ffi
β”œβ”€β”€ web/ React + TypeScript frontend (Vite)
└── bench/ Python benchmark & validation suite
```
Expand Down
Loading