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