alef 0.85.15

Opinionated polyglot binding generator for Rust libraries
Documentation
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};

const HASH_MARKER: &str = concat!("alef:", "hash:");
const OWNERSHIP_MARKER: &str = "generated by alef";
const PROTECTED_PATHS: &[&str] = &[
    "packages/swift/Sources/RustBridgeC/RustBridgeC.c",
    "packages/swift/Sources/RustBridge/RustBridge.swift",
    "packages/swift/rust/Cargo.toml",
    "packages/swift/rust/build.rs",
];

fn alef_binary() -> PathBuf {
    PathBuf::from(env!("CARGO_BIN_EXE_alef"))
}

fn write_fixture(root: &Path) {
    fs::create_dir_all(root.join("src")).expect("create fixture source directory");
    fs::write(
        root.join("Cargo.toml"),
        "[package]\nname = \"atomicity-fixture\"\nversion = \"0.1.0\"\nedition = \"2024\"\n",
    )
    .expect("write fixture Cargo.toml");
    fs::write(
        root.join("src/lib.rs"),
        "pub struct Record { pub value: String }\n\npub fn record_value(record: Record) -> String { record.value }\n",
    )
    .expect("write fixture source");
    fs::write(
        root.join("alef.toml"),
        format!(
            "[workspace]\nalef_version = \"{}\"\nlanguages = [\"swift\", \"go\", \"zig\"]\n\n\
             [[crates]]\nname = \"atomicity-fixture\"\nsources = [\"src/lib.rs\"]\n\
             version_from = \"Cargo.toml\"\n\n[crates.generate]\npublic_api = false\n",
            env!("CARGO_PKG_VERSION")
        ),
    )
    .expect("write alef config");
}

fn seed_owned_files(root: &Path) {
    for relative in PROTECTED_PATHS {
        let path = root.join(relative);
        fs::create_dir_all(path.parent().expect("protected file parent")).expect("create protected parent");
        let content = fs::read_to_string(&path).unwrap_or_else(|_| format!("protected {relative}\n"));
        fs::write(path, format!("// {HASH_MARKER}deadbeef\n{content}")).expect("mark protected file as prior-owned");
    }

    let swift_orphan = root.join("packages/swift/rust/src/obsolete.rs");
    let go_orphan = root.join("packages/go/obsolete.go");
    let unselected_orphan = root.join("packages/python/obsolete.py");
    for orphan in [&swift_orphan, &go_orphan, &unselected_orphan] {
        fs::create_dir_all(orphan.parent().expect("orphan parent")).expect("create orphan parent");
        fs::write(orphan, format!("// {HASH_MARKER}deadbeef\n")).expect("seed orphan");
    }

    let crate_cache = fs::read_dir(root.join(".alef"))
        .expect("read Alef cache")
        .filter_map(Result::ok)
        .map(|entry| entry.path())
        .find(|path| path.is_dir() && path.join("ir.json").is_file())
        .expect("find crate-scoped Alef cache");
    let hashes = crate_cache.join("hashes");
    fs::create_dir_all(&hashes).expect("create manifest directory");
    let mut swift_manifest = PROTECTED_PATHS
        .iter()
        .map(|path| root.join(path).display().to_string())
        .collect::<Vec<_>>();
    swift_manifest.push(swift_orphan.display().to_string());
    fs::write(
        hashes.join("swift.manifest"),
        format!("{}\n", swift_manifest.join("\n")),
    )
    .expect("write Swift manifest");
    fs::write(hashes.join("go.manifest"), format!("{}\n", go_orphan.display())).expect("write Go manifest");
    fs::write(
        hashes.join("python.manifest"),
        format!("{}\n", unselected_orphan.display()),
    )
    .expect("write unselected manifest");
}

fn fixture_command(root: &Path, program: &Path, fail_swift_post_build: bool) -> Command {
    let fake_bin = root.join("fake-bin");
    fs::create_dir_all(&fake_bin).expect("create fixture binary directory");
    let fake_cargo = fake_bin.join(format!("cargo{}", std::env::consts::EXE_SUFFIX));
    if !fake_cargo.exists() {
        let source = root.join("cargo_shim.rs");
        fs::write(&source, include_str!("cli_generate_atomicity/cargo_shim.rs")).expect("write Cargo shim source");
        let compiled = Command::new("rustc")
            .args(["--edition=2024", "--crate-name", "atomicity_cargo_shim"])
            .arg(&source)
            .arg("-o")
            .arg(&fake_cargo)
            .output()
            .expect("compile native Cargo shim");
        assert!(
            compiled.status.success(),
            "compile native Cargo shim: {}",
            String::from_utf8_lossy(&compiled.stderr)
        );
    }
    let path = std::env::var_os("PATH").unwrap_or_default();
    let mut command = Command::new(program);
    command.current_dir(root).env(
        "PATH",
        std::env::join_paths(std::iter::once(fake_bin).chain(std::env::split_paths(&path))).unwrap(),
    );
    command.env("REAL_CARGO", env!("CARGO"));
    command.env("FAIL_SWIFT_POST_BUILD", if fail_swift_post_build { "1" } else { "0" });
    command
}

fn run_generate(root: &Path, fail_swift_post_build: bool) -> Output {
    fixture_command(root, &alef_binary(), fail_swift_post_build)
        .args(["generate", "--lang", "swift,go,zig"])
        .output()
        .expect("run alef generate")
}

#[test]
fn native_cargo_shim_intercepts_both_path_styles_and_delegates_other_commands() {
    let fixture = tempfile::tempdir().expect("create shim fixture");
    let root = fixture.path();
    for manifest in ["packages/swift/rust/Cargo.toml", r"packages\swift\rust\Cargo.toml"] {
        for (fail, status) in [(true, 17), (false, 0)] {
            let output = fixture_command(root, Path::new("cargo"), fail)
                .args(["build", "--manifest-path", manifest])
                .output()
                .expect("invoke Cargo shim through PATH");
            assert_eq!(output.status.code(), Some(status));
            assert_eq!(
                String::from_utf8(output.stderr).unwrap().trim(),
                format!("atomicity fixture cargo: Swift post-build exit {status}")
            );
        }
    }
    let expected = Command::new(env!("CARGO")).arg("--version").output().unwrap();
    let delegated = fixture_command(root, Path::new("cargo"), true)
        .arg("--version")
        .output()
        .unwrap();
    assert!(expected.status.success());
    assert_eq!(delegated.status.code(), expected.status.code());
    assert_eq!(delegated.stdout, expected.stdout);
    assert_eq!(delegated.stderr, expected.stderr);
}

fn generated_hashed_files(root: &Path) -> Vec<PathBuf> {
    walkdir::WalkDir::new(root.join("packages"))
        .into_iter()
        .filter_map(Result::ok)
        .filter(|entry| entry.file_type().is_file())
        .map(walkdir::DirEntry::into_path)
        .filter(|path| fs::read_to_string(path).is_ok_and(|content| content.contains(HASH_MARKER)))
        .collect()
}

fn generated_marked_files(root: &Path) -> Vec<PathBuf> {
    walkdir::WalkDir::new(root.join("packages"))
        .into_iter()
        .filter_map(Result::ok)
        .filter(|entry| entry.file_type().is_file())
        .map(walkdir::DirEntry::into_path)
        .filter(|path| {
            fs::read_to_string(path).is_ok_and(|content| content.to_ascii_lowercase().contains(OWNERSHIP_MARKER))
        })
        .collect()
}

fn generated_ownership_paths(root: &Path) -> Vec<PathBuf> {
    walkdir::WalkDir::new(root.join(".alef"))
        .into_iter()
        .filter_map(Result::ok)
        .filter(|entry| {
            entry.file_type().is_file()
                && entry.file_name().to_string_lossy().starts_with("generate-")
                && entry.file_name().to_string_lossy().ends_with("-ownership.manifest")
        })
        .flat_map(|entry| {
            fs::read_to_string(entry.path())
                .expect("read generation ownership manifest")
                .lines()
                .filter(|line| !line.is_empty())
                .map(PathBuf::from)
                .collect::<Vec<_>>()
        })
        .collect()
}

#[test]
fn failed_swift_post_build_preserves_owned_files_and_written_outputs() {
    let fixture = tempfile::tempdir().expect("create fixture directory");
    let root = fixture.path();
    write_fixture(root);

    let first_failure = run_generate(root, true);
    assert!(
        !first_failure.status.success(),
        "required Swift post-build unexpectedly succeeded"
    );
    let first_stderr = String::from_utf8_lossy(&first_failure.stderr);
    assert!(
        first_stderr.contains("atomicity fixture cargo: Swift post-build exit 17")
            && first_stderr.contains("status 17"),
        "unexpected first failure:\n{first_stderr}"
    );
    // `alef generate` stamps exactly once, after post-build AND the format pass (0.67.6,
    // 848db7774): a run that dies in post-build ships no `alef:hash:` line at all, by design --
    // a stamp over unformatted bytes is one `poly` then refuses to ever format. What
    // failure-atomicity guarantees is that bytes already written stay on disk under their
    // ownership marker; the stamp is asserted on the successful run below. ~keep
    let generated = generated_marked_files(root);
    assert!(
        !generated.is_empty(),
        "outputs written before the first required post-build failure must stay on disk; stderr:\n{first_stderr}"
    );
    assert!(
        generated.iter().any(|path| path.starts_with(root.join("packages/go"))),
        "Go output written before the later Swift post-build failure must stay on disk"
    );
    seed_owned_files(root);

    let failed = run_generate(root, true);
    assert!(
        !failed.status.success(),
        "required Swift cargo post-build unexpectedly succeeded"
    );
    let stderr = String::from_utf8_lossy(&failed.stderr);
    assert!(
        stderr.contains("atomicity fixture cargo: Swift post-build exit 17") && stderr.contains("status 17"),
        "unexpected failure:\n{stderr}"
    );

    for relative in PROTECTED_PATHS {
        assert!(
            root.join(relative).is_file(),
            "failed generation removed protected {relative}"
        );
    }
    assert!(root.join("packages/swift/rust/src/obsolete.rs").is_file());
    assert!(root.join("packages/go/obsolete.go").is_file());
    assert!(root.join("packages/python/obsolete.py").is_file());

    let first_success = run_generate(root, false);
    assert!(
        first_success.status.success(),
        "generation after the post-build failure must recover: {}",
        String::from_utf8_lossy(&first_success.stderr)
    );
    assert!(
        String::from_utf8_lossy(&first_success.stderr).contains("atomicity fixture cargo: Swift post-build exit 0"),
        "recovery must execute the successful fixture post-build"
    );
    assert!(
        !generated_hashed_files(root).is_empty(),
        "a successful generation must stamp its outputs once post-build and formatting have settled"
    );
    let owned_paths = generated_ownership_paths(root);
    assert!(
        !owned_paths.is_empty(),
        "successful generation must record owned outputs"
    );
    assert!(
        owned_paths.iter().all(|path| path.is_file()),
        "the first successful generation must leave every owned output on disk"
    );

    let cached_success = run_generate(root, false);
    assert!(
        cached_success.status.success(),
        "content-identical generation must succeed: {}",
        String::from_utf8_lossy(&cached_success.stderr)
    );
    assert!(
        owned_paths.iter().all(|path| path.is_file()),
        "a cache-hit generation must not sweep valid outputs from its ownership manifest"
    );
}