170 lines
6.8 KiB
Rust
170 lines
6.8 KiB
Rust
//! What the runtime bundle (`bundle`, which `export` turns on) is made from: the flowRS commit
|
|
//! it names (`runtime::REV`), and the source of the crates it carries.
|
|
//!
|
|
//! **The commit.** An exported project carries flowRS's runtime crates in `vendor/`, copied
|
|
//! from the source this exporter was built from, and its `vendor/README.md` names that commit.
|
|
//! The revision is `FLOWRS_EXPORT_REV` when the build sets it, else this checkout's HEAD. A
|
|
//! HEAD that changes nothing over its parent (the commit a build host makes of a clean
|
|
//! worktree it snapshots) names the parent, the commit that is in flowRS's history.
|
|
//!
|
|
//! **The source.** Every text file of the crates the bundle may carry (their sources, assets,
|
|
//! manifests, licences, notices and provenance; not their tests, examples, benches, fuzzers,
|
|
//! fixtures or eval corpora), the workspace's manifest and its `Cargo.lock`, compiled in with
|
|
//! `include_str!` (`$OUT_DIR/runtime_sources.rs`). Which of them go into `vendor/`, and how the
|
|
//! manifests are rewritten to stand alone, is `runtime.rs`'s to decide, where it is tested;
|
|
//! this script only lists files. A generated project, which links the shell alone, runs
|
|
//! nothing here.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
|
|
/// The crates the bundle may carry: `runtime::RULES` names the same, and a test holds the two
|
|
/// together.
|
|
const CRATES: &[&str] = &[
|
|
"flowrs-openui",
|
|
"flowrs-app",
|
|
"flowrs-appshell",
|
|
"flowrs-math",
|
|
"flowrs-dsp",
|
|
"flowrs-sequence",
|
|
"flowrs-record",
|
|
"flowrs-results",
|
|
"flowrs-station",
|
|
"flowrs-transport",
|
|
"flowrs-hal",
|
|
"flowrs-device",
|
|
];
|
|
|
|
/// Directories at a crate's top level that never go into a vendored crate.
|
|
const SKIP_DIRS: &[&str] = &[
|
|
"target", "tests", "benches", "examples", "fuzz", "eval", "fixtures",
|
|
];
|
|
|
|
fn git(dir: &Path, args: &[&str]) -> Option<String> {
|
|
let out = Command::new("git")
|
|
.arg("-C")
|
|
.arg(dir)
|
|
.args(args)
|
|
.output()
|
|
.ok()?;
|
|
if !out.status.success() {
|
|
return None;
|
|
}
|
|
let s = String::from_utf8(out.stdout).ok()?.trim().to_string();
|
|
(!s.is_empty()).then_some(s)
|
|
}
|
|
|
|
fn rev(dir: &Path) -> String {
|
|
let given = std::env::var("FLOWRS_EXPORT_REV")
|
|
.ok()
|
|
.map(|s| s.trim().to_string())
|
|
.filter(|s| !s.is_empty());
|
|
let rev = given.or_else(|| {
|
|
// Rebuild when HEAD moves: the worktree's HEAD, the branch it names, packed refs.
|
|
if let Some(git_dir) = git(dir, &["rev-parse", "--absolute-git-dir"]) {
|
|
println!("cargo:rerun-if-changed={git_dir}/HEAD");
|
|
if let Some(common) = git(dir, &["rev-parse", "--git-common-dir"]) {
|
|
let common = if Path::new(&common).is_absolute() {
|
|
common
|
|
} else {
|
|
dir.join(&common).to_string_lossy().into_owned()
|
|
};
|
|
println!("cargo:rerun-if-changed={common}/packed-refs");
|
|
if let Some(head_ref) = git(dir, &["symbolic-ref", "-q", "HEAD"]) {
|
|
println!("cargo:rerun-if-changed={common}/{head_ref}");
|
|
}
|
|
}
|
|
}
|
|
let head = git(dir, &["rev-parse", "HEAD"])?;
|
|
let tree = git(dir, &["rev-parse", "HEAD^{tree}"]);
|
|
let parent_tree = git(dir, &["rev-parse", "HEAD~1^{tree}"]);
|
|
match (tree, parent_tree) {
|
|
(Some(t), Some(p)) if t == p => git(dir, &["rev-parse", "HEAD~1"]).or(Some(head)),
|
|
_ => Some(head),
|
|
}
|
|
});
|
|
// Without git and without the variable: said so, in the README of every project.
|
|
rev.unwrap_or_else(|| "unknown".into())
|
|
}
|
|
|
|
/// Every text file under `dir`, as paths relative to `root`, sorted. Hidden entries and the
|
|
/// skipped top-level directories are left out; a file that is not UTF-8 is too (no crate the
|
|
/// bundle carries compiles one in).
|
|
fn files(root: &Path, dir: &Path, out: &mut Vec<(String, PathBuf)>) {
|
|
let Ok(entries) = std::fs::read_dir(dir) else {
|
|
return;
|
|
};
|
|
let mut entries: Vec<_> = entries.filter_map(Result::ok).collect();
|
|
entries.sort_by_key(|e| e.file_name());
|
|
for e in entries {
|
|
let name = e.file_name().to_string_lossy().into_owned();
|
|
if name.starts_with('.') {
|
|
continue;
|
|
}
|
|
let path = e.path();
|
|
let rel = path
|
|
.strip_prefix(root)
|
|
.map(|p| p.to_string_lossy().replace('\\', "/"))
|
|
.unwrap_or_default();
|
|
if path.is_dir() {
|
|
if dir == root && SKIP_DIRS.contains(&name.as_str()) {
|
|
continue;
|
|
}
|
|
files(root, &path, out);
|
|
} else if std::fs::read_to_string(&path).is_ok() {
|
|
out.push((rel, path));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
println!("cargo:rerun-if-env-changed=FLOWRS_EXPORT_REV");
|
|
println!("cargo:rerun-if-changed=build.rs");
|
|
if std::env::var_os("CARGO_FEATURE_BUNDLE").is_none() {
|
|
return;
|
|
}
|
|
let dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default());
|
|
println!("cargo:rustc-env=FLOWRS_EXPORT_REV={}", rev(&dir));
|
|
|
|
let crates_dir = dir.join("..");
|
|
let root = dir.join("../..");
|
|
let mut src = String::from(
|
|
"/// Each file the bundle may carry: (crate, path within it, contents).\n\
|
|
pub(crate) static SOURCES: &[(&str, &str, &str)] = &[\n",
|
|
);
|
|
for name in CRATES {
|
|
let crate_dir = crates_dir.join(name);
|
|
println!("cargo:rerun-if-changed={}", crate_dir.display());
|
|
let mut found = Vec::new();
|
|
files(&crate_dir, &crate_dir, &mut found);
|
|
for (rel, path) in found {
|
|
let abs = std::fs::canonicalize(&path).unwrap_or(path);
|
|
src.push_str(&format!(
|
|
" ({name:?}, {rel:?}, include_str!({:?})),\n",
|
|
abs.to_string_lossy()
|
|
));
|
|
}
|
|
}
|
|
src.push_str("];\n");
|
|
// The public engine carries a lock pruned to its own crates; what an export pins is what
|
|
// flowRS's whole workspace pins, so there the full lock travels beside the lock data.
|
|
let full_lock = dir.join("data/export-cargo.lock");
|
|
let lockfile = if full_lock.exists() {
|
|
full_lock
|
|
} else {
|
|
root.join("Cargo.lock")
|
|
};
|
|
for (constant, file, path) in [
|
|
("WORKSPACE_MANIFEST", "Cargo.toml", root.join("Cargo.toml")),
|
|
("LOCKFILE", "Cargo.lock", lockfile),
|
|
] {
|
|
println!("cargo:rerun-if-changed={}", path.display());
|
|
let abs = std::fs::canonicalize(&path).unwrap_or(path);
|
|
src.push_str(&format!(
|
|
"/// The workspace's `{file}`.\npub(crate) static {constant}: &str = include_str!({:?});\n",
|
|
abs.to_string_lossy()
|
|
));
|
|
}
|
|
let out = PathBuf::from(std::env::var("OUT_DIR").unwrap_or_default());
|
|
std::fs::write(out.join("runtime_sources.rs"), src).expect("write runtime_sources.rs");
|
|
}
|