mirror of
https://github.com/aphrody-labs/cef-rs.git
synced 2026-10-13 21:06:46 +00:00
feat: require owner CEF sources and versioned ABI receipts
This commit is contained in:
1 parent
163744c828
commit
480eb8ffb2
8 files changed
+97
-150
No files matched your search
+1
-1
@@ -20,7 +20,7 @@ authors = [
|
|||||||
"Bill Avery <wravery@gmail.com>",
|
"Bill Avery <wravery@gmail.com>",
|
||||||
"csmoe <csmoe@msn.com>",
|
"csmoe <csmoe@msn.com>",
|
||||||
]
|
]
|
||||||
repository = "https://github.com/tauri-apps/cef-rs"
|
repository = "https://github.com/aphrody-labs/cef-rs"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
cef = { path = "cef", default-features = false }
|
cef = { path = "cef", default-features = false }
|
||||||
|
|||||||
@@ -25,3 +25,4 @@ cc.workspace = true
|
|||||||
cmake.workspace = true
|
cmake.workspace = true
|
||||||
download-cef.workspace = true
|
download-cef.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
sha2 = "0.10"
|
||||||
@@ -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
@@ -1,9 +1,13 @@
|
|||||||
|
#[cfg(not(feature = "dox"))]
|
||||||
|
#[path = "build-owner.rs"]
|
||||||
|
mod owner;
|
||||||
|
|
||||||
#[cfg(not(feature = "dox"))]
|
#[cfg(not(feature = "dox"))]
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
use download_cef::OsAndArch;
|
use download_cef::OsAndArch;
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
env,
|
||||||
path::{Path, PathBuf},
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("cargo::rerun-if-changed=build.rs");
|
println!("cargo::rerun-if-changed=build.rs");
|
||||||
@@ -11,104 +15,9 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let target = env::var("TARGET")?;
|
let target = env::var("TARGET")?;
|
||||||
let os_arch = OsAndArch::try_from(target.as_str())?;
|
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");
|
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 out_dir = PathBuf::from(env::var("OUT_DIR")?);
|
||||||
|
let cef_dir = owner::validate_distribution(&target)?;
|
||||||
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)?
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: far from ideal, but there's no other way to get the target dir, see <https://github.com/rust-lang/cargo/issues/9661>
|
// 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
|
let target_dir = out_dir
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# update-bindings
|
# update-bindings
|
||||||
|
|
||||||
Download the prebuilt [Chromium Embedded Framework](https://github.com/chromiumembedded/cef)
|
Use the selected Aphrody CEF source headers to run `bindgen` on the C API for
|
||||||
archive on any supported platform and run `bindgen` on the C API for the `cef-dll-sys` crate,
|
`cef-dll-sys`, then regenerate safe and resource bindings in `cef`. Set
|
||||||
then regenerate the safe bindings in the `cef` crate.
|
`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
|
Bindings use the distribution's `CEF_API_VERSION_LAST` explicitly. Source
|
||||||
(CEF) Automated Builds](https://cef-builds.spotifycdn.com/index.html).
|
generation and the later native library API hash qualification are separate;
|
||||||
|
the Bun wrapper records both in target-specific receipts.
|
||||||
@@ -4,18 +4,10 @@ pub fn get_manifest_dir() -> PathBuf {
|
|||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
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 {
|
pub fn get_cef_root(os: &str, arch: &str) -> PathBuf {
|
||||||
env::var(format!("CEF_PATH_{os}_{arch}"))
|
env::var(format!("CEF_PATH_{os}_{arch}"))
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| panic!("owner-built headers require CEF_PATH_{os}_{arch}"))
|
||||||
let mut out_dir = get_out_dir();
|
|
||||||
out_dir.push(format!("cef_{os}_{arch}"));
|
|
||||||
out_dir
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sys_dir() -> crate::Result<PathBuf> {
|
pub fn get_sys_dir() -> crate::Result<PathBuf> {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ extern crate thiserror;
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use download_cef::DEFAULT_TARGET;
|
use download_cef::DEFAULT_TARGET;
|
||||||
use std::{fs, io::Read, path::Path, sync::OnceLock};
|
use std::{fs, io::Read, path::Path};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@@ -32,33 +32,13 @@ mod parse_tree;
|
|||||||
mod resources;
|
mod resources;
|
||||||
mod upgrade;
|
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)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(about, long_about = None)]
|
#[command(about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
#[arg(short, long)]
|
|
||||||
download: bool,
|
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
bindgen: bool,
|
bindgen: bool,
|
||||||
#[arg(short, long, default_value = DEFAULT_TARGET)]
|
#[arg(short, long, default_value = DEFAULT_TARGET)]
|
||||||
target: String,
|
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<()> {
|
fn main() -> Result<()> {
|
||||||
@@ -66,10 +46,6 @@ fn main() -> Result<()> {
|
|||||||
let target = args.target.as_str();
|
let target = args.target.as_str();
|
||||||
|
|
||||||
if args.bindgen {
|
if args.bindgen {
|
||||||
if args.download {
|
|
||||||
let _ = upgrade::download(args.mirror_url.as_str(), target, args.version.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
upgrade::sys_bindgen(target)?;
|
upgrade::sys_bindgen(target)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+2
-12
@@ -2,7 +2,7 @@ use crate::dirs;
|
|||||||
use bindgen::callbacks::{DeriveInfo, ParseCallbacks, TypeKind};
|
use bindgen::callbacks::{DeriveInfo, ParseCallbacks, TypeKind};
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeSet,
|
collections::BTreeSet,
|
||||||
path::{Path, PathBuf},
|
path::Path,
|
||||||
process::Command,
|
process::Command,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
@@ -32,17 +32,6 @@ const OPAQUE_STRING_COPY_HANDLES: &[&str] = &[
|
|||||||
|
|
||||||
const OPAQUE_X11_COPY_HANDLES: &[&str] = &["_XEvent", "_XDisplay"];
|
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<()> {
|
pub fn sys_bindgen(target: &str) -> crate::Result<()> {
|
||||||
assert!(TARGETS.contains(&target), "unsupported target {target}");
|
assert!(TARGETS.contains(&target), "unsupported target {target}");
|
||||||
let (os, arch) = target_to_os_arch(target);
|
let (os, arch) = target_to_os_arch(target);
|
||||||
@@ -87,6 +76,7 @@ fn bindgen(target: &str, cef_path: &Path) -> crate::Result<()> {
|
|||||||
.clang_args([
|
.clang_args([
|
||||||
format!("-I{}", cef_path.display()),
|
format!("-I{}", cef_path.display()),
|
||||||
format!("--target={target}"),
|
format!("--target={target}"),
|
||||||
|
"-DCEF_API_VERSION=CEF_API_VERSION_LAST".to_string(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if target.contains("windows") {
|
if target.contains("windows") {
|
||||||
|
|||||||
Reference in new issue
Block a user