rust/cxx-qt pocs
This commit is contained in:
297
poc001/build.rs
Normal file
297
poc001/build.rs
Normal file
@@ -0,0 +1,297 @@
|
||||
// file: poc-qt/poc001/build.rs
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use cxx_qt_build::{CxxQtBuilder, QmlModule};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
const DEFAULT_CONFIG_STR: &str = include_str!("build_config.default.json");
|
||||
const CONFIG_SCHEMA_STR: &str = include_str!("../build_config.schema.json");
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct BuildConfig {
|
||||
active_profile: String,
|
||||
profiles: BTreeMap<String, BuildProfile>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct BuildProfile {
|
||||
qt: QtConfig,
|
||||
link: LinkConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct QtConfig {
|
||||
qmake: String,
|
||||
lib_dir: String,
|
||||
qml_dir: String,
|
||||
plugins_dir: String,
|
||||
version_major: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LinkConfig {
|
||||
rpaths: Vec<String>,
|
||||
}
|
||||
|
||||
fn ensure_user_config_exists(config_path: &Path) {
|
||||
if config_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
match fs::write(config_path, DEFAULT_CONFIG_STR) {
|
||||
Ok(()) => {
|
||||
println!(
|
||||
"cargo:warning=build_config.json was missing and has been created from build_config.default.json"
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"failed to create {} from embedded defaults: {}",
|
||||
config_path.display(),
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_embedded_schema() {
|
||||
let schema_json = parse_json_str(CONFIG_SCHEMA_STR, "embedded build_config.schema.json");
|
||||
|
||||
match jsonschema::meta::validate(&schema_json) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
panic!("embedded build_config.schema.json is not a valid JSON Schema: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_default_config() {
|
||||
validate_config_str_against_schema(DEFAULT_CONFIG_STR, "embedded build_config.default.json");
|
||||
}
|
||||
|
||||
fn validate_user_config(config_path: &Path) {
|
||||
let raw = match fs::read_to_string(config_path) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
panic!("failed to read {}: {}", config_path.display(), err);
|
||||
}
|
||||
};
|
||||
|
||||
validate_config_str_against_schema(&raw, &format!("{}", config_path.display()));
|
||||
}
|
||||
|
||||
fn validate_config_str_against_schema(config_raw: &str, config_name: &str) {
|
||||
let schema_json = parse_json_str(CONFIG_SCHEMA_STR, "embedded build_config.schema.json");
|
||||
let config_json = parse_json_str(config_raw, config_name);
|
||||
|
||||
let validator = match jsonschema::validator_for(&schema_json) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"failed to build JSON Schema validator for {}: {}",
|
||||
config_name, err
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if validator.is_valid(&config_json) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut errors = Vec::new();
|
||||
for err in validator.iter_errors(&config_json) {
|
||||
errors.push(format!(
|
||||
"- {} at instance path {}",
|
||||
err,
|
||||
err.instance_path()
|
||||
));
|
||||
}
|
||||
|
||||
let joined = errors.join("\n");
|
||||
panic!("{} failed schema validation:\n{}", config_name, joined);
|
||||
}
|
||||
|
||||
fn load_build_config(config_path: &Path) -> BuildConfig {
|
||||
let raw = match fs::read_to_string(config_path) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
panic!("failed to read {}: {}", config_path.display(), err);
|
||||
}
|
||||
};
|
||||
|
||||
match serde_json::from_str::<BuildConfig>(&raw) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"failed to deserialize {} into BuildConfig: {}",
|
||||
config_path.display(),
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_profile_name(build_config: &BuildConfig) -> String {
|
||||
let env_profile = std::env::var("BUILD_CONFIG_PROFILE");
|
||||
match env_profile {
|
||||
Ok(v) => {
|
||||
if v.trim().is_empty() {
|
||||
build_config.active_profile.clone()
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
Err(_) => build_config.active_profile.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_profile<'a>(build_config: &'a BuildConfig, profile_name: &str) -> &'a BuildProfile {
|
||||
match build_config.profiles.get(profile_name) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
let mut available = Vec::new();
|
||||
for key in build_config.profiles.keys() {
|
||||
available.push(key.clone());
|
||||
}
|
||||
panic!(
|
||||
"active profile '{}' not found in build_config.json. available profiles: {}",
|
||||
profile_name,
|
||||
available.join(", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_profile_paths(profile: &BuildProfile, profile_name: &str) {
|
||||
ensure_existing_file(&profile.qt.qmake, profile_name, "qt.qmake");
|
||||
ensure_existing_dir(&profile.qt.lib_dir, profile_name, "qt.lib_dir");
|
||||
ensure_existing_dir(&profile.qt.qml_dir, profile_name, "qt.qml_dir");
|
||||
ensure_existing_dir(&profile.qt.plugins_dir, profile_name, "qt.plugins_dir");
|
||||
|
||||
let mut index = 0usize;
|
||||
while index < profile.link.rpaths.len() {
|
||||
ensure_existing_dir(
|
||||
&profile.link.rpaths[index],
|
||||
profile_name,
|
||||
&format!("link.rpaths[{index}]"),
|
||||
);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_existing_file(path_str: &str, profile_name: &str, field_name: &str) {
|
||||
let path = Path::new(path_str);
|
||||
if !path.exists() {
|
||||
panic!(
|
||||
"profile '{}' has invalid {}: path does not exist: {}",
|
||||
profile_name,
|
||||
field_name,
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
if !path.is_file() {
|
||||
panic!(
|
||||
"profile '{}' has invalid {}: path is not a file: {}",
|
||||
profile_name,
|
||||
field_name,
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_existing_dir(path_str: &str, profile_name: &str, field_name: &str) {
|
||||
let path = Path::new(path_str);
|
||||
if !path.exists() {
|
||||
panic!(
|
||||
"profile '{}' has invalid {}: path does not exist: {}",
|
||||
profile_name,
|
||||
field_name,
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
if !path.is_dir() {
|
||||
panic!(
|
||||
"profile '{}' has invalid {}: path is not a directory: {}",
|
||||
profile_name,
|
||||
field_name,
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_build_environment(profile: &BuildProfile) {
|
||||
unsafe {
|
||||
std::env::set_var("QMAKE", &profile.qt.qmake);
|
||||
std::env::set_var("QT_VERSION_MAJOR", profile.qt.version_major.to_string());
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-env-changed=BUILD_CONFIG_PROFILE");
|
||||
println!("cargo:rerun-if-env-changed=QMAKE");
|
||||
println!("cargo:rerun-if-env-changed=QT_VERSION_MAJOR");
|
||||
}
|
||||
|
||||
fn apply_linker_settings(profile: &BuildProfile) {
|
||||
let mut index = 0usize;
|
||||
while index < profile.link.rpaths.len() {
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,-rpath,{}",
|
||||
profile.link.rpaths[index]
|
||||
);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_runtime_hints(profile: &BuildProfile, profile_name: &str) {
|
||||
println!(
|
||||
"cargo:warning=using build profile '{}' with qmake '{}'",
|
||||
profile_name, profile.qt.qmake
|
||||
);
|
||||
println!(
|
||||
"cargo:warning=qt lib_dir='{}', qml_dir='{}', plugins_dir='{}'",
|
||||
profile.qt.lib_dir, profile.qt.qml_dir, profile.qt.plugins_dir
|
||||
);
|
||||
}
|
||||
|
||||
fn parse_json_str(raw: &str, label: &str) -> Value {
|
||||
match serde_json::from_str::<Value>(raw) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
panic!("failed to parse {} as JSON: {}", label, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=build_config.default.json");
|
||||
println!("cargo:rerun-if-changed=build_config.schema.json");
|
||||
println!("cargo:rerun-if-changed=src/bridge.rs");
|
||||
println!("cargo:rerun-if-changed=qml/main.qml");
|
||||
|
||||
let config_path = PathBuf::from("build_config.json");
|
||||
|
||||
ensure_user_config_exists(&config_path);
|
||||
validate_embedded_schema();
|
||||
validate_default_config();
|
||||
validate_user_config(&config_path);
|
||||
|
||||
let build_config = load_build_config(&config_path);
|
||||
let profile_name = resolve_profile_name(&build_config);
|
||||
let profile = resolve_profile(&build_config, &profile_name);
|
||||
|
||||
validate_profile_paths(profile, &profile_name);
|
||||
apply_build_environment(profile);
|
||||
apply_linker_settings(profile);
|
||||
|
||||
CxxQtBuilder::new_qml_module(QmlModule::new("com.sasedev.hello").qml_files(["qml/main.qml"]))
|
||||
.qt_module("Qml")
|
||||
.qt_module("Network")
|
||||
.files(["src/bridge.rs"])
|
||||
.build();
|
||||
|
||||
emit_runtime_hints(profile, &profile_name);
|
||||
}
|
||||
Reference in New Issue
Block a user