use compose_rust::codegen::{
ANDROID_BRIDGE_RELATIVE_PATH, ANDROID_FAST_NATIVE_RELATIVE_PATH, EVENT_VECTOR_RELATIVE_PATH,
GENERATED_RELATIVE_PATH, JNI_RUST_RELATIVE_PATH, MUTATION_VECTOR_RELATIVE_PATH,
SCHEMA_HASH_RELATIVE_PATH, VECTOR_DESCRIPTION_RELATIVE_PATH, WASM_RUST_RELATIVE_PATH,
WEB_BRIDGE_RELATIVE_PATH, WEB_LOADER_RELATIVE_PATH, generate_android_bridge_kotlin,
generate_event_vector, generate_fast_native_kotlin, generate_jni_rust, generate_kotlin,
generate_mutation_vector, generate_schema_hash, generate_vector_description,
generate_wasm_rust, generate_web_bridge_kotlin, generate_web_loader_js,
};
use std::path::{Path, PathBuf};
fn main() {
if let Err(message) = run() {
eprintln!("codegen: {message}");
std::process::exit(1);
}
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let manifest_dir = output_tree(
Path::new(env!("CARGO_MANIFEST_DIR")),
std::env::var_os("CARGO_MANIFEST_DIR").map(PathBuf::from),
)?;
write(
manifest_dir.join(GENERATED_RELATIVE_PATH),
generate_kotlin().as_bytes(),
)?;
write(
manifest_dir.join(SCHEMA_HASH_RELATIVE_PATH),
generate_schema_hash().as_bytes(),
)?;
write(
manifest_dir.join(MUTATION_VECTOR_RELATIVE_PATH),
&generate_mutation_vector()?,
)?;
write(
manifest_dir.join(EVENT_VECTOR_RELATIVE_PATH),
&generate_event_vector()?,
)?;
write(
manifest_dir.join(VECTOR_DESCRIPTION_RELATIVE_PATH),
generate_vector_description().as_bytes(),
)?;
write(
manifest_dir.join(JNI_RUST_RELATIVE_PATH),
generate_jni_rust().as_bytes(),
)?;
write(
manifest_dir.join(ANDROID_BRIDGE_RELATIVE_PATH),
generate_android_bridge_kotlin().as_bytes(),
)?;
write(
manifest_dir.join(ANDROID_FAST_NATIVE_RELATIVE_PATH),
generate_fast_native_kotlin().as_bytes(),
)?;
write(
manifest_dir.join(WASM_RUST_RELATIVE_PATH),
generate_wasm_rust().as_bytes(),
)?;
write(
manifest_dir.join(WEB_BRIDGE_RELATIVE_PATH),
generate_web_bridge_kotlin().as_bytes(),
)?;
write(
manifest_dir.join(WEB_LOADER_RELATIVE_PATH),
generate_web_loader_js().as_bytes(),
)?;
Ok(())
}
fn output_tree(compiled_in: &Path, invoked_in: Option<PathBuf>) -> Result<PathBuf, String> {
let Some(invoked_in) = invoked_in else {
return Ok(compiled_in.to_path_buf());
};
if same_directory(compiled_in, &invoked_in) {
return Ok(invoked_in);
}
Err(format!(
"refusing to write into a checkout this binary was not compiled in.\n \
compiled in: {}\n \
running in: {}\n\
Both checkouts build into one directory, so cargo reused the binary compiled in \
the first one and the paths compiled into it point there. Give this checkout its \
own build directory: run scripts/setup-worktrees.sh, which removes a target-dir \
from .cargo/config.toml, and unset CARGO_TARGET_DIR. Then run this again.",
compiled_in.display(),
invoked_in.display(),
))
}
fn same_directory(left: &Path, right: &Path) -> bool {
if left == right {
return true;
}
match (std::fs::canonicalize(left), std::fs::canonicalize(right)) {
(Ok(left), Ok(right)) => left == right,
_ => false,
}
}
fn write(path: PathBuf, contents: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
let parent = path.parent().ok_or("generated output has no parent")?;
std::fs::create_dir_all(parent)?;
std::fs::write(&path, contents)?;
println!("generated {}", path.display());
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn invoked(path: &str) -> Option<PathBuf> {
Some(PathBuf::from(path))
}
#[test]
fn fr7_writes_the_checkout_it_was_compiled_in() {
let tree = output_tree(Path::new("/one/compose-rust"), invoked("/one/compose-rust"))
.expect("the checkout that compiled it is the checkout it writes");
assert_eq!(tree, PathBuf::from("/one/compose-rust"));
}
#[test]
fn fr7_refuses_a_checkout_it_was_not_compiled_in() {
let message = output_tree(Path::new("/one/compose-rust"), invoked("/two/compose-rust"))
.expect_err("a binary compiled elsewhere must not write here");
assert!(
message.contains("/one/compose-rust") && message.contains("/two/compose-rust"),
"the message has to name both checkouts, got: {message}"
);
assert!(
message.contains("setup-worktrees.sh"),
"the message has to say how to fix it, got: {message}"
);
}
#[test]
fn fr7_falls_back_to_the_compiled_checkout_outside_cargo() {
let tree = output_tree(Path::new("/one/compose-rust"), None)
.expect("run outside cargo there is no other checkout to mean");
assert_eq!(tree, PathBuf::from("/one/compose-rust"));
}
#[test]
fn nfr12_two_names_for_one_directory_are_one_checkout() {
let temporary = std::env::temp_dir();
let canonical = std::fs::canonicalize(&temporary).expect("the temporary directory exists");
assert!(
same_directory(&temporary, &canonical),
"{} and {} are the same directory",
temporary.display(),
canonical.display()
);
}
}