Unreal build configurations (Development / Shipping / …)

Build Solution now opens a configuration picker (Development Editor,
Development, DebugGame, Shipping) and passes /p:Configuration=<cfg>
/p:Platform=Win64 to MSBuild, which drives UnrealBuildTool for the chosen
config. Streams into the same minimizable build overlay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bonchellon
2026-07-07 16:40:01 +03:00
parent de6d55ad84
commit 168b6afab3
4 changed files with 72 additions and 11 deletions

View File

@ -774,7 +774,7 @@ async fn find_sln(state: State<'_, AppState>, scope: String) -> Result<String, S
/// its output line-by-line as `build-log` events. Uses the solution's default
/// configuration — for an Unreal project that compiles the game module.
#[tauri::command]
async fn build_sln(path: String) -> Result<String, String> {
async fn build_sln(path: String, config: String) -> Result<String, String> {
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<String, String> {
};
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<String, String> {
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<String> = 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) => {

View File

@ -302,6 +302,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
const [buildOk, setBuildOk] = useState<boolean | null>(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 | { x: number; y: number; items: CtxItem[] }>(null); // right-click menu
const [logs, setLogs] = useState<LogEntry[]>([]); // 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
<div className="tzone" onClick={() => 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 && <ConfirmModal {...ask} onClose={(v) => { ask.resolve(v); setAsk(null); }} />}
{prompt && <TextPrompt title={prompt.title} label={prompt.label} value={prompt.value} onSave={prompt.onSave} onClose={() => setPrompt(null)} />}
{showXfer && transfer && <TransferDialog transfer={transfer} />}
{buildPick && <BuildPicker onPick={buildSln} onClose={() => setBuildPick(false)} />}
{buildOpen && !buildMin && <BuildOverlay lines={buildLog} building={building} ok={buildOk} sln={slnPath} onMinimize={() => setBuildMin(true)} onClose={() => setBuildOpen(false)} />}
{buildOpen && buildMin && <BuildChip building={building} ok={buildOk} onExpand={() => setBuildMin(false)} onClose={() => setBuildOpen(false)} />}
{ctx && <ContextMenu x={ctx.x} y={ctx.y} items={ctx.items} onClose={() => setCtx(null)} />}
@ -1151,6 +1159,39 @@ function BuildOverlay({ lines, building, ok, sln, onMinimize, onClose }: { lines
</div>
);
}
/* 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 (
<div className="modal-back" onClick={onClose}>
<div className="picker" onClick={(e) => e.stopPropagation()}>
<div className="picker-head">{I.hammer}<h3>{t("Build configuration")}</h3>
<button className="x" onClick={onClose}><svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" /></svg></button>
</div>
<div className="picker-list">
{opts.map((o) => (
<div key={o.config} className="picker-item" onClick={() => onPick(o.config)}>
<span className="pi-ic">{I.hammer}</span>
<span className="pi-body"><span className="pi-name">{o.name} · Win64</span><span className="pi-sub">{o.desc}</span></span>
</div>
))}
</div>
<div className="picker-foot">{t("Builds via MSBuild → UnrealBuildTool")}</div>
</div>
</div>
);
}
/* 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 (

View File

@ -78,6 +78,12 @@ const D: Record<string, Tr> = {
"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 dUnreal…", es: "Iniciando Unreal…" },

View File

@ -76,7 +76,7 @@ export const p4 = {
openInExplorer: (target: string) => invoke<void>("open_in_explorer", { target }),
findUproject: (scope: string) => invoke<string>("find_uproject", { scope }),
findSln: (scope: string) => invoke<string>("find_sln", { scope }),
buildSln: (path: string) => invoke<string>("build_sln", { path }),
buildSln: (path: string, config: string) => invoke<string>("build_sln", { path, config }),
launchFile: (path: string) => invoke<void>("launch_file", { path }),
openInVscode: (depot: string) => invoke<void>("open_in_vscode", { depot }),
readDepot: async (depot: string): Promise<ArrayBuffer> => {