Skip to main content

proof_engine/editor/
build_system.rs

1
2//! Build system editor — platform targets, build configurations, asset pipeline,
3//! packaging, code signing, deployment, CI integration, and build log viewer.
4
5#![allow(non_camel_case_types)]
6
7use glam::Vec4;
8use std::collections::HashMap;
9
10// ---------------------------------------------------------------------------
11// Platform targets
12// ---------------------------------------------------------------------------
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum BuildPlatform {
16    Windows,
17    WindowsArm64,
18    MacOS,
19    MacOSArm64,
20    Linux,
21    LinuxArm64,
22    Android,
23    AndroidX86,
24    iOS,
25    WebGL,
26    WebGPU,
27    XboxSeriesX,
28    XboxOne,
29    PlayStation5,
30    PlayStation4,
31    SwitchNX,
32    TVos,
33    VisionOS,
34    WasmModule,
35    EmbeddedLinux,
36}
37
38impl BuildPlatform {
39    pub fn label(&self) -> &'static str {
40        match self {
41            Self::Windows => "Windows (x64)",
42            Self::WindowsArm64 => "Windows (ARM64)",
43            Self::MacOS => "macOS (x64)",
44            Self::MacOSArm64 => "macOS (Apple Silicon)",
45            Self::Linux => "Linux (x64)",
46            Self::LinuxArm64 => "Linux (ARM64)",
47            Self::Android => "Android (ARM64)",
48            Self::AndroidX86 => "Android (x86_64)",
49            Self::iOS => "iOS",
50            Self::WebGL => "WebGL",
51            Self::WebGPU => "WebGPU",
52            Self::XboxSeriesX => "Xbox Series X/S",
53            Self::XboxOne => "Xbox One",
54            Self::PlayStation5 => "PlayStation 5",
55            Self::PlayStation4 => "PlayStation 4",
56            Self::SwitchNX => "Nintendo Switch",
57            Self::TVos => "tvOS",
58            Self::VisionOS => "visionOS",
59            Self::WasmModule => "WASM Module",
60            Self::EmbeddedLinux => "Embedded Linux",
61        }
62    }
63
64    pub fn is_mobile(&self) -> bool {
65        matches!(self, Self::Android | Self::AndroidX86 | Self::iOS | Self::TVos)
66    }
67    pub fn is_console(&self) -> bool {
68        matches!(self, Self::XboxSeriesX | Self::XboxOne | Self::PlayStation5 | Self::PlayStation4 | Self::SwitchNX)
69    }
70    pub fn is_web(&self) -> bool {
71        matches!(self, Self::WebGL | Self::WebGPU | Self::WasmModule)
72    }
73    pub fn is_desktop(&self) -> bool {
74        matches!(self, Self::Windows | Self::WindowsArm64 | Self::MacOS | Self::MacOSArm64 | Self::Linux | Self::LinuxArm64)
75    }
76    pub fn supports_il2cpp(&self) -> bool {
77        !self.is_web()
78    }
79    pub fn requires_signing(&self) -> bool {
80        matches!(self, Self::iOS | Self::MacOS | Self::MacOSArm64 | Self::TVos | Self::VisionOS | Self::Android)
81    }
82    pub fn file_extension(&self) -> &'static str {
83        match self {
84            Self::Windows | Self::WindowsArm64 => "exe",
85            Self::MacOS | Self::MacOSArm64 => "app",
86            Self::Linux | Self::LinuxArm64 | Self::EmbeddedLinux => "",
87            Self::Android | Self::AndroidX86 => "apk",
88            Self::iOS | Self::TVos | Self::VisionOS => "ipa",
89            Self::WebGL | Self::WebGPU | Self::WasmModule => "html",
90            _ => "pkg",
91        }
92    }
93    pub fn min_sdk_version(&self) -> Option<u32> {
94        match self {
95            Self::Android | Self::AndroidX86 => Some(21),
96            Self::iOS => Some(14),
97            _ => None,
98        }
99    }
100    pub fn icon(&self) -> &'static str {
101        match self {
102            Self::Windows | Self::WindowsArm64 => "🪟",
103            Self::MacOS | Self::MacOSArm64 | Self::TVos | Self::VisionOS => "🍎",
104            Self::Linux | Self::LinuxArm64 => "🐧",
105            Self::Android | Self::AndroidX86 => "🤖",
106            Self::iOS => "📱",
107            Self::WebGL | Self::WebGPU | Self::WasmModule => "🌐",
108            Self::XboxSeriesX | Self::XboxOne => "🎮",
109            Self::PlayStation5 | Self::PlayStation4 => "🎮",
110            Self::SwitchNX => "🎮",
111            _ => "📦",
112        }
113    }
114}
115
116// ---------------------------------------------------------------------------
117// Build configuration
118// ---------------------------------------------------------------------------
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
121pub enum BuildConfiguration {
122    Debug,
123    DebugWithOptimizations,
124    Development,
125    Release,
126    ReleaseFinal,
127    Profiling,
128    Shipping,
129    QA,
130}
131
132impl BuildConfiguration {
133    pub fn label(&self) -> &'static str {
134        match self {
135            Self::Debug => "Debug",
136            Self::DebugWithOptimizations => "Debug (Optimized)",
137            Self::Development => "Development",
138            Self::Release => "Release",
139            Self::ReleaseFinal => "Release Final",
140            Self::Profiling => "Profiling",
141            Self::Shipping => "Shipping",
142            Self::QA => "QA",
143        }
144    }
145    pub fn optimization_level(&self) -> u8 {
146        match self {
147            Self::Debug => 0,
148            Self::DebugWithOptimizations => 1,
149            Self::Development => 2,
150            Self::Release | Self::Profiling | Self::QA => 3,
151            Self::ReleaseFinal | Self::Shipping => 3,
152        }
153    }
154    pub fn includes_debug_info(&self) -> bool {
155        matches!(self, Self::Debug | Self::DebugWithOptimizations | Self::Development | Self::Profiling | Self::QA)
156    }
157    pub fn includes_assertions(&self) -> bool {
158        matches!(self, Self::Debug | Self::DebugWithOptimizations | Self::Development | Self::QA)
159    }
160    pub fn strip_logs(&self) -> bool {
161        matches!(self, Self::Shipping | Self::ReleaseFinal)
162    }
163    pub fn enables_cheats(&self) -> bool {
164        matches!(self, Self::Debug | Self::DebugWithOptimizations | Self::Development | Self::QA)
165    }
166    pub fn enable_profiler(&self) -> bool {
167        matches!(self, Self::Profiling | Self::Development | Self::Debug)
168    }
169    pub fn defines(&self) -> Vec<&'static str> {
170        let mut defs = vec![];
171        if self.includes_debug_info() { defs.push("DEBUG"); }
172        if self.includes_assertions() { defs.push("ASSERTIONS_ENABLED"); }
173        if self.strip_logs() { defs.push("STRIP_LOGS"); }
174        if self.enables_cheats() { defs.push("CHEATS_ENABLED"); }
175        if self.enable_profiler() { defs.push("PROFILER_ENABLED"); }
176        match self {
177            Self::Shipping | Self::ReleaseFinal => defs.push("FINAL_BUILD"),
178            Self::QA => defs.push("QA_BUILD"),
179            _ => {}
180        }
181        defs
182    }
183}
184
185// ---------------------------------------------------------------------------
186// Compiler / scripting backend
187// ---------------------------------------------------------------------------
188
189#[derive(Debug, Clone, Copy, PartialEq)]
190pub enum ScriptingBackend { Mono, IL2CPP, NativeAOT, WASM }
191
192impl ScriptingBackend {
193    pub fn label(&self) -> &'static str {
194        match self {
195            Self::Mono => "Mono",
196            Self::IL2CPP => "IL2CPP",
197            Self::NativeAOT => "Native AOT",
198            Self::WASM => "WebAssembly",
199        }
200    }
201    pub fn supports_hot_reload(&self) -> bool {
202        matches!(self, Self::Mono)
203    }
204    pub fn compile_time_estimate_min(&self) -> u32 {
205        match self {
206            Self::Mono => 1,
207            Self::IL2CPP => 8,
208            Self::NativeAOT => 6,
209            Self::WASM => 4,
210        }
211    }
212}
213
214// ---------------------------------------------------------------------------
215// Asset processing pipeline
216// ---------------------------------------------------------------------------
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
219pub enum AssetProcessorKind {
220    TextureCompressor,
221    AudioTranscoder,
222    MeshOptimizer,
223    ShaderCompiler,
224    FontPacker,
225    VideoEncoder,
226    LocalizationPacker,
227    AnimationCompressor,
228    PhysicsColliderBuilder,
229    NavMeshBaker,
230    LightmapBaker,
231    SdfGenerator,
232    AtlasPacker,
233    PrefabSerializer,
234    SceneSerializer,
235    MaterialBaker,
236    LutBaker,
237    EnvironmentProbeConvolver,
238}
239
240impl AssetProcessorKind {
241    pub fn label(&self) -> &'static str {
242        match self {
243            Self::TextureCompressor => "Texture Compressor",
244            Self::AudioTranscoder => "Audio Transcoder",
245            Self::MeshOptimizer => "Mesh Optimizer",
246            Self::ShaderCompiler => "Shader Compiler",
247            Self::FontPacker => "Font Packer",
248            Self::VideoEncoder => "Video Encoder",
249            Self::LocalizationPacker => "Localization Packer",
250            Self::AnimationCompressor => "Animation Compressor",
251            Self::PhysicsColliderBuilder => "Physics Collider Builder",
252            Self::NavMeshBaker => "NavMesh Baker",
253            Self::LightmapBaker => "Lightmap Baker",
254            Self::SdfGenerator => "SDF Generator",
255            Self::AtlasPacker => "Atlas Packer",
256            Self::PrefabSerializer => "Prefab Serializer",
257            Self::SceneSerializer => "Scene Serializer",
258            Self::MaterialBaker => "Material Baker",
259            Self::LutBaker => "LUT Baker",
260            Self::EnvironmentProbeConvolver => "Env Probe Convolver",
261        }
262    }
263    pub fn is_slow(&self) -> bool {
264        matches!(self, Self::LightmapBaker | Self::VideoEncoder | Self::ShaderCompiler | Self::EnvironmentProbeConvolver)
265    }
266    pub fn can_parallelize(&self) -> bool {
267        !matches!(self, Self::AtlasPacker | Self::NavMeshBaker)
268    }
269    pub fn estimated_time_secs(&self, asset_count: usize) -> f32 {
270        let per_asset = match self {
271            Self::TextureCompressor => 0.5,
272            Self::AudioTranscoder => 0.3,
273            Self::MeshOptimizer => 0.2,
274            Self::ShaderCompiler => 2.0,
275            Self::LightmapBaker => 60.0,
276            Self::VideoEncoder => 30.0,
277            Self::NavMeshBaker => 10.0,
278            _ => 0.1,
279        };
280        per_asset * asset_count as f32
281    }
282}
283
284#[derive(Debug, Clone)]
285pub struct AssetProcessorSettings {
286    pub kind: AssetProcessorKind,
287    pub enabled: bool,
288    pub worker_threads: u32,
289    pub cache_enabled: bool,
290    pub incremental: bool,
291    pub platform_overrides: HashMap<BuildPlatform, HashMap<String, String>>,
292    pub params: HashMap<String, String>,
293}
294
295impl AssetProcessorSettings {
296    pub fn new(kind: AssetProcessorKind) -> Self {
297        Self {
298            kind,
299            enabled: true,
300            worker_threads: 4,
301            cache_enabled: true,
302            incremental: true,
303            platform_overrides: HashMap::new(),
304            params: HashMap::new(),
305        }
306    }
307    pub fn with_param(mut self, key: &str, value: &str) -> Self {
308        self.params.insert(key.to_string(), value.to_string());
309        self
310    }
311}
312
313// ---------------------------------------------------------------------------
314// Texture compression formats
315// ---------------------------------------------------------------------------
316
317#[derive(Debug, Clone, Copy, PartialEq)]
318pub enum TextureCompressionFormat {
319    Uncompressed,
320    DXT1,
321    DXT5,
322    BC7,
323    BC6H,
324    ETC2,
325    ASTC4x4,
326    ASTC6x6,
327    ASTC8x8,
328    PVRTC,
329    Crunch,
330    Basis,
331}
332
333impl TextureCompressionFormat {
334    pub fn label(&self) -> &'static str {
335        match self {
336            Self::Uncompressed => "Uncompressed",
337            Self::DXT1 => "DXT1 (BC1)",
338            Self::DXT5 => "DXT5 (BC3)",
339            Self::BC7 => "BC7 (high quality)",
340            Self::BC6H => "BC6H (HDR)",
341            Self::ETC2 => "ETC2",
342            Self::ASTC4x4 => "ASTC 4x4",
343            Self::ASTC6x6 => "ASTC 6x6",
344            Self::ASTC8x8 => "ASTC 8x8",
345            Self::PVRTC => "PVRTC",
346            Self::Crunch => "Crunch",
347            Self::Basis => "Basis Universal",
348        }
349    }
350    pub fn compression_ratio(&self) -> f32 {
351        match self {
352            Self::Uncompressed => 1.0,
353            Self::DXT1 => 6.0,
354            Self::DXT5 => 4.0,
355            Self::BC7 => 4.0,
356            Self::BC6H => 4.0,
357            Self::ETC2 => 4.0,
358            Self::ASTC4x4 => 8.0,
359            Self::ASTC6x6 => 18.0,
360            Self::ASTC8x8 => 32.0,
361            Self::PVRTC => 4.0,
362            Self::Crunch => 20.0,
363            Self::Basis => 25.0,
364        }
365    }
366    pub fn supported_platforms(&self) -> Vec<BuildPlatform> {
367        match self {
368            Self::DXT1 | Self::DXT5 | Self::BC7 | Self::BC6H => vec![BuildPlatform::Windows, BuildPlatform::WindowsArm64, BuildPlatform::Linux, BuildPlatform::MacOS],
369            Self::ETC2 => vec![BuildPlatform::Android, BuildPlatform::AndroidX86],
370            Self::ASTC4x4 | Self::ASTC6x6 | Self::ASTC8x8 => vec![BuildPlatform::iOS, BuildPlatform::Android],
371            Self::PVRTC => vec![BuildPlatform::iOS],
372            Self::Basis | Self::Crunch => vec![BuildPlatform::Windows, BuildPlatform::Linux, BuildPlatform::MacOS, BuildPlatform::Android, BuildPlatform::iOS, BuildPlatform::WebGL],
373            Self::Uncompressed => vec![],
374        }
375    }
376}
377
378// ---------------------------------------------------------------------------
379// Build step
380// ---------------------------------------------------------------------------
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
383pub enum BuildStepKind {
384    CleanOutputDir,
385    CompileScripts,
386    CompileShaders,
387    ProcessAssets,
388    GenerateAssetManifest,
389    LinkBinaries,
390    CopyNativeLibraries,
391    PackageData,
392    SignBinaries,
393    CompressOutput,
394    UploadSymbols,
395    RunTests,
396    GenerateDocumentation,
397    NotifyCISystem,
398    ArchiveBuild,
399    DeployToStore,
400    RunSmokeTest,
401    BumpVersion,
402}
403
404impl BuildStepKind {
405    pub fn label(&self) -> &'static str {
406        match self {
407            Self::CleanOutputDir => "Clean Output Directory",
408            Self::CompileScripts => "Compile Scripts",
409            Self::CompileShaders => "Compile Shaders",
410            Self::ProcessAssets => "Process Assets",
411            Self::GenerateAssetManifest => "Generate Asset Manifest",
412            Self::LinkBinaries => "Link Binaries",
413            Self::CopyNativeLibraries => "Copy Native Libraries",
414            Self::PackageData => "Package Data",
415            Self::SignBinaries => "Sign Binaries",
416            Self::CompressOutput => "Compress Output",
417            Self::UploadSymbols => "Upload Debug Symbols",
418            Self::RunTests => "Run Tests",
419            Self::GenerateDocumentation => "Generate Documentation",
420            Self::NotifyCISystem => "Notify CI System",
421            Self::ArchiveBuild => "Archive Build",
422            Self::DeployToStore => "Deploy to Store",
423            Self::RunSmokeTest => "Run Smoke Test",
424            Self::BumpVersion => "Bump Version",
425        }
426    }
427    pub fn can_skip(&self) -> bool {
428        matches!(self, Self::RunTests | Self::GenerateDocumentation | Self::UploadSymbols | Self::RunSmokeTest)
429    }
430    pub fn requires_internet(&self) -> bool {
431        matches!(self, Self::UploadSymbols | Self::NotifyCISystem | Self::DeployToStore)
432    }
433    pub fn estimated_duration_secs(&self) -> f32 {
434        match self {
435            Self::CleanOutputDir => 2.0,
436            Self::CompileScripts => 30.0,
437            Self::CompileShaders => 120.0,
438            Self::ProcessAssets => 180.0,
439            Self::GenerateAssetManifest => 5.0,
440            Self::LinkBinaries => 15.0,
441            Self::CopyNativeLibraries => 3.0,
442            Self::PackageData => 60.0,
443            Self::SignBinaries => 10.0,
444            Self::CompressOutput => 20.0,
445            Self::UploadSymbols => 30.0,
446            Self::RunTests => 300.0,
447            Self::GenerateDocumentation => 60.0,
448            Self::NotifyCISystem => 2.0,
449            Self::ArchiveBuild => 30.0,
450            Self::DeployToStore => 600.0,
451            Self::RunSmokeTest => 120.0,
452            Self::BumpVersion => 1.0,
453        }
454    }
455}
456
457#[derive(Debug, Clone)]
458pub struct BuildStep {
459    pub kind: BuildStepKind,
460    pub enabled: bool,
461    pub skip_on_error: bool,
462    pub run_on_failure: bool,
463    pub custom_command: Option<String>,
464    pub environment_vars: HashMap<String, String>,
465    pub timeout_secs: Option<u32>,
466}
467
468impl BuildStep {
469    pub fn new(kind: BuildStepKind) -> Self {
470        Self {
471            kind,
472            enabled: true,
473            skip_on_error: false,
474            run_on_failure: false,
475            custom_command: None,
476            environment_vars: HashMap::new(),
477            timeout_secs: None,
478        }
479    }
480    pub fn optional(mut self) -> Self { self.skip_on_error = true; self }
481    pub fn with_timeout(mut self, secs: u32) -> Self { self.timeout_secs = Some(secs); self }
482    pub fn with_command(mut self, cmd: &str) -> Self { self.custom_command = Some(cmd.to_string()); self }
483}
484
485// ---------------------------------------------------------------------------
486// Build target
487// ---------------------------------------------------------------------------
488
489#[derive(Debug, Clone)]
490pub struct BuildTarget {
491    pub name: String,
492    pub platform: BuildPlatform,
493    pub configuration: BuildConfiguration,
494    pub scripting_backend: ScriptingBackend,
495    pub architecture: Architecture,
496    pub output_path: String,
497    pub steps: Vec<BuildStep>,
498    pub asset_processors: Vec<AssetProcessorSettings>,
499    pub texture_compression: TextureCompressionFormat,
500    pub defines: Vec<String>,
501    pub excluded_scenes: Vec<String>,
502    pub included_scenes: Vec<String>,
503    pub compression: PackageCompression,
504    pub split_application_binary: bool,
505    pub development_build: bool,
506    pub connect_profiler: bool,
507    pub allow_debugging: bool,
508    pub il2cpp_optimization: IL2CPPOptimization,
509    pub strip_engine_code: bool,
510    pub strip_unused_code: bool,
511    pub managed_stripping_level: StrippingLevel,
512    pub android_settings: Option<AndroidBuildSettings>,
513    pub ios_settings: Option<IosBuildSettings>,
514    pub signing_settings: Option<SigningSettings>,
515    pub custom_build_script: Option<String>,
516    pub post_build_script: Option<String>,
517    pub enabled: bool,
518}
519
520#[derive(Debug, Clone, Copy, PartialEq)]
521pub enum Architecture { X64, ARM64, X86, Universal, ARMv7 }
522#[derive(Debug, Clone, Copy, PartialEq)]
523pub enum PackageCompression { None, LZ4, LZ4HC, LZMA, Zstd }
524#[derive(Debug, Clone, Copy, PartialEq)]
525pub enum IL2CPPOptimization { None, Size, Speed }
526#[derive(Debug, Clone, Copy, PartialEq)]
527pub enum StrippingLevel { Disabled, Minimal, Medium, High, Experimental }
528
529impl Architecture {
530    pub fn label(&self) -> &'static str {
531        match self {
532            Self::X64 => "x86_64", Self::ARM64 => "ARM64", Self::X86 => "x86",
533            Self::Universal => "Universal", Self::ARMv7 => "ARMv7",
534        }
535    }
536}
537
538impl PackageCompression {
539    pub fn label(&self) -> &'static str {
540        match self {
541            Self::None => "None", Self::LZ4 => "LZ4", Self::LZ4HC => "LZ4HC (Slow)",
542            Self::LZMA => "LZMA", Self::Zstd => "Zstd",
543        }
544    }
545    pub fn ratio_estimate(&self) -> f32 {
546        match self {
547            Self::None => 1.0, Self::LZ4 => 1.4, Self::LZ4HC => 1.6,
548            Self::LZMA => 2.2, Self::Zstd => 2.0,
549        }
550    }
551}
552
553#[derive(Debug, Clone)]
554pub struct AndroidBuildSettings {
555    pub package_name: String,
556    pub version_code: u32,
557    pub version_name: String,
558    pub min_sdk: u32,
559    pub target_sdk: u32,
560    pub keystore_path: String,
561    pub keystore_alias: String,
562    pub build_apk: bool,
563    pub build_aab: bool,
564    pub split_apks: bool,
565    pub target_architectures: Vec<Architecture>,
566    pub install_location: AndroidInstallLocation,
567    pub internet_access: bool,
568    pub vibration: bool,
569    pub accelerometer: bool,
570    pub enable_arm_neon: bool,
571    pub il2cpp_target_api_level: u32,
572}
573
574#[derive(Debug, Clone, Copy, PartialEq)]
575pub enum AndroidInstallLocation { Auto, InternalOnly, PreferExternal }
576
577impl Default for AndroidBuildSettings {
578    fn default() -> Self {
579        Self {
580            package_name: "com.example.game".to_string(),
581            version_code: 1,
582            version_name: "1.0.0".to_string(),
583            min_sdk: 21,
584            target_sdk: 33,
585            keystore_path: String::new(),
586            keystore_alias: String::new(),
587            build_apk: true,
588            build_aab: false,
589            split_apks: false,
590            target_architectures: vec![Architecture::ARM64],
591            install_location: AndroidInstallLocation::Auto,
592            internet_access: true,
593            vibration: true,
594            accelerometer: false,
595            enable_arm_neon: true,
596            il2cpp_target_api_level: 29,
597        }
598    }
599}
600
601#[derive(Debug, Clone)]
602pub struct IosBuildSettings {
603    pub bundle_id: String,
604    pub short_version: String,
605    pub bundle_version: String,
606    pub target_device: IosTargetDevice,
607    pub target_ios_version: String,
608    pub uses_location: bool,
609    pub uses_camera: bool,
610    pub uses_microphone: bool,
611    pub uses_motion_sensors: bool,
612    pub hdr_display_mode: bool,
613    pub requires_full_screen: bool,
614    pub application_category: IosCategory,
615    pub bitcode_enabled: bool,
616    pub apple_developer_team: String,
617    pub provisioning_profile: String,
618    pub metal_enabled: bool,
619}
620
621#[derive(Debug, Clone, Copy, PartialEq)]
622pub enum IosTargetDevice { iPhone, iPad, iPhoneAndiPad }
623#[derive(Debug, Clone, Copy, PartialEq)]
624pub enum IosCategory { Games, Entertainment, Utilities, Education, Other }
625
626impl Default for IosBuildSettings {
627    fn default() -> Self {
628        Self {
629            bundle_id: "com.example.game".to_string(),
630            short_version: "1.0".to_string(),
631            bundle_version: "1".to_string(),
632            target_device: IosTargetDevice::iPhoneAndiPad,
633            target_ios_version: "14.0".to_string(),
634            uses_location: false,
635            uses_camera: false,
636            uses_microphone: false,
637            uses_motion_sensors: false,
638            hdr_display_mode: false,
639            requires_full_screen: true,
640            application_category: IosCategory::Games,
641            bitcode_enabled: false,
642            apple_developer_team: String::new(),
643            provisioning_profile: String::new(),
644            metal_enabled: true,
645        }
646    }
647}
648
649#[derive(Debug, Clone)]
650pub struct SigningSettings {
651    pub certificate_path: String,
652    pub certificate_password: String,
653    pub notarize: bool,
654    pub staple: bool,
655    pub entitlements_path: String,
656    pub codesign_identity: String,
657    pub timestamp_server: String,
658}
659
660impl BuildTarget {
661    pub fn windows_release() -> Self {
662        Self {
663            name: "Windows Release".to_string(),
664            platform: BuildPlatform::Windows,
665            configuration: BuildConfiguration::Release,
666            scripting_backend: ScriptingBackend::NativeAOT,
667            architecture: Architecture::X64,
668            output_path: "build/windows".to_string(),
669            steps: vec![
670                BuildStep::new(BuildStepKind::CleanOutputDir),
671                BuildStep::new(BuildStepKind::CompileShaders),
672                BuildStep::new(BuildStepKind::CompileScripts),
673                BuildStep::new(BuildStepKind::ProcessAssets),
674                BuildStep::new(BuildStepKind::GenerateAssetManifest),
675                BuildStep::new(BuildStepKind::LinkBinaries),
676                BuildStep::new(BuildStepKind::PackageData),
677                BuildStep::new(BuildStepKind::CompressOutput),
678                BuildStep::new(BuildStepKind::RunTests).optional(),
679                BuildStep::new(BuildStepKind::ArchiveBuild),
680            ],
681            asset_processors: vec![
682                AssetProcessorSettings::new(AssetProcessorKind::TextureCompressor).with_param("format", "BC7"),
683                AssetProcessorSettings::new(AssetProcessorKind::ShaderCompiler).with_param("api", "D3D12"),
684                AssetProcessorSettings::new(AssetProcessorKind::AudioTranscoder).with_param("format", "OGG"),
685                AssetProcessorSettings::new(AssetProcessorKind::MeshOptimizer),
686            ],
687            texture_compression: TextureCompressionFormat::BC7,
688            defines: vec!["PLATFORM_WINDOWS".to_string()],
689            excluded_scenes: Vec::new(),
690            included_scenes: Vec::new(),
691            compression: PackageCompression::LZ4HC,
692            split_application_binary: false,
693            development_build: false,
694            connect_profiler: false,
695            allow_debugging: false,
696            il2cpp_optimization: IL2CPPOptimization::Speed,
697            strip_engine_code: true,
698            strip_unused_code: true,
699            managed_stripping_level: StrippingLevel::Medium,
700            android_settings: None,
701            ios_settings: None,
702            signing_settings: None,
703            custom_build_script: None,
704            post_build_script: None,
705            enabled: true,
706        }
707    }
708
709    pub fn android_release() -> Self {
710        let mut t = Self::windows_release();
711        t.name = "Android Release".to_string();
712        t.platform = BuildPlatform::Android;
713        t.output_path = "build/android".to_string();
714        t.texture_compression = TextureCompressionFormat::ASTC4x4;
715        t.scripting_backend = ScriptingBackend::IL2CPP;
716        t.android_settings = Some(AndroidBuildSettings::default());
717        t.defines = vec!["PLATFORM_ANDROID".to_string()];
718        t.asset_processors = vec![
719            AssetProcessorSettings::new(AssetProcessorKind::TextureCompressor).with_param("format", "ASTC"),
720            AssetProcessorSettings::new(AssetProcessorKind::ShaderCompiler).with_param("api", "Vulkan"),
721            AssetProcessorSettings::new(AssetProcessorKind::AudioTranscoder).with_param("format", "OGG"),
722        ];
723        t
724    }
725
726    pub fn web_gl() -> Self {
727        let mut t = Self::windows_release();
728        t.name = "WebGL".to_string();
729        t.platform = BuildPlatform::WebGL;
730        t.output_path = "build/webgl".to_string();
731        t.scripting_backend = ScriptingBackend::WASM;
732        t.texture_compression = TextureCompressionFormat::Basis;
733        t.compression = PackageCompression::Zstd;
734        t.defines = vec!["PLATFORM_WEBGL".to_string()];
735        t.asset_processors = vec![
736            AssetProcessorSettings::new(AssetProcessorKind::TextureCompressor).with_param("format", "Basis"),
737            AssetProcessorSettings::new(AssetProcessorKind::ShaderCompiler).with_param("api", "WebGL2"),
738        ];
739        t
740    }
741
742    pub fn total_estimated_build_time_secs(&self) -> f32 {
743        self.steps.iter()
744            .filter(|s| s.enabled)
745            .map(|s| s.kind.estimated_duration_secs())
746            .sum()
747    }
748
749    pub fn step_count(&self) -> usize {
750        self.steps.iter().filter(|s| s.enabled).count()
751    }
752}
753
754// ---------------------------------------------------------------------------
755// Build log
756// ---------------------------------------------------------------------------
757
758#[derive(Debug, Clone, Copy, PartialEq)]
759pub enum LogLevel { Trace, Debug, Info, Warning, Error, Fatal }
760
761impl LogLevel {
762    pub fn label(&self) -> &'static str {
763        match self {
764            Self::Trace => "TRACE", Self::Debug => "DEBUG", Self::Info => "INFO",
765            Self::Warning => "WARN", Self::Error => "ERROR", Self::Fatal => "FATAL",
766        }
767    }
768    pub fn color(&self) -> Vec4 {
769        match self {
770            Self::Trace => Vec4::new(0.5, 0.5, 0.5, 1.0),
771            Self::Debug => Vec4::new(0.6, 0.8, 1.0, 1.0),
772            Self::Info => Vec4::new(0.9, 0.9, 0.9, 1.0),
773            Self::Warning => Vec4::new(1.0, 0.8, 0.0, 1.0),
774            Self::Error => Vec4::new(1.0, 0.3, 0.3, 1.0),
775            Self::Fatal => Vec4::new(1.0, 0.0, 0.5, 1.0),
776        }
777    }
778    pub fn is_error(&self) -> bool { matches!(self, Self::Error | Self::Fatal) }
779}
780
781#[derive(Debug, Clone)]
782pub struct BuildLogEntry {
783    pub timestamp_ms: f64,
784    pub level: LogLevel,
785    pub step: Option<BuildStepKind>,
786    pub message: String,
787    pub file: Option<String>,
788    pub line: Option<u32>,
789    pub asset_path: Option<String>,
790    pub duration_ms: Option<f32>,
791}
792
793impl BuildLogEntry {
794    pub fn info(msg: &str) -> Self {
795        Self { timestamp_ms: 0.0, level: LogLevel::Info, step: None, message: msg.to_string(), file: None, line: None, asset_path: None, duration_ms: None }
796    }
797    pub fn warning(msg: &str) -> Self {
798        Self { level: LogLevel::Warning, ..Self::info(msg) }
799    }
800    pub fn error(msg: &str) -> Self {
801        Self { level: LogLevel::Error, ..Self::info(msg) }
802    }
803}
804
805#[derive(Debug, Clone)]
806pub struct BuildLog {
807    pub entries: Vec<BuildLogEntry>,
808    pub warnings: u32,
809    pub errors: u32,
810    pub start_time_ms: f64,
811    pub end_time_ms: Option<f64>,
812    pub success: Option<bool>,
813}
814
815impl BuildLog {
816    pub fn new() -> Self {
817        Self { entries: Vec::new(), warnings: 0, errors: 0, start_time_ms: 0.0, end_time_ms: None, success: None }
818    }
819    pub fn push(&mut self, entry: BuildLogEntry) {
820        if entry.level == LogLevel::Warning { self.warnings += 1; }
821        if entry.level.is_error() { self.errors += 1; }
822        self.entries.push(entry);
823    }
824    pub fn duration_ms(&self) -> Option<f64> {
825        self.end_time_ms.map(|e| e - self.start_time_ms)
826    }
827    pub fn duration_secs(&self) -> Option<f32> {
828        self.duration_ms().map(|d| d as f32 / 1000.0)
829    }
830    pub fn filter_by_level(&self, level: LogLevel) -> Vec<&BuildLogEntry> {
831        self.entries.iter().filter(|e| e.level == level).collect()
832    }
833    pub fn errors_and_warnings(&self) -> Vec<&BuildLogEntry> {
834        self.entries.iter().filter(|e| e.level.is_error() || e.level == LogLevel::Warning).collect()
835    }
836    pub fn generate_summary(&self) -> String {
837        let dur = self.duration_secs().map(|d| format!("{:.1}s", d)).unwrap_or_else(|| "N/A".to_string());
838        let status = match self.success {
839            Some(true) => "SUCCESS",
840            Some(false) => "FAILED",
841            None => "IN PROGRESS",
842        };
843        format!("Build {}: {} warnings, {} errors, duration: {}", status, self.warnings, self.errors, dur)
844    }
845}
846
847impl Default for BuildLog {
848    fn default() -> Self { Self::new() }
849}
850
851// ---------------------------------------------------------------------------
852// Build run state
853// ---------------------------------------------------------------------------
854
855#[derive(Debug, Clone, Copy, PartialEq)]
856pub enum BuildState {
857    Idle,
858    Preparing,
859    Running,
860    Paused,
861    Completed,
862    Failed,
863    Cancelled,
864}
865
866impl BuildState {
867    pub fn label(&self) -> &'static str {
868        match self {
869            Self::Idle => "Idle",
870            Self::Preparing => "Preparing",
871            Self::Running => "Running",
872            Self::Paused => "Paused",
873            Self::Completed => "Completed",
874            Self::Failed => "Failed",
875            Self::Cancelled => "Cancelled",
876        }
877    }
878    pub fn is_active(&self) -> bool { matches!(self, Self::Running | Self::Preparing) }
879    pub fn color(&self) -> Vec4 {
880        match self {
881            Self::Idle => Vec4::new(0.6, 0.6, 0.6, 1.0),
882            Self::Preparing | Self::Running => Vec4::new(0.4, 0.8, 1.0, 1.0),
883            Self::Paused => Vec4::new(1.0, 0.8, 0.2, 1.0),
884            Self::Completed => Vec4::new(0.3, 0.9, 0.3, 1.0),
885            Self::Failed => Vec4::new(1.0, 0.3, 0.3, 1.0),
886            Self::Cancelled => Vec4::new(0.8, 0.5, 0.2, 1.0),
887        }
888    }
889}
890
891// ---------------------------------------------------------------------------
892// Version info
893// ---------------------------------------------------------------------------
894
895#[derive(Debug, Clone)]
896pub struct VersionInfo {
897    pub major: u32,
898    pub minor: u32,
899    pub patch: u32,
900    pub build_number: u32,
901    pub pre_release: String,
902    pub git_commit: String,
903    pub git_branch: String,
904    pub build_date: String,
905    pub channel: ReleaseChannel,
906}
907
908#[derive(Debug, Clone, Copy, PartialEq)]
909pub enum ReleaseChannel { Dev, Alpha, Beta, ReleaseCandidate, Stable, Hotfix }
910
911impl ReleaseChannel {
912    pub fn label(&self) -> &'static str {
913        match self {
914            Self::Dev => "dev", Self::Alpha => "alpha", Self::Beta => "beta",
915            Self::ReleaseCandidate => "rc", Self::Stable => "stable", Self::Hotfix => "hotfix",
916        }
917    }
918}
919
920impl VersionInfo {
921    pub fn format_full(&self) -> String {
922        let pre = if self.pre_release.is_empty() { String::new() } else { format!("-{}", self.pre_release) };
923        format!("{}.{}.{}{} (build {})", self.major, self.minor, self.patch, pre, self.build_number)
924    }
925    pub fn format_short(&self) -> String {
926        format!("{}.{}.{}", self.major, self.minor, self.patch)
927    }
928    pub fn bump_patch(&mut self) { self.patch += 1; self.build_number += 1; }
929    pub fn bump_minor(&mut self) { self.minor += 1; self.patch = 0; self.build_number += 1; }
930    pub fn bump_major(&mut self) { self.major += 1; self.minor = 0; self.patch = 0; self.build_number += 1; }
931}
932
933impl Default for VersionInfo {
934    fn default() -> Self {
935        Self {
936            major: 0, minor: 1, patch: 0, build_number: 1,
937            pre_release: "alpha".to_string(),
938            git_commit: "abcdef1".to_string(),
939            git_branch: "main".to_string(),
940            build_date: "2026-03-29".to_string(),
941            channel: ReleaseChannel::Alpha,
942        }
943    }
944}
945
946// ---------------------------------------------------------------------------
947// CI integration
948// ---------------------------------------------------------------------------
949
950#[derive(Debug, Clone, Copy, PartialEq)]
951pub enum CIProvider { GitHub, GitLab, Jenkins, CircleCI, TeamCity, AzureDevOps, Buildkite, Bamboo, Custom }
952
953impl CIProvider {
954    pub fn label(&self) -> &'static str {
955        match self {
956            Self::GitHub => "GitHub Actions",
957            Self::GitLab => "GitLab CI",
958            Self::Jenkins => "Jenkins",
959            Self::CircleCI => "CircleCI",
960            Self::TeamCity => "TeamCity",
961            Self::AzureDevOps => "Azure DevOps",
962            Self::Buildkite => "Buildkite",
963            Self::Bamboo => "Bamboo",
964            Self::Custom => "Custom",
965        }
966    }
967    pub fn webhook_format(&self) -> &'static str {
968        match self {
969            Self::GitHub => "json",
970            Self::Jenkins => "text/plain",
971            _ => "json",
972        }
973    }
974}
975
976#[derive(Debug, Clone)]
977pub struct CISettings {
978    pub provider: CIProvider,
979    pub webhook_url: String,
980    pub api_token: String,
981    pub trigger_on_push: bool,
982    pub trigger_on_tag: bool,
983    pub trigger_branches: Vec<String>,
984    pub notify_on_success: bool,
985    pub notify_on_failure: bool,
986    pub notify_emails: Vec<String>,
987    pub artifact_retention_days: u32,
988    pub parallel_jobs: u32,
989}
990
991impl Default for CISettings {
992    fn default() -> Self {
993        Self {
994            provider: CIProvider::GitHub,
995            webhook_url: String::new(),
996            api_token: String::new(),
997            trigger_on_push: true,
998            trigger_on_tag: true,
999            trigger_branches: vec!["main".to_string(), "release/*".to_string()],
1000            notify_on_success: false,
1001            notify_on_failure: true,
1002            notify_emails: Vec::new(),
1003            artifact_retention_days: 30,
1004            parallel_jobs: 4,
1005        }
1006    }
1007}
1008
1009// ---------------------------------------------------------------------------
1010// Build system editor
1011// ---------------------------------------------------------------------------
1012
1013#[derive(Debug, Clone, Copy, PartialEq)]
1014pub enum BuildEditorPanel {
1015    Targets,
1016    AssetPipeline,
1017    Versioning,
1018    CIIntegration,
1019    Log,
1020    Artifacts,
1021    Settings,
1022}
1023
1024impl BuildEditorPanel {
1025    pub fn label(&self) -> &'static str {
1026        match self {
1027            Self::Targets => "Build Targets",
1028            Self::AssetPipeline => "Asset Pipeline",
1029            Self::Versioning => "Versioning",
1030            Self::CIIntegration => "CI/CD",
1031            Self::Log => "Build Log",
1032            Self::Artifacts => "Artifacts",
1033            Self::Settings => "Settings",
1034        }
1035    }
1036}
1037
1038#[derive(Debug, Clone)]
1039pub struct BuildArtifact {
1040    pub name: String,
1041    pub platform: BuildPlatform,
1042    pub configuration: BuildConfiguration,
1043    pub version: String,
1044    pub file_size_bytes: u64,
1045    pub compressed_size_bytes: u64,
1046    pub path: String,
1047    pub created_at: String,
1048    pub sha256: String,
1049    pub upload_url: Option<String>,
1050    pub build_log_path: String,
1051}
1052
1053impl BuildArtifact {
1054    pub fn size_mb(&self) -> f32 { self.file_size_bytes as f32 / (1024.0 * 1024.0) }
1055    pub fn compressed_mb(&self) -> f32 { self.compressed_size_bytes as f32 / (1024.0 * 1024.0) }
1056    pub fn compression_ratio(&self) -> f32 {
1057        if self.file_size_bytes == 0 { return 1.0; }
1058        self.file_size_bytes as f32 / self.compressed_size_bytes.max(1) as f32
1059    }
1060}
1061
1062#[derive(Debug)]
1063pub struct BuildSystemEditor {
1064    pub targets: Vec<BuildTarget>,
1065    pub selected_target: Option<usize>,
1066    pub active_panel: BuildEditorPanel,
1067    pub build_state: BuildState,
1068    pub current_step_index: usize,
1069    pub step_progress: f32,
1070    pub overall_progress: f32,
1071    pub current_log: BuildLog,
1072    pub historical_logs: Vec<BuildLog>,
1073    pub artifacts: Vec<BuildArtifact>,
1074    pub version: VersionInfo,
1075    pub ci_settings: CISettings,
1076    pub asset_processors: Vec<AssetProcessorSettings>,
1077    pub build_queue: Vec<usize>,
1078    pub last_build_duration_secs: f32,
1079    pub search_filter: String,
1080    pub log_level_filter: Option<LogLevel>,
1081    pub show_only_errors: bool,
1082    pub auto_run_tests: bool,
1083    pub parallel_asset_processing: bool,
1084    pub asset_cache_path: String,
1085    pub output_base_path: String,
1086}
1087
1088impl Default for BuildSystemEditor {
1089    fn default() -> Self {
1090        let targets = vec![
1091            BuildTarget::windows_release(),
1092            BuildTarget::android_release(),
1093            BuildTarget::web_gl(),
1094        ];
1095
1096        let mut log = BuildLog::new();
1097        log.push(BuildLogEntry::info("Build system initialized"));
1098        log.push(BuildLogEntry::info("Loaded 3 build targets"));
1099        log.push(BuildLogEntry::warning("Shader cache is stale, recommend recompiling"));
1100
1101        let asset_processors = vec![
1102            AssetProcessorSettings::new(AssetProcessorKind::TextureCompressor).with_param("quality", "high"),
1103            AssetProcessorSettings::new(AssetProcessorKind::AudioTranscoder).with_param("bitrate", "192"),
1104            AssetProcessorSettings::new(AssetProcessorKind::MeshOptimizer).with_param("lod_levels", "3"),
1105            AssetProcessorSettings::new(AssetProcessorKind::ShaderCompiler).with_param("cache", "true"),
1106            AssetProcessorSettings::new(AssetProcessorKind::FontPacker),
1107            AssetProcessorSettings::new(AssetProcessorKind::AnimationCompressor),
1108            AssetProcessorSettings::new(AssetProcessorKind::NavMeshBaker),
1109            AssetProcessorSettings::new(AssetProcessorKind::LutBaker),
1110        ];
1111
1112        Self {
1113            targets,
1114            selected_target: Some(0),
1115            active_panel: BuildEditorPanel::Targets,
1116            build_state: BuildState::Idle,
1117            current_step_index: 0,
1118            step_progress: 0.0,
1119            overall_progress: 0.0,
1120            current_log: log,
1121            historical_logs: Vec::new(),
1122            artifacts: Vec::new(),
1123            version: VersionInfo::default(),
1124            ci_settings: CISettings::default(),
1125            asset_processors,
1126            build_queue: Vec::new(),
1127            last_build_duration_secs: 0.0,
1128            search_filter: String::new(),
1129            log_level_filter: None,
1130            show_only_errors: false,
1131            auto_run_tests: false,
1132            parallel_asset_processing: true,
1133            asset_cache_path: ".build_cache".to_string(),
1134            output_base_path: "build".to_string(),
1135        }
1136    }
1137}
1138
1139impl BuildSystemEditor {
1140    pub fn selected_target(&self) -> Option<&BuildTarget> {
1141        self.selected_target.and_then(|i| self.targets.get(i))
1142    }
1143
1144    pub fn start_build(&mut self, target_index: usize) {
1145        self.build_state = BuildState::Running;
1146        self.current_step_index = 0;
1147        self.step_progress = 0.0;
1148        self.overall_progress = 0.0;
1149        self.current_log = BuildLog::new();
1150        self.current_log.push(BuildLogEntry::info(&format!("Starting build: {}", self.targets[target_index].name)));
1151    }
1152
1153    pub fn simulate_build_tick(&mut self, dt: f32) {
1154        if self.build_state != BuildState::Running { return; }
1155        let target = match self.selected_target.and_then(|i| self.targets.get(i)) {
1156            Some(t) => t,
1157            None => return,
1158        };
1159        let total_steps = target.step_count();
1160        if total_steps == 0 { self.build_state = BuildState::Completed; return; }
1161
1162        self.step_progress += dt * 0.5;
1163        if self.step_progress >= 1.0 {
1164            self.step_progress = 0.0;
1165            self.current_step_index += 1;
1166            if self.current_step_index >= total_steps {
1167                self.build_state = BuildState::Completed;
1168                self.current_log.success = Some(true);
1169                self.current_log.push(BuildLogEntry::info("Build completed successfully"));
1170                return;
1171            }
1172            let enabled_steps: Vec<&BuildStep> = target.steps.iter().filter(|s| s.enabled).collect();
1173            if let Some(step) = enabled_steps.get(self.current_step_index) {
1174                self.current_log.push(BuildLogEntry::info(&format!("Step: {}", step.kind.label())));
1175            }
1176        }
1177        self.overall_progress = (self.current_step_index as f32 + self.step_progress) / total_steps as f32;
1178    }
1179
1180    pub fn cancel_build(&mut self) {
1181        if self.build_state.is_active() {
1182            self.build_state = BuildState::Cancelled;
1183            self.current_log.success = Some(false);
1184            self.current_log.push(BuildLogEntry::warning("Build cancelled by user"));
1185        }
1186    }
1187
1188    pub fn filtered_log_entries(&self) -> Vec<&BuildLogEntry> {
1189        let q = self.search_filter.to_lowercase();
1190        self.current_log.entries.iter().filter(|e| {
1191            let level_ok = if self.show_only_errors { e.level.is_error() }
1192                else if let Some(lvl) = self.log_level_filter { e.level == lvl } else { true };
1193            let text_ok = q.is_empty() || e.message.to_lowercase().contains(&q);
1194            level_ok && text_ok
1195        }).collect()
1196    }
1197
1198    pub fn add_to_queue(&mut self, target_index: usize) {
1199        if !self.build_queue.contains(&target_index) {
1200            self.build_queue.push(target_index);
1201        }
1202    }
1203
1204    pub fn remove_from_queue(&mut self, target_index: usize) {
1205        self.build_queue.retain(|&i| i != target_index);
1206    }
1207
1208    pub fn total_estimated_build_time(&self) -> f32 {
1209        self.targets.iter().filter(|t| t.enabled).map(|t| t.total_estimated_build_time_secs()).sum()
1210    }
1211
1212    pub fn platforms_summary(&self) -> Vec<(BuildPlatform, usize)> {
1213        let mut counts: HashMap<BuildPlatform, usize> = HashMap::new();
1214        for t in &self.targets {
1215            *counts.entry(t.platform).or_insert(0) += 1;
1216        }
1217        counts.into_iter().collect()
1218    }
1219
1220    pub fn generate_ci_yaml(&self) -> String {
1221        match self.ci_settings.provider {
1222            CIProvider::GitHub => {
1223                let mut lines = vec![
1224                    "name: Build".to_string(),
1225                    "on:".to_string(),
1226                    "  push:".to_string(),
1227                    "    branches: [main, 'release/*']".to_string(),
1228                    "  tags: ['v*']".to_string(),
1229                    "jobs:".to_string(),
1230                    "  build:".to_string(),
1231                    "    runs-on: ubuntu-latest".to_string(),
1232                    "    steps:".to_string(),
1233                    "    - uses: actions/checkout@v4".to_string(),
1234                    "    - name: Build All Targets".to_string(),
1235                    "      run: cargo build --release".to_string(),
1236                ];
1237                for target in &self.targets {
1238                    if target.enabled {
1239                        lines.push(format!("    # Target: {} ({})", target.name, target.platform.label()));
1240                    }
1241                }
1242                lines.join("\n")
1243            }
1244            _ => "# CI configuration not yet generated for this provider".to_string(),
1245        }
1246    }
1247}