Files
Genarrative/apps/ai-game-creator-shell/src-tauri/build.rs
T
kdletters 27de28a487 迁移 npm workspaces 统一依赖边界
根目录统一管理九个 workspace 与唯一 lockfile

迁移 CI、Jenkins 和容器的根目录单次 npm ci

补齐 hoist 兼容、版本校验与 workspace 门禁

同步依赖边界方案、运维文档和长期记忆
2026-08-22 13:17:26 +08:00

206 lines
7.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#[path = "build_support/frontend_dist_guard.rs"]
mod frontend_dist_guard;
#[path = "build_support/runtime_prompt_bundle.rs"]
mod runtime_prompt_bundle;
#[cfg(windows)]
use sha2::{Digest, Sha256};
use std::collections::BTreeSet;
use std::env;
use std::fs;
use std::path::PathBuf;
#[cfg(windows)]
use std::io::{BufReader, Read};
const BUNDLED_CODEX_CLI_VERSION: &str = "codex-cli 0.147.0";
#[cfg(windows)]
const BUNDLED_CODEX_FILES: [&str; 6] = [
"bin/codex.exe",
"bin/codex-code-mode-host.exe",
"codex-path/rg.exe",
"codex-resources/codex-command-runner.exe",
"codex-resources/codex-windows-sandbox-setup.exe",
"codex-package.json",
];
#[cfg(windows)]
fn sha256_file(path: &std::path::Path) -> Result<String, std::io::Error> {
let file = fs::File::open(path)?;
let mut reader = BufReader::new(file);
let mut hasher = Sha256::new();
let mut buffer = [0_u8; 64 * 1024];
loop {
let read = reader.read(&mut buffer)?;
if read == 0 {
break;
}
hasher.update(&buffer[..read]);
}
Ok(format!("{:x}", hasher.finalize()))
}
fn stage_bundled_codex_cli(manifest_dir: &std::path::Path) {
#[cfg(windows)]
{
let app_root = manifest_dir
.parent()
.expect("AI 游戏创作 Tauri manifest 必须位于应用目录下");
let repo_root = app_root
.parent()
.and_then(|apps_dir| apps_dir.parent())
.expect("AI 游戏创作应用必须位于仓库 apps 目录下");
let source_candidates = [
app_root.join(
"node_modules/@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc",
),
app_root.join(
"node_modules/@openai/codex/node_modules/@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc",
),
repo_root.join(
"node_modules/@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc",
),
repo_root.join(
"node_modules/@openai/codex/node_modules/@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc",
),
];
let source = source_candidates
.iter()
.find(|path| {
BUNDLED_CODEX_FILES
.iter()
.all(|relative| path.join(relative).is_file())
})
.cloned()
.unwrap_or_else(|| {
panic!(
"内置 Codex CLI 缺失;请先在仓库根目录执行 npm ci(已检查:{}",
source_candidates
.iter()
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
.join("")
)
});
let target_dir = manifest_dir.join("resources/codex/win-x64");
let notice = target_dir.join("NOTICE.md");
if !notice.is_file() {
panic!("内置 Codex CLI 第三方声明缺失:{}", notice.display());
}
fs::create_dir_all(&target_dir).expect("创建内置 Codex CLI 资源目录失败");
let mut file_hashes = serde_json::Map::new();
for relative in BUNDLED_CODEX_FILES {
let source_path = source.join(relative);
let target_path = target_dir.join(relative);
if let Some(parent) = target_path.parent() {
fs::create_dir_all(parent).expect("创建内置 Codex CLI 资源子目录失败");
}
let source_sha256 = sha256_file(&source_path).expect("读取内置 Codex CLI 资源失败");
let target_matches_source = target_path.is_file()
&& sha256_file(&target_path)
.map(|target_sha256| target_sha256 == source_sha256)
.unwrap_or(false);
if !target_matches_source {
fs::copy(&source_path, &target_path).expect("复制内置 Codex CLI 资源失败");
}
file_hashes.insert(
relative.to_string(),
serde_json::Value::String(source_sha256),
);
}
let manifest = serde_json::json!({
"schemaVersion": "genarrative-codex-sidecar.v2",
"platform": "win32-x64",
"version": BUNDLED_CODEX_CLI_VERSION,
"files": file_hashes,
});
let manifest_path = target_dir.join("manifest.json");
let manifest_payload = format!(
"{}\n",
serde_json::to_string_pretty(&manifest).expect("序列化内置 Codex CLI 清单失败")
);
if fs::read_to_string(&manifest_path)
.map(|current| current != manifest_payload)
.unwrap_or(true)
{
fs::write(&manifest_path, manifest_payload).expect("写入内置 Codex CLI 清单失败");
}
for relative in BUNDLED_CODEX_FILES {
println!("cargo:rerun-if-changed={}", source.join(relative).display());
}
println!("cargo:rerun-if-changed={}", notice.display());
}
}
fn seed_task_group_id(
group: &shared_contracts::game_creation_app::GameCreationAppAgentGroup,
) -> &'static str {
use shared_contracts::game_creation_app::GameCreationAppAgentGroup;
match group {
GameCreationAppAgentGroup::Design => "design",
GameCreationAppAgentGroup::Art => "art",
GameCreationAppAgentGroup::Code => "code",
GameCreationAppAgentGroup::Balance => "balance",
GameCreationAppAgentGroup::Audio => "audio",
GameCreationAppAgentGroup::Publishing => "publishing",
}
}
fn validate_seed_task_catalog(compiled: &runtime_prompt_bundle::CompiledPromptBundle) {
let actual = compiled
.specialist_nodes
.iter()
.map(|node| {
(
node.task_id.clone(),
node.group_id.clone(),
node.role.clone(),
)
})
.collect::<BTreeSet<_>>();
let expected = shared_contracts::game_creation_app::new_game_creation_app_seed_tasks()
.into_iter()
.map(|task| {
(
task.id,
seed_task_group_id(&task.group).to_string(),
task.role,
)
})
.collect::<BTreeSet<_>>();
if actual != expected {
panic!(
"Prompt Bundle agentCatalog 与正式 seed DAG 的 taskId/group/role 不一致\nactual={actual:#?}\nexpected={expected:#?}"
);
}
}
fn main() {
let manifest_dir = PathBuf::from(
env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be available"),
);
let manifest_path = manifest_dir.join("prompts/runtime/manifest.json");
stage_bundled_codex_cli(&manifest_dir);
let compiled = runtime_prompt_bundle::compile_manifest(&manifest_path)
.unwrap_or_else(|error| panic!("Prompt Bundle 编译失败:{error}"));
validate_seed_task_catalog(&compiled);
for dependency in &compiled.dependencies {
println!("cargo:rerun-if-changed={}", dependency.display());
}
let output_path = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR must be available"))
.join("agent_runtime_prompt_bundle.rs");
fs::write(&output_path, compiled.rust_source)
.unwrap_or_else(|error| panic!("写入 Prompt Bundle 生成代码失败:{error}"));
if !tauri_build::is_dev() {
let frontend_dist = manifest_dir
.parent()
.expect("AI 游戏创作 Tauri manifest 必须位于应用目录下")
.join("dist");
println!("cargo:rerun-if-changed={}", frontend_dist.display());
frontend_dist_guard::validate_frontend_dist(&frontend_dist)
.unwrap_or_else(|error| panic!("生产 frontendDist 检查失败:{error}"));
}
tauri_build::build()
}