Skip to content
Open
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
48 changes: 40 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ dirs = "5"
pathdiff = "0.2.3"
glob = "0.3"
flate2 = "1.1.2"
tar = "0.4"
tempfile = "3"
uuid = { version = "1.21.0", features = ["v4"] }

Expand Down
91 changes: 91 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

fn non_empty_env(name: &str) -> Option<String> {
Expand Down Expand Up @@ -66,4 +68,93 @@ fn main() {
} else {
println!("cargo:rerun-if-changed=.git/HEAD");
}

stage_spark_assets();
}

fn stage_spark_assets() {
println!("cargo:rerun-if-env-changed=BT_SPARK_DIR");
println!("cargo:rerun-if-env-changed=BT_SPARK_EMBED");

let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is set by cargo"));
let spark_out = out_dir.join("spark");
fs::create_dir_all(&spark_out).expect("create OUT_DIR/spark");

let cli_dest = spark_out.join("cli.mjs");
let harness_dest = spark_out.join("harness.tgz");
let hash_dest = spark_out.join("asset_hash");
let embedded_marker = spark_out.join("embedded");

let embed_enabled = non_empty_env("BT_SPARK_EMBED")
.map(|value| !matches!(value.as_str(), "0" | "false" | "no" | "off"))
.unwrap_or(true);

if !embed_enabled {
write_placeholder_assets(&cli_dest, &harness_dest, &hash_dest, &embedded_marker);
println!("cargo:warning=BT_SPARK_EMBED disabled; `bt spark` will be a stub");
return;
}

let manifest_dir = PathBuf::from(
env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set by cargo"),
);
let spark_dir = non_empty_env("BT_SPARK_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| manifest_dir.join("..").join("spark"));

let cli_src = spark_dir.join("packages/spark/dist/cli.mjs");
let harness_src = spark_dir.join("dist-sea-build/harness.tgz");

if !cli_src.exists() || !harness_src.exists() {
write_placeholder_assets(&cli_dest, &harness_dest, &hash_dest, &embedded_marker);
println!(
"cargo:warning=spark assets not found under {}; build spark with `pnpm build:sea` to embed (looked for {} and {})",
spark_dir.display(),
cli_src.display(),
harness_src.display(),
);
return;
}

println!("cargo:rerun-if-changed={}", cli_src.display());
println!("cargo:rerun-if-changed={}", harness_src.display());

fs::copy(&cli_src, &cli_dest).unwrap_or_else(|err| panic!("copy spark cli.mjs: {err}"));
fs::copy(&harness_src, &harness_dest)
.unwrap_or_else(|err| panic!("copy spark harness.tgz: {err}"));

let hash = hash_files(&[cli_dest.as_path(), harness_dest.as_path()]);
fs::write(&hash_dest, &hash).expect("write asset_hash");
fs::write(&embedded_marker, b"1").expect("write embedded marker");
}

fn write_placeholder_assets(cli: &Path, harness: &Path, hash: &Path, marker: &Path) {
fs::write(cli, b"").expect("write empty cli.mjs placeholder");
fs::write(harness, b"").expect("write empty harness.tgz placeholder");
fs::write(hash, b"unembedded").expect("write asset_hash placeholder");
if marker.exists() {
fs::remove_file(marker).expect("remove stale embedded marker");
}
}

fn hash_files(paths: &[&Path]) -> String {
use std::io::Read;

// Tiny FNV-1a 64-bit, ample for cache-key purposes (no crypto needs here).
let mut hash: u64 = 0xcbf29ce484222325;
for path in paths {
let mut file = fs::File::open(path).expect("open asset for hashing");
let mut buf = [0u8; 8192];
loop {
let n = file.read(&mut buf).expect("read asset for hashing");
if n == 0 {
break;
}
for byte in &buf[..n] {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x100000001b3);
}
}
}
format!("{hash:016x}")
}
7 changes: 7 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod scorers;
mod self_update;
mod setup;
mod source_language;
mod spark;
mod sql;
mod status;
mod switch;
Expand Down Expand Up @@ -79,6 +80,7 @@ Additional
docs Manage workflow docs for coding agents
self Self-management commands
setup Configure Braintrust setup flows
spark Run the embedded spark wizard
status Show current org and project context

Flags
Expand Down Expand Up @@ -161,6 +163,8 @@ enum Commands {
Switch(CLIArgs<switch::SwitchArgs>),
/// Show current org and project context
Status(CLIArgs<status::StatusArgs>),
/// Run the embedded spark wizard
Spark(CLIArgs<spark::SparkArgs>),
// /// View and modify config
// Config(CLIArgs<config::ConfigArgs>),
}
Expand Down Expand Up @@ -189,6 +193,7 @@ impl Commands {
Commands::Util(cmd) => &cmd.base,
Commands::Switch(cmd) => &cmd.base,
Commands::Status(cmd) => &cmd.base,
Commands::Spark(cmd) => &cmd.base,
}
}

Expand All @@ -215,6 +220,7 @@ impl Commands {
Commands::Util(cmd) => &mut cmd.base,
Commands::Switch(cmd) => &mut cmd.base,
Commands::Status(cmd) => &mut cmd.base,
Commands::Spark(cmd) => &mut cmd.base,
}
}

Expand Down Expand Up @@ -293,6 +299,7 @@ fn try_main() -> Result<()> {
Commands::SelfCommand(cmd) => self_update::run(cmd.base, cmd.args).await?,
Commands::Switch(cmd) => switch::run(cmd.base, cmd.args).await?,
Commands::Status(cmd) => status::run(cmd.base, cmd.args).await?,
Commands::Spark(cmd) => spark::run(cmd.base, cmd.args).await?,
}
Ok(())
});
Expand Down
Loading
Loading