feat: require owner CEF sources and versioned ABI receipts

This commit is contained in:
aphrody-dev committed 2026-10-13 04:27:05 +02:00
1 parent 163744c828
commit 480eb8ffb2
8 files changed
+97 -150

No files matched your search

+1 -1
View File
@@ -20,7 +20,7 @@ authors = [
"Bill Avery <wravery@gmail.com>",
"csmoe <csmoe@msn.com>",
]
repository = "https://github.com/tauri-apps/cef-rs"
repository = "https://github.com/aphrody-labs/cef-rs"
[workspace.dependencies]
cef = { path = "cef", default-features = false }
+1
View File
@@ -25,3 +25,4 @@ cc.workspace = true
cmake.workspace = true
download-cef.workspace = true
serde_json.workspace = true
sha2 = "0.10"
+77
View File
@@ -0,0 +1,77 @@
use anyhow::{Context, bail};
use serde_json::Value;
use sha2::{Digest, Sha256};
use std::{fs, path::{Component, Path, PathBuf}};
const CHROMIUM_REVISION: &str = "ce0615a76d6347f1914d673f1da66285b328d494";
const CHROMIUM_VERSION: &str = "157.0.8099.0";
const CEF_REVISION: &str = "f04144327bbee3818d4b594c2a812a1022861ae0";
fn read_json(path: &Path) -> anyhow::Result<Value> {
println!("cargo::rerun-if-changed={}", path.display());
Ok(serde_json::from_slice(&fs::read(path).with_context(|| format!("Missing owner CEF receipt: {}", path.display()))?)?)
}
fn hashed_file(root: &Path, relative: &str, expected: &str) -> anyhow::Result<()> {
let path = Path::new(relative);
if path.is_absolute() || path.components().any(|part| !matches!(part, Component::Normal(_))) || relative.contains('\\') {
bail!("Invalid owner CEF receipt path");
}
let file = root.join(path);
let canonical = file.canonicalize()?;
if !canonical.starts_with(root.canonicalize()?) { bail!("Owner CEF receipt escapes its source root"); }
let actual = format!("{:x}", Sha256::digest(fs::read(&canonical)?));
if actual != expected { bail!("Owner CEF artifact hash mismatch: {}", file.display()); }
println!("cargo::rerun-if-changed={}", file.display());
Ok(())
}
fn constant(source: &str, name: &str) -> anyhow::Result<u32> {
source.lines().find_map(|line| {
let value = line.strip_prefix(&format!("pub const {name}: i32 = "))?;
value.strip_suffix(';')?.parse().ok()
}).with_context(|| format!("Missing generated cef-rs constant {name}"))
}
pub fn validate_distribution(target: &str) -> anyhow::Result<PathBuf> {
let path = PathBuf::from(std::env::var_os("CEF_PATH").context("CEF_PATH must point to an owner-built Chromium 157 CEF distribution; downloading prebuilt CEF is disabled")?);
if !path.is_absolute() { bail!("CEF_PATH must be absolute"); }
let receipt = read_json(&path.join(".bun-cef-build.json"))?;
if receipt["schema"] != 1 || receipt["target"] != target
|| receipt["cefRevision"] != CEF_REVISION || receipt["chromiumRevision"] != CHROMIUM_REVISION
|| receipt["chromiumVersion"] != CHROMIUM_VERSION {
bail!("CEF distribution is not built from the selected Aphrody CEF and Chromium 157 sources");
}
let api_hash = receipt["apiHash"].as_str().filter(|hash| !hash.is_empty()).context("Owner CEF receipt lacks the native API hash probe")?;
let api_version = receipt["apiVersion"].as_u64().context("Owner CEF receipt lacks the native API version")?;
let binary = &receipt["binary"];
hashed_file(&path, binary["path"].as_str().context("Owner CEF binary path missing")?, binary["sha256"].as_str().context("Owner CEF binary digest missing")?)?;
if !target.bytes().all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_') { bail!("Invalid Cargo target"); }
let filename = format!("{}.rs", target.replace('-', "_"));
let sys = Path::new(env!("CARGO_MANIFEST_DIR"));
let bindings_path = sys.join("src/bindings").join(&filename);
let generated = fs::read_to_string(&bindings_path)?;
if constant(&generated, "CEF_API_VERSION_LAST")? as u64 != api_version
|| ["CHROME_VERSION_MAJOR", "CHROME_VERSION_MINOR", "CHROME_VERSION_BUILD", "CHROME_VERSION_PATCH"].iter()
.map(|name| constant(&generated, name).map(|version| version.to_string()))
.collect::<anyhow::Result<Vec<_>>>()?.join(".") != CHROMIUM_VERSION {
bail!("cef-rs bindings must be regenerated from the selected owner CEF headers before compiling Chromium 157");
}
let generated_receipt = read_json(&bindings_path.with_extension("bun-cef.json"))?;
for key in ["schema", "target", "cefRevision", "chromiumRevision", "chromiumVersion", "apiVersion", "apiHash"] {
if generated_receipt[key] != receipt[key] { bail!("cef-rs generated bindings do not match the owner CEF native API receipt ({key})"); }
}
if generated_receipt["apiHash"] != api_hash { bail!("cef-rs API hash mismatch"); }
let root = sys.parent().context("cef-rs workspace root missing")?;
let files = generated_receipt["files"].as_array().filter(|files| files.len() == 3).context("cef-rs generation must cover raw, safe and resource bindings")?;
let expected = [format!("sys/src/bindings/{filename}"), format!("cef/src/bindings/{filename}"), format!("cef/src/resources/{filename}")];
for filename in expected {
let entry = files.iter().find(|entry| entry["path"] == filename).context("cef-rs generated binding file missing")?;
hashed_file(root, &filename, entry["sha256"].as_str().context("cef-rs generated binding digest missing")?)?;
}
let headers = generated_receipt["headers"].as_array().filter(|headers| !headers.is_empty()).context("cef-rs generation lacks its exact header inputs")?;
for header in headers {
hashed_file(&path, header["path"].as_str().context("CEF header path missing")?, header["sha256"].as_str().context("CEF header digest missing")?)?;
}
Ok(path)
}
+7 -98
View File
@@ -1,9 +1,13 @@
#[cfg(not(feature = "dox"))]
#[path = "build-owner.rs"]
mod owner;
#[cfg(not(feature = "dox"))]
fn main() -> anyhow::Result<()> {
use download_cef::OsAndArch;
use std::{
env, fs,
path::{Path, PathBuf},
env,
path::PathBuf,
};
println!("cargo::rerun-if-changed=build.rs");
@@ -11,104 +15,9 @@ fn main() -> anyhow::Result<()> {
let target = env::var("TARGET")?;
let os_arch = OsAndArch::try_from(target.as_str())?;
println!("cargo::rerun-if-env-changed=FLATPAK");
println!("cargo::rerun-if-env-changed=NIX_CEF_BINARY");
println!("cargo::rerun-if-env-changed=CEF_PATH");
let package_version = env::var("CARGO_PKG_VERSION")?;
let cef_version = download_cef::default_version(&package_version);
let check_archive = |path: &Path| -> anyhow::Result<()> {
download_cef::check_archive_json(&package_version, &path.to_string_lossy())?;
Ok(())
};
let resolve_cef_dir = |location: &Path| -> anyhow::Result<PathBuf> {
let cef_dir = location.join(os_arch.to_string());
if !fs::exists(&cef_dir)? {
if env::var("NIX_CEF_BINARY").is_ok() {
download_cef::install_nix_cef(&cef_version, &cef_dir, false)?;
} else {
use download_cef::CefIndex;
let download_url = download_cef::default_download_url();
let index = CefIndex::download_from(&download_url)?;
let platform = index.platform(&target)?;
let version = platform.version(&cef_version)?;
let archive = version.download_archive_from(&download_url, location, false)?;
let extracted_dir =
download_cef::extract_target_archive(&target, &archive, location, false)?;
let extracted_dir_canonical = fs::canonicalize(&extracted_dir)?;
let cef_dir_canonical = fs::canonicalize(&cef_dir)?;
if extracted_dir_canonical != cef_dir_canonical {
return Err(anyhow::anyhow!(
"extracted dir {extracted_dir_canonical:?} does not match cef_dir {cef_dir_canonical:?}",
));
}
version.write_archive_json(extracted_dir)?;
}
}
Ok(cef_dir)
};
let resolve_from_versioned = |configured_path: &Path| -> anyhow::Result<PathBuf> {
let versioned_location = configured_path.join(&cef_version);
let resolved = resolve_cef_dir(&versioned_location)?;
println!(
"Using versioned CEF path from environment: {}",
resolved.display()
);
check_archive(&resolved)?;
Ok(resolved)
};
let download_to_versioned = |configured_path: &Path, reason: &str| -> anyhow::Result<PathBuf> {
let versioned_location = configured_path.join(&cef_version);
println!(
"{reason}, downloading archive to: {}",
versioned_location.display()
);
let resolved = resolve_cef_dir(&versioned_location)?;
println!("Using downloaded CEF path: {}", resolved.display());
Ok(resolved)
};
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let cef_dir = if env::var("FLATPAK").is_ok() {
let cef_path = String::from("/usr/lib");
println!("Using CEF path from FLATPAK: {cef_path}");
let cef_path = PathBuf::from(cef_path);
check_archive(&cef_path)?;
cef_path
} else if let Ok(cef_path) = env::var("CEF_PATH") {
let configured_path = PathBuf::from(cef_path);
if fs::exists(&configured_path)? {
let versioned_location = configured_path.join(&cef_version);
if fs::exists(&versioned_location)? {
resolve_from_versioned(&configured_path)?
} else {
println!(
"Using CEF path from environment: {}",
configured_path.display()
);
match check_archive(&configured_path) {
Ok(()) => configured_path,
Err(error) => download_to_versioned(
&configured_path,
&format!("CEF_PATH is invalid ({error})"),
)?,
}
}
} else {
download_to_versioned(&configured_path, "CEF_PATH does not exist")?
}
} else {
resolve_cef_dir(&out_dir)?
};
let cef_dir = owner::validate_distribution(&target)?;
// TODO: far from ideal, but there's no other way to get the target dir, see <https://github.com/rust-lang/cargo/issues/9661>
let target_dir = out_dir
+7 -5
View File
@@ -1,8 +1,10 @@
# update-bindings
Download the prebuilt [Chromium Embedded Framework](https://github.com/chromiumembedded/cef)
archive on any supported platform and run `bindgen` on the C API for the `cef-dll-sys` crate,
then regenerate the safe bindings in the `cef` crate.
Use the selected Aphrody CEF source headers to run `bindgen` on the C API for
`cef-dll-sys`, then regenerate safe and resource bindings in `cef`. Set
`CEF_PATH_<os>_<arch>` to the owner header directory and run `update-bindings
--bindgen --target <rust-target>`. No binary archive is downloaded.
You can find the latest version of the prebuilt CEF archives on the [Chromium Embedded Framework
(CEF) Automated Builds](https://cef-builds.spotifycdn.com/index.html).
Bindings use the distribution's `CEF_API_VERSION_LAST` explicitly. Source
generation and the later native library API hash qualification are separate;
the Bun wrapper records both in target-specific receipts.
+1 -9
View File
@@ -4,18 +4,10 @@ pub fn get_manifest_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
pub fn get_out_dir() -> PathBuf {
PathBuf::from(env!("OUT_DIR"))
}
pub fn get_cef_root(os: &str, arch: &str) -> PathBuf {
env::var(format!("CEF_PATH_{os}_{arch}"))
.map(PathBuf::from)
.unwrap_or_else(|_| {
let mut out_dir = get_out_dir();
out_dir.push(format!("cef_{os}_{arch}"));
out_dir
})
.unwrap_or_else(|_| panic!("owner-built headers require CEF_PATH_{os}_{arch}"))
}
pub fn get_sys_dir() -> crate::Result<PathBuf> {
+1 -25
View File
@@ -5,7 +5,7 @@ extern crate thiserror;
use clap::Parser;
use download_cef::DEFAULT_TARGET;
use std::{fs, io::Read, path::Path, sync::OnceLock};
use std::{fs, io::Read, path::Path};
#[derive(Debug, Error)]
pub enum Error {
@@ -32,33 +32,13 @@ mod parse_tree;
mod resources;
mod upgrade;
fn default_version() -> &'static str {
static DEFAULT_VERSION: OnceLock<String> = OnceLock::new();
DEFAULT_VERSION
.get_or_init(|| download_cef::default_version(env!("CARGO_PKG_VERSION")))
.as_str()
}
fn default_download_url() -> &'static str {
static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
DEFAULT_DOWNLOAD_URL
.get_or_init(download_cef::default_download_url)
.as_str()
}
#[derive(Parser, Debug)]
#[command(about, long_about = None)]
struct Args {
#[arg(short, long)]
download: bool,
#[arg(short, long)]
bindgen: bool,
#[arg(short, long, default_value = DEFAULT_TARGET)]
target: String,
#[arg(short, long, default_value = default_version())]
version: String,
#[arg(short, long, default_value = default_download_url())]
mirror_url: String,
}
fn main() -> Result<()> {
@@ -66,10 +46,6 @@ fn main() -> Result<()> {
let target = args.target.as_str();
if args.bindgen {
if args.download {
let _ = upgrade::download(args.mirror_url.as_str(), target, args.version.as_str());
}
upgrade::sys_bindgen(target)?;
}
Executable → Regular
+2 -12
View File
@@ -2,7 +2,7 @@ use crate::dirs;
use bindgen::callbacks::{DeriveInfo, ParseCallbacks, TypeKind};
use std::{
collections::BTreeSet,
path::{Path, PathBuf},
path::Path,
process::Command,
sync::{Arc, Mutex},
};
@@ -32,17 +32,6 @@ const OPAQUE_STRING_COPY_HANDLES: &[&str] = &[
const OPAQUE_X11_COPY_HANDLES: &[&str] = &["_XEvent", "_XDisplay"];
pub fn download(url: &str, target: &str, version: &str) -> PathBuf {
assert!(TARGETS.contains(&target), "unsupported target {target}");
let archive =
download_cef::download_target_archive_from(url, target, version, dirs::get_out_dir(), true)
.expect("download failed");
download_cef::extract_target_archive(target, &archive, dirs::get_out_dir(), true)
.expect("extraction failed")
}
pub fn sys_bindgen(target: &str) -> crate::Result<()> {
assert!(TARGETS.contains(&target), "unsupported target {target}");
let (os, arch) = target_to_os_arch(target);
@@ -87,6 +76,7 @@ fn bindgen(target: &str, cef_path: &Path) -> crate::Result<()> {
.clang_args([
format!("-I{}", cef_path.display()),
format!("--target={target}"),
"-DCEF_API_VERSION=CEF_API_VERSION_LAST".to_string(),
]);
if target.contains("windows") {