use crate::core::backend::GeneratedFile;
use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::ApiSurface;
use crate::scaffold::naming::{swift_min_ios, swift_min_macos};
use crate::scaffold::scaffold_meta;
use std::path::PathBuf;
pub(crate) fn scaffold_swift(_api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let meta = scaffold_meta(config);
let module = config.swift_module();
let repository_url = config.github_repo();
let min_macos_major = swift_min_macos(config).split('.').next().unwrap_or("13").to_string();
let min_ios_major = swift_min_ios(config).split('.').next().unwrap_or("16").to_string();
let crate_name = &config.name;
let binding_crate_name = format!("{crate_name}-swift");
let binding_crate_underscore = binding_crate_name.replace('-', "_");
let package_swift = format!(
r#"// swift-tools-version: 6.0
import PackageDescription
// NOTE: Run `cargo build -p {binding_crate}` before `swift build`.
// The build step generates Swift + C bridge sources; copy them into Sources/RustBridge
// and Sources/RustBridgeC before building. See README.md for the full workflow.
let package = Package(
name: "{module}",
platforms: [
.macOS(.v{min_macos}),
.iOS(.v{min_ios}),
],
products: [
.library(name: "{module}", targets: ["{module}"])
],
targets: [
// RustBridgeC: pure C/headers target. Swift files in RustBridge import this
// to access C types (RustStr, etc.) produced by swift-bridge.
// publicHeadersPath: "." exposes RustBridgeC.h to dependents.
.target(
name: "RustBridgeC",
path: "Sources/RustBridgeC",
publicHeadersPath: "."
),
// RustBridge: Swift wrapper around the Rust static library.
// Depends on RustBridgeC so the generated Swift files can use the C types.
// linkerSettings wire the Rust staticlib (lib{binding_underscore}.a) produced by
// `cargo build -p {binding_crate}` so `swift build` / `swift test` can resolve
// the `__swift_bridge__$*` C symbols. Both target/release and target/debug are
// searched so either cargo profile works.
.target(
name: "RustBridge",
dependencies: ["RustBridgeC"],
path: "Sources/RustBridge",
linkerSettings: [
.unsafeFlags([
"-L../../target/release",
"-L../../target/debug",
]),
.linkedLibrary("{binding_underscore}"),
.linkedFramework("Security", .when(platforms: [.macOS, .iOS])),
.linkedFramework("CoreFoundation", .when(platforms: [.macOS, .iOS])),
.linkedFramework("SystemConfiguration", .when(platforms: [.macOS])),
]
),
.target(name: "{module}", dependencies: ["RustBridge"], path: "Sources/{module}"),
.testTarget(name: "{module}Tests", dependencies: ["{module}"], path: "Tests/{module}Tests"),
]
)
"#,
module = module,
min_macos = min_macos_major,
min_ios = min_ios_major,
binding_crate = binding_crate_name,
binding_underscore = binding_crate_underscore,
);
let gitignore = ".build/\nPackages/\nxcuserdata/\nDerivedData/\n.swiftpm/\n*.xcodeproj\n";
let test_stub = format!(
r#"import XCTest
@testable import {module}
final class {module}Tests: XCTestCase {{
func testPlaceholder() throws {{
// Placeholder test so `swift test` has a target to run.
// Replace or extend with real tests against the {module} module.
XCTAssertTrue(true)
}}
}}
"#,
module = module,
);
let rust_bridge_c_header = build_rust_bridge_c_header(&binding_crate_name);
let rust_bridge_swift = format!(
r#"// Placeholder Swift source for the RustBridge target.
// Run `cargo build -p {binding_crate}` and copy the generated Swift files here
// (with `import RustBridgeC` prepended). See README.md for instructions.
//
// This file is intentionally minimal so SwiftPM accepts the target before
// the cargo build step has been run.
public enum RustBridgePlaceholder {{}}
"#,
binding_crate = binding_crate_name,
);
let module_modulemap = "// This modulemap is unused — the RustBridgeC target provides the C types.\n// SwiftPM discovers RustBridgeC.h via the publicHeadersPath setting.\n";
let editorconfig = "[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\n\n[*.swift]\nindent_style = space\nindent_size = 2\n";
let swiftformat = "lineLength = 120\nindent = 2\nusesTabs = false\n";
let readme = format!(
r#"# {module}
{description}
## Installation
Add to your `Package.swift`:
```swift
.package(path: "packages/swift"),
```
## Building
```sh
cargo build -p {binding_crate}
OUT=$(ls -dt target/debug/build/{binding_crate}-*/out 2>/dev/null | head -1)
cat "$OUT/SwiftBridgeCore.h" "$OUT/{binding_crate}/{binding_crate}.h" \
> packages/swift/Sources/RustBridgeC/RustBridgeC.h
{{ echo "import RustBridgeC"; cat "$OUT/SwiftBridgeCore.swift"; }} \
> packages/swift/Sources/RustBridge/SwiftBridgeCore.swift
{{ echo "import RustBridgeC"; cat "$OUT/{binding_crate}/{binding_crate}.swift"; }} \
> packages/swift/Sources/RustBridge/{binding_crate}.swift
swift build --package-path packages/swift
swift test --package-path packages/swift
```
The generated `Sources/RustBridgeC` and `Sources/RustBridge` artifacts are
rewritten after each Cargo clean or rebuild.
## License
{license}
"#,
module = module,
description = meta.description,
binding_crate = binding_crate_name,
license = meta.license,
);
let demo_swift = format!(
r#"import {module}
@main
struct Demo {{
static func main() {{
print("Demo: {module} loaded successfully")
// Add your API calls here after code generation
}}
}}
"#,
module = module,
);
let _root_package_swift = format!(
r#"// swift-tools-version: 6.0
// Root-level Package.swift — alef-generated for published distributions.
//
// This manifest uses `.binaryTarget` for pre-built XCFramework/artifact bundles.
// External consumers depend on this via `.package(url: "...", from: "...")`.
//
// For in-tree development, see `packages/swift/Package.swift` and
// `packages/swift/README.md` for the source-based workflow.
import PackageDescription
let package = Package(
name: "{module}",
platforms: [
.macOS(.v{min_macos}),
.iOS(.v{min_ios}),
],
products: [
.library(name: "{module}", targets: ["{module}"])
],
targets: [
// RustBridge: pre-built binary target containing the compiled Rust library
// for macOS (arm64, x86_64), iOS (device, simulator), and Linux (arm64, x86_64).
// The binary includes C headers for swift-bridge interop.
.binaryTarget(
name: "RustBridge",
url: "{repository_url}/releases/download/v__ALEF_SWIFT_VERSION__/{module}-rs.artifactbundle.zip",
checksum: "__ALEF_SWIFT_CHECKSUM__"
),
.target(
name: "{module}",
dependencies: ["RustBridge"],
path: "packages/swift/Sources/{module}"
),
]
)
"#,
module = module,
min_macos = min_macos_major,
min_ios = min_ios_major,
repository_url = repository_url,
);
Ok(vec![
GeneratedFile {
path: PathBuf::from("Package.swift"),
content: _root_package_swift,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/Package.swift"),
content: package_swift,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/.gitignore"),
content: gitignore.to_string(),
generated_header: false,
},
GeneratedFile {
path: PathBuf::from(format!("packages/swift/Tests/{module}Tests/{module}Tests.swift")),
content: test_stub,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/Sources/RustBridgeC/RustBridgeC.h"),
content: rust_bridge_c_header,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/Sources/RustBridge/module.modulemap"),
content: module_modulemap.to_string(),
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/Sources/RustBridge/RustBridge.swift"),
content: rust_bridge_swift,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/.editorconfig"),
content: editorconfig.to_string(),
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/.swiftformat"),
content: swiftformat.to_string(),
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/README.md"),
content: readme,
generated_header: false,
},
GeneratedFile {
path: PathBuf::from("packages/swift/Examples/Demo/main.swift"),
content: demo_swift,
generated_header: false,
},
])
}
fn build_rust_bridge_c_header(binding_crate_name: &str) -> String {
if let Some((core_h, crate_h)) = read_swift_bridge_headers(binding_crate_name) {
format!(
"#ifndef RUST_BRIDGE_C_H\n\
#define RUST_BRIDGE_C_H\n\
\n\
// Auto-generated by alef — do not edit by hand.\n\
// Concatenates SwiftBridgeCore.h and {binding_crate_name}.h produced by\n\
// `cargo build -p {binding_crate_name}` via swift_bridge_build.\n\
\n\
{core_h}\n\
{crate_h}\n\
#endif /* RUST_BRIDGE_C_H */\n"
)
} else {
format!(
"#ifndef RUST_BRIDGE_C_H\n\
#define RUST_BRIDGE_C_H\n\
\n\
// Placeholder header for the RustBridgeC SwiftPM target.\n\
// Run `cargo build -p {binding_crate_name}` and re-run `alef all` to populate.\n\
// The RustStr typedef below is the minimum required for SwiftBridgeCore.swift\n\
// to compile before the full cargo build has been run.\n\
\n\
#include <stdint.h>\n\
\n\
typedef struct RustStr {{ uint8_t* const start; uintptr_t len; }} RustStr;\n\
\n\
#endif /* RUST_BRIDGE_C_H */\n"
)
}
}
fn read_swift_bridge_headers(binding_crate_name: &str) -> Option<(String, String)> {
let cwd = std::env::current_dir().ok()?;
let workspace_root = std::iter::once(cwd.clone())
.chain(cwd.ancestors().skip(1).map(|p| p.to_path_buf()))
.take(8)
.find(|p| p.join("Cargo.lock").exists())?;
let target = workspace_root.join("target");
let crate_prefix = format!("{}-", binding_crate_name);
let mut best: Option<(std::time::SystemTime, PathBuf)> = None;
for profile in ["release", "debug"] {
let build_dir = target.join(profile).join("build");
let entries = match std::fs::read_dir(&build_dir) {
Ok(e) => e,
Err(_) => continue,
};
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if !name_str.starts_with(&crate_prefix) {
continue;
}
let out = entry.path().join("out");
let core_h = out.join("SwiftBridgeCore.h");
let crate_h = out.join(binding_crate_name).join(format!("{binding_crate_name}.h"));
if !core_h.exists() || !crate_h.exists() {
continue;
}
let mtime = std::fs::metadata(&core_h)
.and_then(|m| m.modified())
.unwrap_or(std::time::UNIX_EPOCH);
if best.as_ref().map(|(t, _)| mtime > *t).unwrap_or(true) {
best = Some((mtime, out));
}
}
}
let out = best?.1;
let core_h = std::fs::read_to_string(out.join("SwiftBridgeCore.h")).ok()?;
let crate_h = std::fs::read_to_string(out.join(binding_crate_name).join(format!("{binding_crate_name}.h"))).ok()?;
Some((core_h, crate_h))
}