diff --git a/.github/workflows/swift_ci.yml b/.github/workflows/swift_ci.yml index 6e2a4de..5d54f99 100644 --- a/.github/workflows/swift_ci.yml +++ b/.github/workflows/swift_ci.yml @@ -6,7 +6,7 @@ on: - crates/algo_models_ffi/** - crates/algo_models/** - packages/swift/AlgoModels/** - - scripts/build/swift.ts + - scripts/build/languages/swift.ts - scripts/build/index.ts - "!*.md" - .github/workflows/swift_ci.yml @@ -20,7 +20,7 @@ on: - crates/algo_models_ffi/** - crates/algo_models/** - packages/swift/AlgoModels/** - - scripts/build/swift.ts + - scripts/build/languages/swift.ts - scripts/build/index.ts - "!*.md" - .github/workflows/swift_ci.yml @@ -30,7 +30,7 @@ permissions: contents: write env: - CRATE: algo_models_ffi + CRATE: algo_models PACKAGE: AlgoModels jobs: @@ -56,14 +56,14 @@ jobs: - name: Install iOS Simulator run: xcodebuild -downloadPlatform iOS - name: Build - run: bun scripts/build/swift.ts ${{ env.CRATE }} + run: bun scripts/build ${{ env.CRATE }} swift # Ideally we'd use a matrix for the platforms, but due to the limitations of Mac runners on GitHub it's probably better to just have a single job with multiple steps - name: Test (macOS) - run: cd crates/${{ env.CRATE}}/tests/swift/ && xcodebuild -scheme ${{ env.PACKAGE }}Tests-Package test -destination "platform=macOS" + run: cd crates/${{ env.CRATE}}_ffi/tests/swift/ && xcodebuild -scheme ${{ env.PACKAGE }}Tests-Package test -destination "platform=macOS" - name: Test (iOS) - run: cd crates/${{ env.CRATE}}/tests/swift/ && xcodebuild -scheme ${{ env.PACKAGE }}Tests-Package test -destination "platform=iOS Simulator,name=iPhone 16,OS=latest" + run: cd crates/${{ env.CRATE}}_ffi/tests/swift/ && xcodebuild -scheme ${{ env.PACKAGE }}Tests-Package test -destination "platform=iOS Simulator,name=iPhone 16,OS=latest" - name: Test (Catalyst) - run: cd crates/${{ env.CRATE}}/tests/swift/ && xcodebuild -scheme ${{ env.PACKAGE }}Tests-Package test -destination "platform=macOS,variant=Mac Catalyst" + run: cd crates/${{ env.CRATE}}_ffi/tests/swift/ && xcodebuild -scheme ${{ env.PACKAGE }}Tests-Package test -destination "platform=macOS,variant=Mac Catalyst" - name: Commit Package uses: stefanzweifel/git-auto-commit-action@v5 if: github.event_name == 'push' diff --git a/scripts/build/index.ts b/scripts/build/index.ts index fe28a28..8788844 100644 --- a/scripts/build/index.ts +++ b/scripts/build/index.ts @@ -1,10 +1,13 @@ import path from "path"; import { spawn } from "child_process"; import { resolve } from "path"; +import { buildPython } from "./languages/python.ts"; +import { buildSwift } from "./languages/swift.ts"; +import { buildTypescript } from "./languages/typescript.ts"; export const REPO_ROOT = path.resolve(__dirname, "../../"); - process.chdir(REPO_ROOT); + export function toPascalCase(string: string): string { return string .replaceAll("_", " ") @@ -13,14 +16,6 @@ export function toPascalCase(string: string): string { .join(""); } -export function getCrateNanme(): string { - if (process.argv[2] == undefined) { - throw new Error("Crate must be specified as the argument"); - } - - return process.argv[2].replace("_ffi", ""); -} - export function run(command: string, cwd: string | null = null): Promise { return new Promise((resolvePromise) => { console.log(`Running '${command}'`); @@ -52,3 +47,38 @@ export function run(command: string, cwd: string | null = null): Promise { }); }); } + +const languages = { + python: buildPython, + swift: buildSwift, + typescript: buildTypescript, +}; + +const crates = ["algo_models"]; + +if (process.argv.length !== 4) { + throw new Error("Usage: bun scripts/build "); +} + +const crate = process.argv[2]; +const language = process.argv[3]; + +if (language !== "all" && !Object.keys(languages).includes(language)) { + throw new Error( + "Language must be one of: all, " + Object.keys(languages).join(", "), + ); +} + +if (!crates.includes(crate)) { + throw new Error("Crate must be one of: " + crates.join(", ")); +} + +if (language === "all") { + await Promise.all( + Object.keys(languages).map(async (language) => { + await languages[language](crate); + }), + ); +} else { + await languages[language](crate); +} diff --git a/scripts/build/languages/python.ts b/scripts/build/languages/python.ts new file mode 100644 index 0000000..cf58dc0 --- /dev/null +++ b/scripts/build/languages/python.ts @@ -0,0 +1,5 @@ +import { getCrateNanme, run } from ".."; + +export async function buildPython(crate: string) { + await run(`maturin build -m crates/${crate}_ffi/Cargo.toml`); +} diff --git a/scripts/build/languages/swift.ts b/scripts/build/languages/swift.ts new file mode 100644 index 0000000..57f7678 --- /dev/null +++ b/scripts/build/languages/swift.ts @@ -0,0 +1,94 @@ +import { toPascalCase, run } from ".."; +import * as fs from "fs"; + +export async function buildSwift(crate: string) { + const targets = ["aarch64-apple-ios"]; + const fatTargets: Record = { + "ios-sim": ["x86_64-apple-ios", "aarch64-apple-ios-sim"], + catalyst: ["x86_64-apple-ios-macabi", "aarch64-apple-ios-macabi"], + macos: ["x86_64-apple-darwin", "aarch64-apple-darwin"], + }; + + let cargoBuildCmd = `cargo --color always build --manifest-path crates/${crate}_ffi/Cargo.toml --features ffi_uniffi`; + + const allTargets = [...Object.values(fatTargets).flat(), ...targets]; + + await Promise.all( + allTargets.map(async (target) => { + await run(`rustup target add ${target}`); + cargoBuildCmd += ` --target ${target}`; + }), + ); + + await run(cargoBuildCmd); + + await run( + `cargo --color always run -p uniffi-bindgen generate --no-format --library target/aarch64-apple-darwin/debug/lib${crate}_ffi.dylib --language swift --out-dir target/debug/swift/${crate}`, + ); + + let createXcfCmd = "xcodebuild -create-xcframework"; + targets.forEach((target) => { + createXcfCmd += ` -library target/${target}/debug/lib${crate}_ffi.dylib -headers target/debug/swift/${crate}/`; + }); + + await Promise.all( + Object.keys(fatTargets).map(async (fatTargetName) => { + const libPaths: string[] = []; + fatTargets[fatTargetName].forEach((target) => { + libPaths.push(`target/${target}/debug/lib${crate}_ffi.dylib`); + }); + + await run( + `lipo -create ${libPaths.join(" ")} -output target/debug/lib${crate}_ffi-${fatTargetName}.dylib`, + ); + + createXcfCmd += ` -library target/debug/lib${crate}_ffi-${fatTargetName}.dylib -headers target/debug/swift/${crate}/`; + }), + ); + + const swiftPackage = toPascalCase(crate); + createXcfCmd += ` -output packages/swift/${swiftPackage}/Frameworks/${crate}.xcframework`; + + if ( + fs.existsSync( + `packages/swift/${swiftPackage}/Frameworks/${crate}.xcframework`, + ) + ) { + fs.rmdirSync( + `packages/swift/${swiftPackage}/Frameworks/${crate}.xcframework`, + { + recursive: true, + }, + ); + } + + // xcframework needs the modulemap to be named module.modulemap + fs.renameSync( + `target/debug/swift/${crate}/${crate}FFI.modulemap`, + `target/debug/swift/${crate}/module.modulemap`, + ); + + // replace var with let to resolve swift concurrency issues + // I believe this is fixed in https://github.com/mozilla/uniffi-rs/pull/2294 + // The above PR is available in uniffi-rs 0.29.0, but we won't be updating until + // Nord generators (i.e. Golang) are updated to use 0.29.0 + let content = fs.readFileSync( + `target/debug/swift/${crate}/${crate}.swift`, + "utf-8", + ); + content = content.replace( + "private var initializationResult", + "private let initializationResult", + ); + + fs.writeFileSync(`target/debug/swift/${crate}/${crate}.swift`, content); + + await run(createXcfCmd); + + fs.renameSync( + `target/debug/swift/${crate}/${crate}.swift`, + `packages/swift/${swiftPackage}/Sources/${swiftPackage}/${swiftPackage}.swift`, + ); + + console.log(`Updated ${swiftPackage} in packages/swift/${swiftPackage}/`); +} diff --git a/scripts/build/languages/typescript.ts b/scripts/build/languages/typescript.ts new file mode 100644 index 0000000..c8d487c --- /dev/null +++ b/scripts/build/languages/typescript.ts @@ -0,0 +1,13 @@ +import { run } from ".."; +import * as fs from "fs"; + +export async function buildTypescript(crate: string) { + await run( + `wasm-pack build crates/${crate}_ffi --target web --out-dir ../../packages/typescript/${crate} -- --color always --no-default-features --features ffi_wasm`, + ); + + // Remove the generated .gitignore file from the pkg directory + if (fs.existsSync(`packages/typescript/${crate}/.gitignore`)) { + fs.rmSync(`packages/typescript/${crate}/.gitignore`); + } +} diff --git a/scripts/build/python.ts b/scripts/build/python.ts deleted file mode 100644 index b45a640..0000000 --- a/scripts/build/python.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { getCrateNanme, run } from "./index.ts"; - -const crate = getCrateNanme(); -await run(`maturin build -m crates/${crate}_ffi/Cargo.toml`); diff --git a/scripts/build/swift.ts b/scripts/build/swift.ts deleted file mode 100644 index 189fbda..0000000 --- a/scripts/build/swift.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { REPO_ROOT, getCrateNanme, toPascalCase, run } from "."; -import * as fs from "fs"; - -process.chdir(REPO_ROOT); -const targets = ["aarch64-apple-ios"]; - -const fatTargets: Record = { - "ios-sim": ["x86_64-apple-ios", "aarch64-apple-ios-sim"], - catalyst: ["x86_64-apple-ios-macabi", "aarch64-apple-ios-macabi"], - macos: ["x86_64-apple-darwin", "aarch64-apple-darwin"], -}; - -const crate = getCrateNanme(); - -let cargoBuildCmd = `cargo --color always build --manifest-path crates/${crate}_ffi/Cargo.toml --features ffi_uniffi`; - -const allTargets = [...Object.values(fatTargets).flat(), ...targets]; - -await Promise.all( - allTargets.map(async (target) => { - await run(`rustup target add ${target}`); - cargoBuildCmd += ` --target ${target}`; - }), -); - -await run(cargoBuildCmd); - -await run( - `cargo --color always run -p uniffi-bindgen generate --no-format --library target/aarch64-apple-darwin/debug/lib${crate}_ffi.dylib --language swift --out-dir target/debug/swift/${crate}`, -); - -let createXcfCmd = "xcodebuild -create-xcframework"; -targets.forEach((target) => { - createXcfCmd += ` -library target/${target}/debug/lib${crate}_ffi.dylib -headers target/debug/swift/${crate}/`; -}); - -await Promise.all( - Object.keys(fatTargets).map(async (fatTargetName) => { - const libPaths: string[] = []; - fatTargets[fatTargetName].forEach((target) => { - libPaths.push(`target/${target}/debug/lib${crate}_ffi.dylib`); - }); - - await run( - `lipo -create ${libPaths.join(" ")} -output target/debug/lib${crate}_ffi-${fatTargetName}.dylib`, - ); - - createXcfCmd += ` -library target/debug/lib${crate}_ffi-${fatTargetName}.dylib -headers target/debug/swift/${crate}/`; - }), -); - -const swiftPackage = toPascalCase(crate); -createXcfCmd += ` -output packages/swift/${swiftPackage}/Frameworks/${crate}.xcframework`; - -if ( - fs.existsSync( - `packages/swift/${swiftPackage}/Frameworks/${crate}.xcframework`, - ) -) { - fs.rmdirSync( - `packages/swift/${swiftPackage}/Frameworks/${crate}.xcframework`, - { - recursive: true, - }, - ); -} - -// xcframework needs the modulemap to be named module.modulemap -fs.renameSync( - `target/debug/swift/${crate}/${crate}FFI.modulemap`, - `target/debug/swift/${crate}/module.modulemap`, -); - -// replace var with let to resolve swift concurrency issues -// I believe this is fixed in https://github.com/mozilla/uniffi-rs/pull/2294 -// The above PR is available in uniffi-rs 0.29.0, but we won't be updating until -// Nord generators (i.e. Golang) are updated to use 0.29.0 -let content = fs.readFileSync( - `target/debug/swift/${crate}/${crate}.swift`, - "utf-8", -); -content = content.replace( - "private var initializationResult", - "private let initializationResult", -); - -fs.writeFileSync(`target/debug/swift/${crate}/${crate}.swift`, content); - -await run(createXcfCmd); - -fs.renameSync( - `target/debug/swift/${crate}/${crate}.swift`, - `packages/swift/${swiftPackage}/Sources/${swiftPackage}/${swiftPackage}.swift`, -); - -console.log(`Updated ${swiftPackage} in packages/swift/${swiftPackage}/`); diff --git a/scripts/build/typescript.ts b/scripts/build/typescript.ts deleted file mode 100644 index 8dcc89f..0000000 --- a/scripts/build/typescript.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { run, getCrateNanme } from "."; -import * as fs from "fs"; - -const crate = getCrateNanme(); - -await run( - `wasm-pack build crates/${crate}_ffi --target web --out-dir ../../packages/typescript/${crate} -- --color always --no-default-features --features ffi_wasm`, -); - -// Remove the generated .gitignore file from the pkg directory -if (fs.existsSync(`packages/typescript/${crate}/.gitignore`)) { - fs.rmSync(`packages/typescript/${crate}/.gitignore`); -}