Skip to content
Draft
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
252 changes: 185 additions & 67 deletions crates/tracedecay-cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ const DASHBOARD_DIST_PATH_ENV: &str = "TRACEDECAY_DASHBOARD_DIST_PATH";
/// Directory under `OUT_DIR` for gzip-compressed dashboard asset payloads.
const DASHBOARD_GZ_DIR: &str = "dashboard-gz";

/// Compression settings version in the prepared set's directory name; bump it
/// when the gzip level or format changes.
const DASHBOARD_GZ_SETTINGS: &str = "gzip-best-v1";
const DASHBOARD_GZ_MANIFEST: &str = "manifest.tsv";
const DASHBOARD_GZ_MANIFEST_HEADER: &str = "tracedecay-dashboard-gz-v1";

/// Extensions whose on-disk form compresses enough to justify a gzip embed.
/// Already-compressed fonts/images stay identity.
fn dashboard_asset_should_gzip(relative: &str) -> bool {
Expand Down Expand Up @@ -134,89 +140,201 @@ impl EmbeddedDashboard {
out_dir: &Path,
digest_hex: String,
) -> Result<Self, Box<dyn Error>> {
let gz_root = out_dir.join(DASHBOARD_GZ_DIR);
fs::create_dir_all(&gz_root).map_err(|error| {
let gz_store = out_dir.join(DASHBOARD_GZ_DIR);
fs::create_dir_all(&gz_store).map_err(|error| {
format!(
"failed to create dashboard gzip dir {}: {error}",
gz_root.display()
gz_store.display()
)
})?;
let set_name = format!("{digest_hex}-{DASHBOARD_GZ_SETTINGS}");
let set_root = gz_store.join(&set_name);
let prepared = match read_prepared_set(&set_root, asset_paths) {
Some(prepared) => prepared,
None => {
publish_prepared_set(&gz_store, &set_name, asset_paths, source_root, &digest_hex)?
}
};
let mut assets = Vec::with_capacity(asset_paths.len());
for relative in asset_paths {
let content_type = match relative.rsplit('.').next().unwrap_or("") {
"html" => "text/html; charset=utf-8",
"js" | "mjs" => "application/javascript",
"css" => "text/css",
"json" | "map" => "application/json",
"svg" => "image/svg+xml",
"png" => "image/png",
"ico" => "image/x-icon",
"woff2" => "font/woff2",
"woff" => "font/woff",
"ttf" => "font/ttf",
"txt" => "text/plain; charset=utf-8",
_ => "application/octet-stream",
};
for (relative, gzipped) in asset_paths.iter().zip(prepared) {
let content_type = dashboard_content_type(relative);
if gzipped {
assets.push(EmbeddedDashboardAsset {
relative: relative.clone(),
include_env: "OUT_DIR",
include_path: format!("/{DASHBOARD_GZ_DIR}/{set_name}/{relative}"),
content_type,
encoding: "Gzip",
});
} else {
assets.push(EmbeddedDashboardAsset {
relative: relative.clone(),
include_env: identity_include_env,
include_path: format!("{identity_include_root}/{relative}"),
content_type,
encoding: "Identity",
});
}
}
Ok(Self { assets, digest_hex })
}
}

fn dashboard_content_type(relative: &str) -> &'static str {
match relative.rsplit('.').next().unwrap_or("") {
"html" => "text/html; charset=utf-8",
"js" | "mjs" => "application/javascript",
"css" => "text/css",
"json" | "map" => "application/json",
"svg" => "image/svg+xml",
"png" => "image/png",
"ico" => "image/x-icon",
"woff2" => "font/woff2",
"woff" => "font/woff",
"ttf" => "font/ttf",
"txt" => "text/plain; charset=utf-8",
_ => "application/octet-stream",
}
}

fn sha256_hex(bytes: &[u8]) -> String {
let mut hasher = <sha2::Sha256 as sha2::Digest>::new();
sha2::Digest::update(&mut hasher, bytes);
dashboard_bundle::hex(sha2::Digest::finalize(hasher).as_slice())
}

/// Returns, per asset, whether the completed set stores a gzip payload, or
/// `None` when the set is absent, incomplete, for different assets, or any
/// payload no longer matches its recorded hash. Validation hashes bytes only;
/// it never compresses.
fn read_prepared_set(set_root: &Path, asset_paths: &[String]) -> Option<Vec<bool>> {
let manifest = fs::read_to_string(set_root.join(DASHBOARD_GZ_MANIFEST)).ok()?;
let mut lines = manifest.lines();
if lines.next()? != DASHBOARD_GZ_MANIFEST_HEADER {
return None;
}
let mut prepared = Vec::with_capacity(asset_paths.len());
for relative in asset_paths {
let mut fields = lines.next()?.splitn(3, '\t');
let encoding = fields.next()?;
let hash = fields.next()?;
if fields.next()? != relative.as_str() {
return None;
}
match encoding {
"identity" => prepared.push(false),
"gzip" => {
let bytes = fs::read(set_root.join(relative)).ok()?;
if sha256_hex(&bytes) != hash {
return None;
}
prepared.push(true);
}
_ => return None,
}
}
if lines.next().is_some() {
return None;
}
Some(prepared)
}

/// Compresses every gzip-eligible asset once, round-trip validates it, and
/// publishes the whole set with a single directory rename so no build ever
/// observes a partial set.
fn publish_prepared_set(
gz_store: &Path,
set_name: &str,
asset_paths: &[String],
source_root: &Path,
digest_hex: &str,
) -> Result<Vec<bool>, Box<dyn Error>> {
let set_root = gz_store.join(set_name);
let staging = gz_store.join(format!(".staging-{digest_hex}-{}", std::process::id()));
if staging.exists() {
fs::remove_dir_all(&staging)
.map_err(|error| format!("failed to clear {}: {error}", staging.display()))?;
}
fs::create_dir_all(&staging)
.map_err(|error| format!("failed to create {}: {error}", staging.display()))?;
let mut manifest = format!("{DASHBOARD_GZ_MANIFEST_HEADER}\n");
let mut prepared = Vec::with_capacity(asset_paths.len());
for relative in asset_paths {
let mut gzipped = None;
if dashboard_asset_should_gzip(relative) {
let source_path = source_root.join(relative);
let raw = fs::read(&source_path).map_err(|error| {
format!(
"failed to read dashboard asset {} for embed: {error}",
source_path.display()
)
})?;
if dashboard_asset_should_gzip(relative) {
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
encoder
.write_all(&raw)
.map_err(|error| format!("gzip dashboard asset {relative}: {error}"))?;
let compressed = encoder
.finish()
.map_err(|error| format!("gzip finish dashboard asset {relative}: {error}"))?;
let mut roundtrip = Vec::new();
GzDecoder::new(compressed.as_slice())
.read_to_end(&mut roundtrip)
.map_err(|error| {
format!("gzip roundtrip dashboard asset {relative}: {error}")
})?;
if roundtrip != raw {
return Err(format!(
"gzip roundtrip for dashboard asset {relative} did not match the source bytes"
)
.into());
}
if compressed.len() < raw.len() {
let gz_relative = relative;
let dest = gz_root.join(gz_relative);
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent).map_err(|error| {
format!("failed to create {}: {error}", parent.display())
})?;
}
fs::write(&dest, &compressed).map_err(|error| {
format!(
"failed to write gzip dashboard asset {}: {error}",
dest.display()
)
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
encoder
.write_all(&raw)
.map_err(|error| format!("gzip dashboard asset {relative}: {error}"))?;
let compressed = encoder
.finish()
.map_err(|error| format!("gzip finish dashboard asset {relative}: {error}"))?;
let mut roundtrip = Vec::new();
GzDecoder::new(compressed.as_slice())
.read_to_end(&mut roundtrip)
.map_err(|error| format!("gzip roundtrip dashboard asset {relative}: {error}"))?;
if roundtrip != raw {
return Err(format!(
"gzip roundtrip for dashboard asset {relative} did not match the source bytes"
)
.into());
}
if compressed.len() < raw.len() {
gzipped = Some(compressed);
}
}
match gzipped {
Some(compressed) => {
let dest = staging.join(relative);
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent).map_err(|error| {
format!("failed to create {}: {error}", parent.display())
})?;
assets.push(EmbeddedDashboardAsset {
relative: relative.clone(),
include_env: "OUT_DIR",
include_path: format!("/{DASHBOARD_GZ_DIR}/{gz_relative}"),
content_type,
encoding: "Gzip",
});
continue;
}
fs::write(&dest, &compressed).map_err(|error| {
format!(
"failed to write gzip dashboard asset {}: {error}",
dest.display()
)
})?;
let _ = writeln!(manifest, "gzip\t{}\t{relative}", sha256_hex(&compressed));
prepared.push(true);
}
None => {
let _ = writeln!(manifest, "identity\t\t{relative}");
prepared.push(false);
}
}
}
fs::write(staging.join(DASHBOARD_GZ_MANIFEST), manifest)
.map_err(|error| format!("failed to write gzip manifest: {error}"))?;

// A set that exists but failed validation is corrupt or foreign: replace it.
if set_root.exists() {
fs::remove_dir_all(&set_root)
.map_err(|error| format!("failed to remove invalid {}: {error}", set_root.display()))?;
}
match fs::rename(&staging, &set_root) {
Ok(()) => Ok(prepared),
Err(error) => {
// A concurrent build may have published the same immutable set.
let _ = fs::remove_dir_all(&staging);
match read_prepared_set(&set_root, asset_paths) {
Some(existing) => Ok(existing),
None => Err(format!(
"failed to publish dashboard gzip set {}: {error}",
set_root.display()
)
.into()),
}
assets.push(EmbeddedDashboardAsset {
relative: relative.clone(),
include_env: identity_include_env,
include_path: format!("{identity_include_root}/{relative}"),
content_type,
encoding: "Identity",
});
}
Ok(Self { assets, digest_hex })
}
}

Expand Down