1
//! The crate responsible for executing the core work of `Zork++`,
2
//! generate command lines and execute them in a shell of the current
3
//! operating system against the designed compilers in the configuration
4
//! file.
5

            
6
use color_eyre::eyre::{Context, ContextCompat};
7
use std::path::Path;
8

            
9
use color_eyre::Result;
10

            
11
use crate::domain::commands::arguments::Argument;
12
use crate::domain::flyweight_data::FlyweightData;
13
use crate::domain::target::TargetIdentifier;
14
use crate::domain::translation_unit::TranslationUnitStatus;
15
use crate::project_model::modules::SystemModule;
16
use crate::project_model::target::TargetModel;
17
use crate::utils::constants::error_messages;
18
use crate::{
19
    cache::ZorkCache,
20
    domain::translation_unit::{TranslationUnit, TranslationUnitKind},
21
    project_model::{
22
        compiler::{CppCompiler, StdLibMode},
23
        modules::{ModuleImplementationModel, ModuleInterfaceModel},
24
        sourceset::SourceFile,
25
        ZorkModel,
26
    },
27
    utils::constants,
28
};
29

            
30
/// The core procedure. Generates the commands arguments that will be sent to the compiler
31
/// for every translation unit declared by the user for its project
32
2
pub fn generate_commands_arguments<'a>(
33
    model: &'a ZorkModel<'a>,
34
    cache: &mut ZorkCache<'a>,
35
) -> Result<()> {
36
    // Load the flyweight arguments (repeated args across all the source command lines)
37
2
    if cache.generated_commands.flyweight_data.is_none() || cache.metadata.cfg_modified {
38
2
        cache.generated_commands.flyweight_data =
39
2
            Some(FlyweightData::new(model, &cache.compilers_metadata));
40
    }
41

            
42
    // Build the std library as a module
43
2
    generate_modular_stdlibs_cmds(model, cache);
44

            
45
    // System headers as modules
46
2
    if model.compiler.cpp_compiler != CppCompiler::MSVC && !model.modules.sys_modules.is_empty() {
47
1
        generate_sys_modules_commands(model, cache)?;
48
    }
49

            
50
    // Generates commands for the modules
51
2
    process_modules(model, cache)?;
52

            
53
    // Translation units and linker
54
    // Generate commands for the declared targets
55
4
    process_targets(model, cache)?;
56

            
57
2
    Ok(())
58
2
}
59

            
60
/// Generates the cmds for build the C++ standard libraries (std and std.compat) according to the specification
61
/// of each compiler vendor
62
2
fn generate_modular_stdlibs_cmds<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) {
63
2
    match model.compiler.cpp_compiler {
64
        CppCompiler::CLANG => {
65
1
            if cache.compilers_metadata.clang.major > 17 {
66
                modules::generate_modular_cpp_stdlib_args(model, cache, StdLibMode::Cpp);
67
                modules::generate_modular_cpp_stdlib_args(model, cache, StdLibMode::CCompat);
68
            }
69
        }
70
        CppCompiler::MSVC => {
71
            modules::generate_modular_cpp_stdlib_args(model, cache, StdLibMode::Cpp);
72
            modules::generate_modular_cpp_stdlib_args(model, cache, StdLibMode::CCompat);
73
        }
74
        _ => (),
75
    }
76
2
}
77

            
78
/// Procedure to generate the commands for the system headers of their standard C++ library
79
/// for a given compiler
80
///
81
/// These commands are the ones that allows to translate C++ standard headers to named modules
82
1
fn generate_sys_modules_commands<'a>(
83
    model: &'a ZorkModel<'a>,
84
    cache: &mut ZorkCache<'a>,
85
) -> Result<()> {
86
1
    process_kind_translation_units(
87
        model,
88
        cache,
89
1
        &model.modules.sys_modules,
90
1
        TranslationUnitKind::SystemHeader,
91
    )
92
    .with_context(|| error_messages::FAILURE_SYSTEM_MODULES)
93
1
}
94

            
95
/// The procedure that takes care of generating the [`SourceCommandLine`] to build the user's declared
96
/// C++ standard names modules
97
2
fn process_modules<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) -> Result<()> {
98
2
    let modules = &model.modules;
99

            
100
2
    log::info!("Generating the commands for the module interfaces and partitions...");
101
2
    process_kind_translation_units(
102
        model,
103
        cache,
104
2
        &modules.interfaces,
105
2
        TranslationUnitKind::ModuleInterface,
106
    )
107
    .with_context(|| error_messages::FAILURE_MODULE_INTERFACES)?;
108

            
109
2
    log::info!("Generating the commands for the module implementations and partitions...");
110
4
    process_kind_translation_units(
111
        model,
112
        cache,
113
2
        &modules.implementations,
114
2
        TranslationUnitKind::ModuleImplementation,
115
    )
116
    .with_context(|| error_messages::FAILURE_MODULE_IMPLEMENTATIONS)?;
117

            
118
2
    Ok(())
119
2
}
120

            
121
2
fn process_targets<'a>(model: &'a ZorkModel<'a>, cache: &mut ZorkCache<'a>) -> Result<()> {
122
6
    for target in model
123
        .targets
124
        .iter()
125
4
        .filter(|(_, target_data)| target_data.enabled_for_current_program_iteration)
126
    {
127
        // 1st - Generate the commands for the non-module sources
128
4
        generate_sources_cmds_args(model, cache, target)?;
129
        // 2nd - Generate the linker command for the 'target' declared by the user
130
6
        generate_linkage_targets_commands(model, cache, target)?;
131
    }
132

            
133
2
    Ok(())
134
2
}
135

            
136
/// Processor for generate the commands of the non-modular translation units
137
///
138
/// *NOTE*: This will be changed on the future, when we decide how we should architecture the implementation
139
/// of named targets
140
///
141
/// *IMPL_NOTE*: Consider in the future if it's worth to maintain two paths for build module implementations
142
/// and source, since they are basically (almost) the same thing
143
4
fn generate_sources_cmds_args<'a>(
144
    model: &'a ZorkModel<'a>,
145
    cache: &mut ZorkCache<'a>,
146
    target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>),
147
) -> Result<()> {
148
4
    let target_identifier = target.0;
149
4
    let target_data = target.1;
150

            
151
4
    log::info!(
152
        "Generating the commands for the source files of target: {:?}",
153
        target_identifier.name()
154
    );
155

            
156
4
    process_kind_translation_units(
157
        model,
158
        cache,
159
4
        target_data.sources.as_slice(),
160
        // names
161
4
        TranslationUnitKind::SourceFile(target_identifier),
162
    )
163
    .with_context(|| error_messages::TARGET_SOURCES_FAILURE)
164
4
}
165

            
166
/// Generates the command line that will be passed to the linker to generate an target final product
167
4
fn generate_linkage_targets_commands<'a>(
168
    model: &'a ZorkModel<'_>,
169
    cache: &mut ZorkCache<'a>,
170
    target: (&'a TargetIdentifier<'a>, &'a TargetModel<'a>),
171
) -> Result<()> {
172
4
    log::info!(
173
        "Generating the linker command line for target: {:?}",
174
        &target.0.name()
175
    );
176

            
177
4
    let target_identifier = target.0;
178
4
    let target_details = target.1;
179

            
180
4
    let linker = &mut cache
181
        .generated_commands
182
        .targets
183
        .get_mut(target_identifier)
184
        .with_context(|| error_messages::TARGET_SOURCES_FAILURE)?
185
        .linker;
186

            
187
4
    let compiler = &model.compiler.cpp_compiler;
188
4
    let out_dir: &Path = model.build.output_dir.as_ref();
189

            
190
4
    let target_output = Argument::from(
191
8
        out_dir
192
4
            .join(compiler.as_ref())
193
4
            .join(target_identifier.name())
194
            .with_extension(constants::BINARY_EXTENSION),
195
4
    );
196

            
197
    // Check if its necessary to change the target output details
198
4
    if linker.target.ne(&target_output) {
199
4
        match compiler {
200
4
            CppCompiler::CLANG | CppCompiler::GCC => linker.target = target_output,
201
            CppCompiler::MSVC => linker.target = Argument::from(format!("/Fe{}", target_output)),
202
        };
203
    }
204

            
205
    // Check if the extra args passed by the user to the linker has changed from previous
206
    // iterations
207
4
    if Iterator::ne(linker.extra_args.iter(), target_details.extra_args.iter()) {
208
        linker.extra_args.clear();
209
        linker
210
            .extra_args
211
            .extend_from_to_argument_slice(&target_details.extra_args);
212
    }
213

            
214
4
    Ok(())
215
4
}
216

            
217
/// The core procedure of the commands generation process.
218
///
219
/// It takes care of generate the [`SourceCommandLine`] for a set of given implementors of [`TranslationUnit`],
220
/// processing them according to the passed [`TranslationUnitKind`] discriminator if the command doesn't exist,
221
/// otherwise, it will handle the need of tracking individually every translation unit in every program iteration
222
/// (while the cache isn't purged by the user) to set their [`TranslationUnitStatus`] flag, which ultimately
223
/// decides on every run if the file must be sent to build to the target [`CppCompiler`]
224
9
fn process_kind_translation_units<'a, T: TranslationUnit<'a>>(
225
    model: &'a ZorkModel<'a>,
226
    cache: &mut ZorkCache<'a>,
227
    translation_units: &'a [T],
228
    for_kind: TranslationUnitKind<'a>,
229
) -> Result<()> {
230
20
    for translation_unit in translation_units.iter() {
231
20
        process_kind_translation_unit(model, cache, translation_unit, &for_kind)?
232
    }
233

            
234
9
    Ok(())
235
9
}
236

            
237
11
fn process_kind_translation_unit<'a, T: TranslationUnit<'a>>(
238
    model: &'a ZorkModel<'a>,
239
    cache: &mut ZorkCache<'a>,
240
    translation_unit: &'a T,
241
    for_kind: &TranslationUnitKind<'a>,
242
) -> Result<()> {
243
11
    let lpe = cache.metadata.last_program_execution;
244

            
245
11
    if let Some(generated_cmd) = cache.get_cmd_for_translation_unit_kind(translation_unit, for_kind)
246
    {
247
        let build_translation_unit =
248
            helpers::determine_translation_unit_status(&lpe, generated_cmd);
249

            
250
        if build_translation_unit.ne(&TranslationUnitStatus::PendingToBuild) {
251
            log::trace!("Source file: {:?} was not modified since the last iteration. No need to rebuilt it again.", &translation_unit.path());
252
        }
253

            
254
        generated_cmd.status = build_translation_unit;
255
    } else {
256
11
        cache.metadata.generate_compilation_database = true;
257
11
        let tu_with_erased_type = translation_unit.as_any();
258

            
259
11
        match &for_kind {
260
            TranslationUnitKind::ModuleInterface => {
261
                let resolved_tu =
262
2
                    transient::Downcast::downcast_ref::<ModuleInterfaceModel>(tu_with_erased_type)
263
                        .with_context(|| helpers::wrong_downcast_msg(translation_unit))?;
264
2
                modules::generate_module_interface_cmd(model, cache, resolved_tu);
265
            }
266
            TranslationUnitKind::ModuleImplementation => {
267
4
                let resolved_tu = transient::Downcast::downcast_ref::<ModuleImplementationModel>(
268
                    tu_with_erased_type,
269
                )
270
                .with_context(|| helpers::wrong_downcast_msg(translation_unit))?;
271
4
                modules::generate_module_implementation_cmd(model, cache, resolved_tu)
272
            }
273
4
            TranslationUnitKind::SourceFile(related_target) => {
274
                let resolved_tu =
275
4
                    transient::Downcast::downcast_ref::<SourceFile>(tu_with_erased_type)
276
                        .with_context(|| helpers::wrong_downcast_msg(translation_unit))?;
277
15
                sources::generate_sources_arguments(model, cache, resolved_tu, related_target)?;
278
            }
279
            TranslationUnitKind::SystemHeader => {
280
                let resolved_tu =
281
1
                    transient::Downcast::downcast_ref::<SystemModule>(tu_with_erased_type)
282
                        .with_context(|| helpers::wrong_downcast_msg(translation_unit))?;
283
1
                modules::generate_sys_module_cmd(model, cache, resolved_tu)
284
            }
285
            _ => (),
286
        }
287
    };
288

            
289
11
    Ok(())
290
11
}
291

            
292
/// Command line arguments generators procedures for C++ standard modules
293
mod modules {
294
    use std::path::{Path, PathBuf};
295

            
296
    use crate::cache::ZorkCache;
297
    use crate::compiler::helpers;
298
    use crate::compiler::helpers::generate_bmi_file_path;
299
    use crate::domain::commands::arguments::{clang_args, msvc_args, Arguments};
300
    use crate::domain::commands::command_lines::SourceCommandLine;
301
    use crate::domain::translation_unit::{TranslationUnit, TranslationUnitStatus};
302
    use crate::project_model::compiler::{CppCompiler, StdLibMode};
303
    use crate::project_model::modules::{
304
        ModuleImplementationModel, ModuleInterfaceModel, SystemModule,
305
    };
306
    use crate::project_model::ZorkModel;
307
    use crate::utils::constants::dir_names;
308

            
309
    /// Generates the expected arguments for precompile the BMIs depending on self
310
2
    pub fn generate_module_interface_cmd<'a>(
311
        model: &'a ZorkModel<'a>,
312
        cache: &mut ZorkCache<'a>,
313
        interface: &'a ModuleInterfaceModel<'a>,
314
    ) {
315
2
        let mut arguments = Arguments::default();
316
2
        let compiler = model.compiler.cpp_compiler;
317
2
        let out_dir: &Path = model.build.output_dir.as_ref();
318

            
319
        // The Path of the generated binary module interface
320
        let binary_module_ifc =
321
2
            helpers::generate_module_output_filename(compiler, out_dir, interface);
322

            
323
2
        match compiler {
324
            CppCompiler::CLANG => {
325
1
                arguments.push("-x");
326
1
                arguments.push("c++-module");
327
1
                arguments.push("--precompile");
328
1
                arguments.extend(clang_args::add_direct_module_interfaces_dependencies(
329
1
                    &interface.dependencies,
330
                    out_dir,
331
1
                    cache.compilers_metadata.clang.major,
332
                ));
333

            
334
                // The generated BMI
335
1
                arguments.push("-o");
336
1
                arguments.push(&binary_module_ifc);
337
            }
338
            CppCompiler::MSVC => {
339
                arguments.push("/ifcOutput");
340
                let implicit_lookup_mius_path = out_dir
341
                    .join(compiler.as_ref())
342
                    .join(dir_names::MODULES)
343
                    .join(dir_names::INTERFACES);
344
                arguments.push(implicit_lookup_mius_path);
345

            
346
                // The output .obj file
347
                arguments.push(format!("/Fo{}", binary_module_ifc.display()));
348

            
349
                if let Some(partition) = &interface.partition {
350
                    if partition.is_internal_partition {
351
                        arguments.push("/internalPartition");
352
                    } else {
353
                        arguments.push("/interface");
354
                    }
355
                } else {
356
                    arguments.push("/interface");
357
                }
358
                arguments.push("/TP");
359
            }
360
            CppCompiler::GCC => {
361
1
                arguments.push("-x");
362
1
                arguments.push("c++");
363
                // The output file
364
1
                arguments.push("-o");
365
1
                arguments.push(&binary_module_ifc);
366
            }
367
        }
368

            
369
        // The input file
370
2
        arguments.push(interface.path());
371

            
372
2
        let cmd_line = SourceCommandLine::new(interface, arguments, binary_module_ifc);
373
2
        cache.generated_commands.modules.interfaces.push(cmd_line);
374
2
    }
375

            
376
    /// Generates the required arguments for compile the implementation module files
377
4
    pub fn generate_module_implementation_cmd<'a>(
378
        model: &'a ZorkModel<'a>,
379
        cache: &mut ZorkCache<'a>,
380
        implementation: &'a ModuleImplementationModel<'a>,
381
    ) {
382
4
        let compiler = model.compiler.cpp_compiler;
383
4
        let out_dir = model.build.output_dir.as_ref();
384

            
385
4
        let mut arguments = Arguments::default();
386

            
387
        // The input file
388
4
        arguments.push(implementation.path());
389
4
        let obj_file_path = helpers::generate_obj_file(compiler, out_dir, implementation);
390

            
391
4
        match compiler {
392
            CppCompiler::CLANG => {
393
                // The resultant object file
394
2
                arguments.push("-o");
395
2
                arguments.push(&obj_file_path);
396

            
397
2
                arguments.extend(clang_args::add_direct_module_interfaces_dependencies(
398
2
                    &implementation.dependencies,
399
                    out_dir,
400
2
                    cache.compilers_metadata.clang.major,
401
                ));
402
            }
403
            CppCompiler::MSVC => {
404
                // The output .obj file
405
                arguments.push(format!("/Fo{}", obj_file_path.display()));
406
            }
407
            CppCompiler::GCC => {
408
                // The output file
409
2
                arguments.push("-o");
410
2
                arguments.push(&obj_file_path);
411
            }
412
        }
413

            
414
4
        let cmd = SourceCommandLine::new(implementation.to_owned(), arguments, obj_file_path);
415
4
        cache.generated_commands.modules.implementations.push(cmd);
416
4
    }
417

            
418
    /// System headers can be imported as modules, but they must be built before being imported.
419
    ///
420
    /// This feature is supported by `GCC` and `Clang`
421
1
    pub(crate) fn generate_sys_module_cmd<'a>(
422
        model: &'a ZorkModel<'a>,
423
        cache: &mut ZorkCache<'a>,
424
        sys_module: &'a SystemModule<'a>,
425
    ) {
426
1
        let sys_module_name = &sys_module.file_stem;
427
1
        let generated_bmi_path = generate_bmi_file_path(
428
1
            &model.build.output_dir,
429
1
            model.compiler.cpp_compiler,
430
1
            sys_module_name,
431
        );
432

            
433
1
        let mut args = Arguments::default();
434
1
        args.push("-x");
435
1
        args.push("c++-system-header");
436
1
        args.push(sys_module_name);
437

            
438
1
        match model.compiler.cpp_compiler {
439
            CppCompiler::CLANG => {
440
                args.push("-o");
441
                args.push(&generated_bmi_path);
442
            }
443
            CppCompiler::GCC => {
444
                // `GCC` system headers built as modules goes directly to their `gcm.cache`
445
1
                args.push("-fmodules-ts");
446
            }
447
            _ => {}
448
        };
449

            
450
1
        let cmd = SourceCommandLine {
451
1
            directory: PathBuf::default(), // NOTE: While we don't implement the lookup of the
452
            // system headers
453
1
            filename: sys_module.to_string(),
454
1
            args,
455
1
            status: TranslationUnitStatus::PendingToBuild,
456
1
            byproduct: generated_bmi_path.into(),
457
        };
458
1
        cache.generated_commands.modules.system_modules.push(cmd);
459
1
    }
460

            
461
    pub(crate) fn generate_modular_cpp_stdlib_args<'a>(
462
        model: &'a ZorkModel<'a>,
463
        cache: &mut ZorkCache<'a>,
464
        stdlib_mode: StdLibMode,
465
    ) {
466
        let cached_stdlib_cmd = cache.get_cpp_stdlib_cmd_by_kind(stdlib_mode);
467
        if cached_stdlib_cmd.is_none()
468
            && cached_stdlib_cmd
469
                .map(|scl| Path::new(&scl.byproduct).exists())
470
                .is_none()
471
        {
472
            let compiler = model.compiler.cpp_compiler;
473
            log::info!(
474
                "Generating the command for build the {:?} {}",
475
                compiler,
476
                stdlib_mode.printable_info()
477
            );
478

            
479
            let scl = match compiler {
480
                CppCompiler::CLANG => clang_args::generate_std_cmd(cache, stdlib_mode),
481
                CppCompiler::MSVC => msvc_args::generate_std_cmd(cache, stdlib_mode),
482
                CppCompiler::GCC => todo!(),
483
            };
484
            cache.set_cpp_stdlib_cmd_by_kind(stdlib_mode, Some(scl));
485
        }
486
    }
487
}
488

            
489
/// Specific operations over source files
490
mod sources {
491
    use crate::cache::ZorkCache;
492
    use crate::domain::commands::arguments::Arguments;
493
    use crate::domain::commands::command_lines::SourceCommandLine;
494
    use crate::domain::target::TargetIdentifier;
495
    use crate::domain::translation_unit::TranslationUnit;
496
    use crate::project_model::sourceset::SourceFile;
497
    use crate::project_model::{compiler::CppCompiler, ZorkModel};
498
    use crate::utils::constants::error_messages;
499
    use color_eyre::eyre::{ContextCompat, Result};
500

            
501
    use super::helpers;
502

            
503
    /// Generates the command line arguments for non-module source files
504
4
    pub fn generate_sources_arguments<'a>(
505
        model: &'a ZorkModel<'a>,
506
        cache: &mut ZorkCache<'a>,
507
        source: &'a SourceFile<'a>,
508
        target_identifier: &TargetIdentifier<'a>,
509
    ) -> Result<()> {
510
4
        let compiler = model.compiler.cpp_compiler;
511
4
        let out_dir = model.build.output_dir.as_ref();
512

            
513
4
        let mut arguments = Arguments::default();
514

            
515
4
        let obj_file = helpers::generate_obj_file(compiler, out_dir, source);
516
4
        match compiler {
517
            CppCompiler::CLANG | CppCompiler::GCC => {
518
4
                arguments.push("-o");
519
4
                arguments.push(&obj_file);
520
            }
521
            CppCompiler::MSVC => arguments.push(format!("/Fo{}", obj_file.display())),
522
        }
523
4
        arguments.push(source.path());
524

            
525
4
        let command_line = SourceCommandLine::new(source, arguments, obj_file);
526
4
        cache
527
            .generated_commands
528
            .targets
529
            .get_mut(target_identifier)
530
            .with_context(|| {
531
                format!(
532
                    "{}: {:?}",
533
                    error_messages::TARGET_ENTRY_NOT_FOUND,
534
                    target_identifier
535
                )
536
            })?
537
            .sources
538
4
            .push(command_line);
539

            
540
4
        Ok(())
541
4
    }
542
}
543

            
544
/// Helpers for reduce the cyclomatic complexity of generating command lines, arguments
545
/// and in other cases, paths depending on what kind of [`TranslationUnitKind`] we are
546
/// processing
547
///
548
/// This module is actually public(crate) reexported since we need to
549
pub(crate) mod helpers {
550
    use super::*;
551
    use crate::domain::commands::command_lines::SourceCommandLine;
552
    use crate::domain::translation_unit::TranslationUnitStatus;
553
    use crate::utils::constants::dir_names;
554
    use chrono::{DateTime, Utc};
555
    use std::path::PathBuf;
556

            
557
    /// Creates the path for a prebuilt module interface, based on the default expected
558
    /// extension for BMI's given a compiler
559
2
    pub(crate) fn generate_module_output_filename(
560
        compiler: CppCompiler,
561
        out_dir: &Path,
562
        interface: &ModuleInterfaceModel,
563
    ) -> PathBuf {
564
2
        let mut module_filename = String::new();
565
2
        if let Some(partition) = &interface.partition {
566
            module_filename.push_str(&partition.module);
567
            module_filename.push('-');
568
            if !partition.partition_name.is_empty() {
569
                module_filename.push_str(&partition.partition_name)
570
            } else {
571
                module_filename.push_str(interface.file_stem())
572
            }
573
        } else {
574
2
            module_filename.push_str(&interface.module_name)
575
        }
576

            
577
2
        generate_bmi_file_path(out_dir, compiler, &module_filename)
578
2
    }
579

            
580
    /// Generates the [`PathBuf`] of the resultant binary module interface file of a C++ module interface
581
3
    pub(crate) fn generate_bmi_file_path(
582
        out_dir: &Path,
583
        compiler: CppCompiler,
584
        module_name: &str,
585
    ) -> PathBuf {
586
3
        let base = out_dir.join(compiler.as_ref());
587
3
        let (intermediate, extension) = if compiler.eq(&CppCompiler::MSVC) {
588
            let intermediate = base.join(dir_names::OBJECT_FILES);
589
            (intermediate, compiler.get_obj_file_extension())
590
        } else {
591
3
            let intermediate = base.join(dir_names::MODULES).join(dir_names::INTERFACES);
592
3
            (intermediate, compiler.get_typical_bmi_extension())
593
        };
594

            
595
6
        base.join(intermediate)
596
3
            .join(format!("{module_name}.{}", extension))
597
3
    }
598

            
599
    /// Generates the [`PathBuf`] of the resultant `.obj` file of a [`TranslationUnit`] where the
600
    /// `.obj` file is one of the byproducts of the build process (and the one that will be sent
601
    /// to the linker)
602
8
    pub(crate) fn generate_obj_file<'a, T: TranslationUnit<'a>>(
603
        compiler: CppCompiler,
604
        out_dir: &Path,
605
        implementation: &T,
606
    ) -> PathBuf {
607
24
        out_dir
608
8
            .join(compiler.as_ref())
609
            .join(dir_names::OBJECT_FILES)
610
8
            .join::<&str>(implementation.file_stem())
611
8
            .with_extension(compiler.get_obj_file_extension())
612
8
    }
613

            
614
    /// Template factory function to call the inspectors of the status of a file on the fs that
615
    /// is represented within `Zork++` as some kind of [`TranslationUnit`] and the status flags
616
    /// tracked on the entities like [`SourceCommandLine::status`] and others from the [`ZorkCache`]
617
    /// as well to determine when a concrete user declared file must be sent to the compiler in order
618
    /// to be built, or we can skip it
619
    ///
620
    /// *returns: <[`TranslationUnitStatus`]>* - The state that should be set to the current
621
    /// [`SourceCommandLine`] in order to be handled
622
    pub(crate) fn determine_translation_unit_status(
623
        last_process_execution: &DateTime<Utc>,
624
        cached_source_cmd: &SourceCommandLine,
625
    ) -> TranslationUnitStatus {
626
        // In case the user deleted the translation unit from the fs but not from the Zork++ cfg file
627
        let translation_unit_has_been_deleted = !cached_source_cmd.path().exists();
628
        if translation_unit_has_been_deleted {
629
            return TranslationUnitStatus::ToDelete;
630
        }
631

            
632
        // In case the file suffered changes
633
        let need_to_build =
634
            translation_unit_has_changes_on_fs(last_process_execution, cached_source_cmd);
635

            
636
        if need_to_build {
637
            TranslationUnitStatus::PendingToBuild
638
        } else {
639
            compute_translation_unit_status(cached_source_cmd)
640
        }
641
    }
642

            
643
    /// Checks whenever a [`TranslationUnit`] has been modified on the filesystem and its changes
644
    /// was made *after* the last time that `Zork++` made a run.
645
    ///
646
    /// *returns: <bool>* - true if the target [`TranslationUnit`] has been modified after the last
647
    /// iteration, false otherwise
648
    pub fn translation_unit_has_changes_on_fs(
649
        last_process_execution: &DateTime<Utc>,
650
        cached_source_cmd: &SourceCommandLine,
651
    ) -> bool {
652
        let file = cached_source_cmd.path();
653
        let file_metadata = file.metadata();
654

            
655
        // If exists and was successful, let's see if has been modified after the program last iteration
656
        match file_metadata {
657
            Ok(m) => match m.modified() {
658
                Ok(modified) => DateTime::<Utc>::from(modified) > *last_process_execution,
659
                Err(e) => {
660
                    log::error!("An error happened trying to get the last time that the {file:?} was modified. Processing it anyway because {e:?}");
661
                    true
662
                }
663
            },
664
            Err(e) => {
665
                log::error!("An error happened trying to retrieve the metadata of {file:?}. Processing it anyway because {e:?}");
666
                true
667
            }
668
        }
669
    }
670

            
671
    /// Determines which kind of [`TranslationUnitStatus`] variant must a [`SourceCommandLine`]
672
    /// have on every process regarding specific checks and conditions before and after sent to
673
    /// build
674
    pub(crate) fn compute_translation_unit_status(
675
        scl: &SourceCommandLine,
676
    ) -> TranslationUnitStatus {
677
        match scl.status {
678
            TranslationUnitStatus::Success | TranslationUnitStatus::Cached => {
679
                TranslationUnitStatus::Cached
680
            }
681
            _ => TranslationUnitStatus::PendingToBuild,
682
        }
683
    }
684

            
685
    pub(crate) fn wrong_downcast_msg<'a, T: TranslationUnit<'a>>(translation_unit: &T) -> String {
686
        format!(
687
            "{}: {:?}",
688
            error_messages::WRONG_DOWNCAST_FOR,
689
            translation_unit.path()
690
        )
691
    }
692
}