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:
@ -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) => {
|
||||
|
||||
51
src/App.tsx
51
src/App.tsx
@ -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 (
|
||||
|
||||
@ -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 d’Unreal…", es: "Iniciando Unreal…" },
|
||||
|
||||
|
||||
@ -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> => {
|
||||
|
||||
Reference in New Issue
Block a user