Maintain/Build/
Process.rs1use std::{
66 env,
67 fs,
68 path::PathBuf,
69 process::{Command as ProcessCommand, Stdio},
70};
71
72use log::info;
73use toml;
74
75use crate::Build::Error::Error as BuildError;
93use crate::Build::{
94 Constant::{CargoFile, CocoonEsbuildDefineEnv, IdDelimiter, JsonFile, JsonfiveFile, NameDelimiter},
95 Definition::{Argument, Guard, Manifest},
96 GetTauriTargetTriple::GetTauriTargetTriple,
97 JsonEdit::JsonEdit,
98 Pascalize::Pascalize,
99 TomlEdit::TomlEdit,
100 WordsFromPascal::WordsFromPascal,
101};
102
103pub fn Process(Argument:&Argument) -> Result<(), BuildError> {
178 info!(target: "Build", "Starting build orchestration...");
179
180 log::debug!(target: "Build", "Argument: {:?}", Argument);
181
182 if let Some(Features) = Argument.CargoFeatures.as_deref().filter(|v| !v.is_empty()) {
188 info!(target: "Build", "Cargo features: {}", Features);
189 }
190
191 if let Some(Defines) = Argument.CocoonEsbuildDefine.as_deref().filter(|v| !v.is_empty()) {
192 info!(target: "Build", "Cocoon esbuild defines: {}", Defines);
193 }
194
195 let ProjectDir = PathBuf::from(&Argument.Directory);
196
197 if !ProjectDir.is_dir() {
198 return Err(BuildError::Missing(ProjectDir));
199 }
200
201 let CargoPath = ProjectDir.join(CargoFile);
202
203 let ConfigPath = {
204 let Jsonfive = ProjectDir.join(JsonfiveFile);
205
206 if Jsonfive.exists() { Jsonfive } else { ProjectDir.join(JsonFile) }
207 };
208
209 if !ConfigPath.exists() {
210 return Err(BuildError::Config);
211 }
212
213 let mut CargoGuard = Guard::New(CargoPath.clone(), "Cargo.toml".to_string())?;
215
216 let mut ConfigGuard = Guard::New(ConfigPath.clone(), "Tauri config".to_string())?;
217
218 let mut NamePartsForProductName = Vec::new();
219
220 let mut NamePartsForId = Vec::new();
221
222 if let Some(NodeValue) = &Argument.Environment {
224 if !NodeValue.is_empty() {
225 let PascalEnv = Pascalize(NodeValue);
226
227 if !PascalEnv.is_empty() {
228 NamePartsForProductName.push(format!("{}NodeEnvironment", PascalEnv));
229
230 NamePartsForId.extend(WordsFromPascal(&PascalEnv));
231
232 NamePartsForId.push("node".to_string());
233
234 NamePartsForId.push("environment".to_string());
235 }
236 }
237 }
238
239 if let Some(DependencyValue) = &Argument.Dependency {
241 if !DependencyValue.is_empty() {
242 let (PascalDepBase, IdDepWords) = if DependencyValue.eq_ignore_ascii_case("true") {
243 ("Generic".to_string(), vec!["generic".to_string()])
244 } else if let Some((Org, Repo)) = DependencyValue.split_once('/') {
245 (format!("{}{}", Pascalize(Org), Pascalize(Repo)), {
246 let mut w = WordsFromPascal(&Pascalize(Org));
247
248 w.extend(WordsFromPascal(&Pascalize(Repo)));
249
250 w
251 })
252 } else {
253 (Pascalize(DependencyValue), WordsFromPascal(&Pascalize(DependencyValue)))
254 };
255
256 if !PascalDepBase.is_empty() {
257 NamePartsForProductName.push(format!("{}Dependency", PascalDepBase));
258
259 NamePartsForId.extend(IdDepWords);
260
261 NamePartsForId.push("dependency".to_string());
262 }
263 }
264 }
265
266 if let Some(Version) = &Argument.NodeVersion {
268 if !Version.is_empty() {
269 let PascalVersion = format!("{}NodeVersion", Version);
270
271 NamePartsForProductName.push(PascalVersion.clone());
272
273 NamePartsForId.push("node".to_string());
274
275 NamePartsForId.push(Version.to_string());
276 }
277 }
278
279 if Argument.Bundle.as_ref().map_or(false, |v| v == "true") {
281 NamePartsForProductName.push("Bundle".to_string());
282
283 NamePartsForId.push("bundle".to_string());
284 }
285
286 if Argument.Clean.as_ref().map_or(false, |v| v == "true") {
287 NamePartsForProductName.push("Clean".to_string());
288
289 NamePartsForId.push("clean".to_string());
290 }
291
292 if Argument.Browser.as_ref().map_or(false, |v| v == "true") {
293 NamePartsForProductName.push("Browser".to_string());
294
295 NamePartsForId.push("browser".to_string());
296 }
297
298 if Argument.Compile.as_ref().map_or(false, |v| v == "true") {
299 NamePartsForProductName.push("Compile".to_string());
300
301 NamePartsForId.push("compile".to_string());
302 }
303
304 if Argument.Debug.as_ref().map_or(false, |v| v == "true")
305 || Argument.Command.iter().any(|arg| arg.contains("--debug"))
306 {
307 NamePartsForProductName.push("Debug".to_string());
308
309 NamePartsForId.push("debug".to_string());
310 }
311
312 if Argument.Mountain.as_ref().map_or(false, |v| v == "true") {
319 NamePartsForProductName.push("MountainProfile".to_string());
320 NamePartsForId.push("mountain".to_string());
321 NamePartsForId.push("profile".to_string());
322 }
323
324 if Argument.Electron.as_ref().map_or(false, |v| v == "true") {
325 NamePartsForProductName.push("ElectronProfile".to_string());
326 NamePartsForId.push("electron".to_string());
327 NamePartsForId.push("profile".to_string());
328 }
329
330 if let Some(Variant) = &Argument.Compiler {
334 if !Variant.is_empty() {
335 let PascalCompiler = Pascalize(Variant);
336 if !PascalCompiler.is_empty() {
337 NamePartsForProductName.push(format!("{}Compiler", PascalCompiler));
338 NamePartsForId.extend(WordsFromPascal(&PascalCompiler));
339 NamePartsForId.push("compiler".to_string());
340 }
341 }
342 }
343
344 let ProductNamePrefix = NamePartsForProductName.join(NameDelimiter);
346
347 let FinalName = if !ProductNamePrefix.is_empty() {
348 format!("{}{}{}", ProductNamePrefix, NameDelimiter, Argument.Name)
349 } else {
350 Argument.Name.clone()
351 };
352
353 info!(target: "Build", "Final generated product name: '{}'", FinalName);
354
355 NamePartsForId.extend(WordsFromPascal(&Argument.Name));
357
358 let IdSuffix = NamePartsForId
359 .into_iter()
360 .filter(|s| !s.is_empty())
361 .collect::<Vec<String>>()
362 .join(IdDelimiter);
363
364 let FinalId = format!("{}{}{}", Argument.Prefix, IdDelimiter, IdSuffix);
365
366 info!(target: "Build", "Generated bundle identifier: '{}'", FinalId);
367
368 if FinalName != Argument.Name {
370 TomlEdit(&CargoPath, &Argument.Name, &FinalName)?;
371 }
372
373 let AppVersion = toml::from_str::<Manifest>(&fs::read_to_string(&CargoPath)?)?
375 .get_version()
376 .to_string();
377
378 JsonEdit(
380 &ConfigPath,
381 &FinalName,
382 &FinalId,
383 &AppVersion,
384 (if let Some(version) = &Argument.NodeVersion {
385 info!(target: "Build", "Selected Node.js version: {}", version);
386
387 let Triple = GetTauriTargetTriple();
388
389 let Executable = if cfg!(target_os = "windows") {
391 PathBuf::from(format!("./Element/SideCar/{}/NODE/{}/node.exe", Triple, version))
392 } else {
393 PathBuf::from(format!("./Element/SideCar/{}/NODE/{}/bin/node", Triple, version))
394 };
395
396 let DirectorySideCarTemporary = ProjectDir.join("Binary");
398
399 fs::create_dir_all(&DirectorySideCarTemporary)?;
400
401 let PathExecutableDestination = if cfg!(target_os = "windows") {
403 DirectorySideCarTemporary.join(format!("node-{}.exe", Triple))
404 } else {
405 DirectorySideCarTemporary.join(format!("node-{}", Triple))
406 };
407
408 info!(
409 target: "Build",
410 "Staging sidecar from {} to {}",
411 Executable.display(),
412 PathExecutableDestination.display()
413 );
414
415 fs::copy(&Executable, &PathExecutableDestination)?;
417
418 #[cfg(not(target_os = "windows"))]
420 {
421 use std::os::unix::fs::PermissionsExt;
422
423 let mut Permission = fs::metadata(&PathExecutableDestination)?.permissions();
424
425 Permission.set_mode(0o755);
427
428 fs::set_permissions(&PathExecutableDestination, Permission)?;
429 }
430
431 Some("Binary/node".to_string())
432 } else {
433 info!(target: "Build", "No Node.js flavour selected for bundling.");
434
435 None
436 })
437 .as_deref(),
438 )?;
439
440 if Argument.Command.is_empty() {
442 return Err(BuildError::NoCommand);
443 }
444
445 let mut CommandArguments:Vec<String> = Argument.Command.clone();
451
452 let IsTauriBuild = CommandArguments.len() >= 3
453 && CommandArguments[0] == "pnpm"
454 && CommandArguments[1] == "tauri"
455 && CommandArguments[2] == "build";
456
457 if IsTauriBuild {
458 if let Some(Features) = Argument.CargoFeatures.as_deref().filter(|v| !v.is_empty()) {
459 let AlreadyPresent = CommandArguments.iter().any(|a| a == "--features" || a == "-f");
460
461 if !AlreadyPresent {
462 info!(
463 target: "Build",
464 "Forwarding Cargo features to `tauri build`: {}",
465 Features
466 );
467 CommandArguments.push("--features".to_string());
468 CommandArguments.push(Features.to_string());
469 }
470 }
471 }
472
473 let mut ShellCommand = if cfg!(target_os = "windows") {
474 let mut Command = ProcessCommand::new("cmd");
475
476 Command.arg("/C").args(&CommandArguments);
477
478 Command
479 } else {
480 let mut Command = ProcessCommand::new(&CommandArguments[0]);
481
482 Command.args(&CommandArguments[1..]);
483
484 Command
485 };
486
487 if let Some(Defines) = Argument.CocoonEsbuildDefine.as_deref().filter(|v| !v.is_empty()) {
492 ShellCommand.env(CocoonEsbuildDefineEnv, Defines);
493 }
494
495 info!(target: "Build::Exec", "Executing final build command: {:?}", ShellCommand);
496
497 let Status = ShellCommand
498 .current_dir(env::current_dir()?)
499 .stdout(Stdio::inherit())
500 .stderr(Stdio::inherit())
501 .status()?;
502
503 if !Status.success() {
505 let temp_sidecar_dir = ProjectDir.join("bin");
506
507 if temp_sidecar_dir.exists() {
508 let _ = fs::remove_dir_all(&temp_sidecar_dir);
509 }
510
511 return Err(BuildError::Shell(Status));
512 }
513
514 let DirectorySideCarTemporary = ProjectDir.join("bin");
516
517 if DirectorySideCarTemporary.exists() {
518 fs::remove_dir_all(&DirectorySideCarTemporary)?;
519
520 info!(target: "Build", "Cleaned up temporary sidecar directory.");
521 }
522
523 drop(CargoGuard);
528 drop(ConfigGuard);
529
530 info!(target: "Build", "Build orchestration completed successfully.");
531
532 Ok(())
533}