diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index fea5ecc..38a4c47 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -774,7 +774,7 @@ async fn find_sln(state: State<'_, AppState>, scope: String) -> Result Result { +async fn build_sln(path: String, config: String) -> Result { if !std::path::Path::new(&path).exists() { return Err(format!("Solution not found: {path}")); } @@ -805,7 +805,11 @@ async fn build_sln(path: String) -> Result { }; let msbuild = if msbuild.is_empty() { "MSBuild.exe".to_string() } else { msbuild }; emit(format!("MSBuild: {msbuild}"), false, false); - emit(format!("Building {path} …"), false, false); + emit( + format!("Building {path}{} …", if config.is_empty() { String::new() } else { format!(" [{config} | Win64]") }), + false, + false, + ); let mut cmd = Command::new(&msbuild); #[cfg(windows)] @@ -813,9 +817,19 @@ async fn build_sln(path: String) -> Result { use std::os::windows::process::CommandExt; cmd.creation_flags(0x0800_0000); } - cmd.args([&path, "/m", "/nologo", "/v:minimal", "/clp:NoSummary"]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + // build args; a specific UE configuration ("Development Editor", "Shipping", …) → Win64 + let mut args: Vec = vec![ + path.clone(), + "/m".into(), + "/nologo".into(), + "/v:minimal".into(), + "/clp:NoSummary".into(), + ]; + if !config.is_empty() { + args.push(format!("/p:Configuration={config}")); + args.push("/p:Platform=Win64".into()); + } + cmd.args(&args).stdout(Stdio::piped()).stderr(Stdio::piped()); let mut child = match cmd.spawn() { Ok(c) => c, Err(e) => { diff --git a/src/App.tsx b/src/App.tsx index 4e33814..c749d9b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -302,6 +302,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set const [buildOk, setBuildOk] = useState(null); const [buildOpen, setBuildOpen] = useState(false); const [buildMin, setBuildMin] = useState(false); // collapsed to a small floating chip + const [buildPick, setBuildPick] = useState(false); // configuration picker before building const [ctx, setCtx] = useState(null); // right-click menu const [logs, setLogs] = useState([]); // live p4 command log (P4V-style) const [logOpen, setLogOpen] = useState(false); @@ -533,11 +534,17 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set catch (e) { flash(String(e), true); } } - // build the solution found in the working folder (MSBuild) with live output - async function buildSln() { + // open the configuration picker (Development / Shipping / …) before building + function startBuild() { if (!slnPath) { flash(t("No .sln found in the working folder. Choose the project folder first."), true); return; } + setBuildPick(true); + } + // build the solution (MSBuild) with the chosen UE configuration, live output + async function buildSln(config: string) { + setBuildPick(false); + if (!slnPath) return; setBuildLog([]); setBuildOk(null); setBuilding(true); setBuildOpen(true); setBuildMin(false); - try { await p4.buildSln(slnPath); } + try { await p4.buildSln(slnPath, config); } catch { /* the overlay already shows the failure line */ } } @@ -716,7 +723,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set Connection: [{ label: t("Refresh"), kb: "F5", act: () => refresh() }, { label: t("Choose working folder…"), act: () => browseTo("") }], Actions: [{ label: t("Rescan changes"), kb: "Ctrl+R", act: rescan }, { label: t("Get Latest"), kb: "Ctrl+G", act: getLatest }, { label: t("Commit (local)"), act: doCommit }, { label: t("Submit to server"), kb: "Ctrl+S", act: pushAll }, { label: t("Revert All…"), act: revertAll }, { label: t("Refresh"), kb: "F5", act: () => refresh() }], Window: [{ label: (logOpen ? "✓ " : "") + t("Log"), kb: "Ctrl+L", act: () => setLogOpen((o) => !o) }], - Tools: [{ label: slnPath ? t("Build Solution (.sln)") : t("Build Solution — no .sln"), kb: "Ctrl+B", act: buildSln }, { label: t("Settings…"), act: () => setModal({ kind: "settings" }) }], + Tools: [{ label: slnPath ? t("Build Solution (.sln)") : t("Build Solution — no .sln"), kb: "Ctrl+B", act: startBuild }, { label: t("Settings…"), act: () => setModal({ kind: "settings" }) }], Help: [{ label: t("About"), act: () => setModal({ kind: "about" }) }], }; @@ -759,7 +766,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
browseTo("")} title={t("Choose the project working folder · right-click for more")} onContextMenu={(e) => openCtx(e, [ ...(uproject ? [{ label: t("Launch Unreal Engine"), icon: I.ue, act: launchUE }] : []), - ...(slnPath ? [{ label: t("Build Solution (.sln)"), icon: I.hammer, act: buildSln }] : []), + ...(slnPath ? [{ label: t("Build Solution (.sln)"), icon: I.hammer, act: startBuild }] : []), { label: t("Open in Explorer"), act: () => reveal(activePath || String(info?.clientRoot || "")) }, { label: t("Choose another folder…"), act: () => browseTo("") }, { label: t("Copy path"), act: () => copyText(activePath || "//…", t("Path")) }, @@ -920,6 +927,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set {ask && { ask.resolve(v); setAsk(null); }} />} {prompt && setPrompt(null)} />} {showXfer && transfer && } + {buildPick && setBuildPick(false)} />} {buildOpen && !buildMin && setBuildMin(true)} onClose={() => setBuildOpen(false)} />} {buildOpen && buildMin && setBuildMin(false)} onClose={() => setBuildOpen(false)} />} {ctx && setCtx(null)} />} @@ -1151,6 +1159,39 @@ function BuildOverlay({ lines, building, ok, sln, onMinimize, onClose }: { lines
); } +/* build configuration picker (Unreal: Development / Shipping / …) */ +function BuildPicker({ onPick, onClose }: { onPick: (config: string) => void; onClose: () => void }) { + useEffect(() => { + const onKey = (e: KeyboardEvent) => e.key === "Escape" && onClose(); + document.addEventListener("keydown", onKey); + return () => document.removeEventListener("keydown", onKey); + }, [onClose]); + const opts: { config: string; name: string; desc: string }[] = [ + { config: "Development Editor", name: "Development Editor", desc: t("Editor build for working in-engine (default)") }, + { config: "Development", name: "Development", desc: t("Standalone game, dev config") }, + { config: "DebugGame", name: "DebugGame", desc: t("Game with debuggable game code") }, + { config: "Shipping", name: "Shipping", desc: t("Optimized release build") }, + ]; + return ( +
+
e.stopPropagation()}> +
{I.hammer}

{t("Build configuration")}

+ +
+
+ {opts.map((o) => ( +
onPick(o.config)}> + {I.hammer} + {o.name} · Win64{o.desc} +
+ ))} +
+
{t("Builds via MSBuild → UnrealBuildTool")}
+
+
+ ); +} + /* minimized build indicator — small floating chip, click to expand */ function BuildChip({ building, ok, onExpand, onClose }: { building: boolean; ok: boolean | null; onExpand: () => void; onClose: () => void }) { return ( diff --git a/src/i18n.ts b/src/i18n.ts index c7e3d45..485ccb2 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -78,6 +78,12 @@ const D: Record = { "Build succeeded": { ru: "Сборка успешна", de: "Build erfolgreich", fr: "Compilation réussie", es: "Compilación correcta" }, "Build failed": { ru: "Сборка провалена", de: "Build fehlgeschlagen", fr: "Échec de la compilation", es: "Compilación fallida" }, "Starting MSBuild…": { ru: "Запуск MSBuild…", de: "MSBuild wird gestartet…", fr: "Démarrage de MSBuild…", es: "Iniciando MSBuild…" }, + "Build configuration": { ru: "Конфигурация сборки", de: "Build-Konfiguration", fr: "Configuration de build", es: "Configuración de compilación" }, + "Editor build for working in-engine (default)": { ru: "Сборка редактора для работы в движке (по умолчанию)", de: "Editor-Build zum Arbeiten in der Engine (Standard)", fr: "Build éditeur pour travailler dans le moteur (défaut)", es: "Build del editor para trabajar en el motor (predeterminado)" }, + "Standalone game, dev config": { ru: "Отдельная игра, dev-конфиг", de: "Eigenständiges Spiel, Dev-Konfig", fr: "Jeu autonome, config dev", es: "Juego independiente, config dev" }, + "Game with debuggable game code": { ru: "Игра с отлаживаемым кодом", de: "Spiel mit debugfähigem Code", fr: "Jeu avec code débogable", es: "Juego con código depurable" }, + "Optimized release build": { ru: "Оптимизированный релизный билд", de: "Optimierter Release-Build", fr: "Build de release optimisé", es: "Build de lanzamiento optimizado" }, + "Builds via MSBuild → UnrealBuildTool": { ru: "Сборка через MSBuild → UnrealBuildTool", de: "Build über MSBuild → UnrealBuildTool", fr: "Compile via MSBuild → UnrealBuildTool", es: "Compila con MSBuild → UnrealBuildTool" }, "Open this project in Unreal Engine": { ru: "Открыть проект в Unreal Engine", de: "Projekt in Unreal Engine öffnen", fr: "Ouvrir le projet dans Unreal Engine", es: "Abrir el proyecto en Unreal Engine" }, "Launching Unreal…": { ru: "Запуск Unreal…", de: "Unreal wird gestartet…", fr: "Lancement d’Unreal…", es: "Iniciando Unreal…" }, diff --git a/src/p4.ts b/src/p4.ts index 29520c9..7785824 100644 --- a/src/p4.ts +++ b/src/p4.ts @@ -76,7 +76,7 @@ export const p4 = { openInExplorer: (target: string) => invoke("open_in_explorer", { target }), findUproject: (scope: string) => invoke("find_uproject", { scope }), findSln: (scope: string) => invoke("find_sln", { scope }), - buildSln: (path: string) => invoke("build_sln", { path }), + buildSln: (path: string, config: string) => invoke("build_sln", { path, config }), launchFile: (path: string) => invoke("launch_file", { path }), openInVscode: (depot: string) => invoke("open_in_vscode", { depot }), readDepot: async (depot: string): Promise => {