1
//! Contains helpers and data structures to be processed in a nice and neat way the commands generated to be executed
2
//! by Zork++
3

            
4
use std::ffi::OsStr;
5
use std::{path::Path, process::ExitStatus};
6

            
7
use crate::cache::EnvVars;
8
use crate::domain::commands::arguments::Argument;
9
use crate::domain::commands::command_lines::ModulesCommands;
10
use crate::domain::flyweight_data::FlyweightData;
11
use crate::domain::target::{Target, TargetIdentifier};
12
use crate::domain::translation_unit::TranslationUnitStatus;
13
use crate::utils::constants::error_messages;
14
use crate::{
15
    project_model::{compiler::CppCompiler, ZorkModel},
16
    utils::constants,
17
};
18
use color_eyre::eyre::ContextCompat;
19
use color_eyre::{eyre::Context, Report, Result};
20
use indexmap::IndexMap;
21

            
22
2
pub fn run_modules_generated_commands(
23
    program_data: &ZorkModel<'_>,
24
    flyweight_data: &FlyweightData,
25
    modules_generated_commands: &mut ModulesCommands<'_>,
26
) -> Result<()> {
27
2
    log::info!("Proceeding to execute the generated modules commands...");
28

            
29
4
    helpers::process_std_modules_commands(
30
        program_data,
31
        flyweight_data,
32
        modules_generated_commands,
33
    )?;
34
2
    helpers::process_user_modules_commands(program_data, flyweight_data, modules_generated_commands)
35
2
}
36

            
37
2
pub fn run_targets_generated_commands(
38
    program_data: &ZorkModel<'_>,
39
    flyweight_data: &FlyweightData,
40
    targets: &mut IndexMap<TargetIdentifier, Target>,
41
    modules: &ModulesCommands<'_>,
42
) -> Result<()> {
43
2
    log::info!("Proceeding to execute the generated commands...");
44

            
45
6
    let shared_args = flyweight_data
46
        .general_args
47
        .iter()
48
2
        .chain(flyweight_data.shared_args.iter())
49
2
        .chain(flyweight_data.std_references.iter());
50

            
51
    // Process the user declared targets
52
6
    for (target_identifier, target_data) in targets
53
        .iter_mut()
54
4
        .filter(|(_, target_data)| target_data.enabled_for_current_program_iteration)
55
    {
56
4
        let env_vars = &flyweight_data.env_vars;
57

            
58
4
        log::info!(
59
            "Executing the generated commands of the sources declared for target: {:?}",
60
            target_identifier.name()
61
        );
62

            
63
4
        let extra_args = &program_data
64
            .targets
65
            .get(target_identifier)
66
            .with_context(|| error_messages::TARGET_ENTRY_NOT_FOUND)?
67
            .extra_args;
68

            
69
12
        let target_shared_args = shared_args
70
            .clone()
71
4
            .chain(flyweight_data.compile_but_dont_link.iter())
72
4
            .chain(extra_args.as_slice())
73
            .collect();
74

            
75
        // Send to build to the compiler the sources declared for the current iteration target
76
8
        for source in target_data
77
            .sources
78
            .iter_mut()
79
4
            .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild))
80
        {
81
6
            helpers::execute_source_command_line(
82
                program_data,
83
                &target_shared_args,
84
                env_vars,
85
                source,
86
            )?;
87
        }
88

            
89
4
        log::info!(
90
            "Executing the linker command line for target: {:?}",
91
            target_identifier.name()
92
        );
93

            
94
        // Invoke the linker to generate the final product for the current iteration target
95
4
        helpers::execute_linker_command_line(
96
            program_data,
97
            flyweight_data,
98
            modules,
99
            env_vars,
100
            target_data,
101
        )?;
102
4
    }
103

            
104
2
    Ok(())
105
2
}
106

            
107
/// Executes a new [`std::process::Command`] to run the generated binary
108
/// after the build process in the specified shell
109
4
pub fn autorun_generated_binary(
110
    compiler: &CppCompiler,
111
    output_dir: &Path,
112
    executable_name: &str,
113
) -> Result<()> {
114
4
    let args = &[Argument::from(
115
4
        output_dir
116
4
            .join(compiler.as_ref())
117
            .join(executable_name)
118
            .with_extension(constants::BINARY_EXTENSION),
119
4
    )];
120

            
121
4
    log::info!(
122
        "[{compiler}] - Executing the generated binary => {:?}",
123
        args.join(" ")
124
    );
125

            
126
4
    std::process::Command::new(Argument::from(
127
4
        output_dir.join(compiler.as_ref()).join(executable_name),
128
    ))
129
    .spawn()?
130
    .wait()
131
4
    .with_context(|| format!("[{compiler}] - Command {:?} failed!", args.join(" ")))?;
132

            
133
4
    Ok(())
134
4
}
135

            
136
/// Executes a new [`std::process::Command`] configured according the chosen
137
/// compiler and the current operating system
138
15
fn execute_command<T, S>(
139
    model: &ZorkModel,
140
    arguments: T,
141
    env_vars: &EnvVars,
142
) -> Result<ExitStatus, Report>
143
where
144
    T: IntoIterator<Item = S> + std::fmt::Display + std::marker::Copy,
145
    S: AsRef<OsStr>,
146
{
147
15
    let compiler = model.compiler.cpp_compiler;
148
15
    log::trace!(
149
        "[{compiler}] - Executing command => {:?}",
150
        format!("{} {}", compiler.get_driver(&model.compiler), arguments)
151
    );
152

            
153
15
    let driver = compiler.get_driver(&model.compiler);
154
15
    let os_driver = OsStr::new(driver.as_ref());
155
30
    std::process::Command::new(os_driver)
156
15
        .args(arguments)
157
        .envs(env_vars)
158
        .spawn()?
159
        .wait()
160
        .with_context(|| format!("[{compiler}] - Command {} failed!", arguments))
161
15
}
162

            
163
mod helpers {
164
    use crate::cache::EnvVars;
165
    use crate::cli::output::executors::execute_command;
166
    use crate::domain::commands::arguments::Arguments;
167
    use crate::domain::commands::command_lines::{ModulesCommands, SourceCommandLine};
168
    use crate::domain::flyweight_data::FlyweightData;
169
    use crate::domain::target::Target;
170
    use crate::domain::translation_unit::TranslationUnitStatus;
171
    use crate::project_model::compiler::CppCompiler;
172
    use crate::project_model::ZorkModel;
173

            
174
    use color_eyre::eyre::{eyre, Result};
175
    use std::process::ExitStatus;
176

            
177
4
    pub(crate) fn execute_source_command_line(
178
        program_data: &ZorkModel<'_>,
179
        shared_args: &Arguments<'_>,
180
        env_vars: &EnvVars,
181
        source: &mut SourceCommandLine<'_>,
182
    ) -> Result<()> {
183
8
        let args = shared_args
184
            .as_slice()
185
            .iter()
186
4
            .chain(source.args.as_slice().iter())
187
            .collect::<Arguments>();
188

            
189
4
        let r = execute_command(program_data, &args, env_vars);
190
4
        source.status = TranslationUnitStatus::from(&r);
191

            
192
4
        if let Err(e) = r {
193
            return Err(e);
194
4
        } else if !r.as_ref().unwrap().success() {
195
            let err = eyre!(
196
                "Ending the program, because the build of: {:?} failed",
197
                source.filename
198
            );
199
            return Err(err);
200
        }
201

            
202
4
        Ok(())
203
4
    }
204

            
205
4
    pub(crate) fn execute_linker_command_line(
206
        program_data: &ZorkModel,
207
        flyweight_data: &FlyweightData,
208
        modules: &ModulesCommands<'_>,
209
        env_vars: &EnvVars,
210
        target_data: &mut Target,
211
    ) -> Result<ExitStatus> {
212
4
        let compiler = program_data.compiler.cpp_compiler;
213
4
        let target_output = target_data.linker.get_target_output_for(compiler);
214

            
215
8
        let linker_sources_byproducts = target_data.sources.iter().map(|scl| &scl.byproduct);
216
20
        let modules_byproducts = modules
217
            .cpp_stdlib
218
            .as_slice()
219
            .iter()
220
4
            .chain(modules.c_compat_stdlib.iter())
221
4
            .chain(modules.interfaces.iter())
222
4
            .chain(modules.implementations.iter())
223
4
            .chain(if compiler.eq(&CppCompiler::CLANG) {
224
                // NOTE: gcc handles them itself with the
225
                // gcm.cache. MSVC doesn't need them and
226
                // this should be removed since when
227
                // import std is impl for the big 3
228
2
                modules.system_modules.iter()
229
            } else {
230
2
                [].iter()
231
            })
232
12
            .map(|scl| &scl.byproduct);
233

            
234
24
        let args = flyweight_data
235
            .general_args
236
            .iter()
237
4
            .chain(flyweight_data.shared_args.iter())
238
4
            .chain(flyweight_data.std_references.iter())
239
4
            .chain(target_data.linker.args.iter())
240
4
            .chain(target_data.linker.extra_args.iter())
241
4
            .chain(target_output.iter())
242
            .chain(modules_byproducts)
243
            .chain(linker_sources_byproducts)
244
            .collect::<Arguments>();
245

            
246
4
        let r = execute_command(program_data, &args, env_vars);
247
4
        target_data.linker.execution_result = TranslationUnitStatus::from(&r);
248

            
249
4
        if let Err(e) = r {
250
            return Err(e);
251
4
        } else if !r.as_ref().unwrap().success() {
252
            return Err(eyre!(
253
                "Ending the program, because the linker command line execution failed",
254
            ));
255
        }
256

            
257
4
        r
258
4
    }
259

            
260
2
    pub(crate) fn process_std_modules_commands(
261
        program_data: &ZorkModel<'_>,
262
        flyweight_data: &FlyweightData,
263
        generated_commands: &mut ModulesCommands<'_>,
264
    ) -> Result<()> {
265
2
        let std_libs_commands: Vec<&mut SourceCommandLine> =
266
2
            get_std_modules_commands(generated_commands);
267

            
268
2
        if std_libs_commands.is_empty() {
269
2
            return Ok(());
270
        }
271

            
272
        for std_lib in std_libs_commands {
273
            // Join the concrete args of any translation unit with the ones held in the flyweights
274
            let translation_unit_cmd_args = flyweight_data
275
                .general_args
276
                .iter()
277
                .chain(flyweight_data.shared_args.iter())
278
                .chain(flyweight_data.compile_but_dont_link.iter()) // NOTE: non-required in Clang
279
                .chain(std_lib.args.iter())
280
                .collect::<Arguments>();
281

            
282
            let r = execute_command(
283
                program_data,
284
                &translation_unit_cmd_args,
285
                &flyweight_data.env_vars,
286
            );
287
            std_lib.status = TranslationUnitStatus::from(&r);
288

            
289
            if let Err(e) = r {
290
                return Err(e);
291
            } else if !r.as_ref().unwrap().success() {
292
                let err = eyre!(
293
                    "Ending the program, because the build of: {:?} failed",
294
                    std_lib.filename
295
                );
296
                return Err(err);
297
            }
298
        }
299

            
300
        Ok(())
301
2
    }
302

            
303
2
    pub(crate) fn process_user_modules_commands(
304
        program_data: &ZorkModel<'_>,
305
        flyweight_data: &FlyweightData,
306
        generated_commands: &mut ModulesCommands<'_>,
307
    ) -> Result<()> {
308
2
        let translation_units_commands: Vec<&mut SourceCommandLine> =
309
2
            get_user_modules_translation_units_commands(generated_commands);
310

            
311
2
        if translation_units_commands.is_empty() {
312
            log::debug!(
313
                "No user or system modules to process, build or rebuild in this iteration."
314
            );
315
            return Ok(());
316
        }
317

            
318
9
        for translation_unit_cmd in translation_units_commands {
319
            // Join the concrete args of any translation unit with the ones held in the flyweights
320
35
            let translation_unit_cmd_args = flyweight_data
321
                .general_args
322
                .iter()
323
7
                .chain(flyweight_data.shared_args.iter())
324
7
                .chain(flyweight_data.std_references.iter())
325
7
                .chain(flyweight_data.compile_but_dont_link.iter())
326
7
                .chain(translation_unit_cmd.args.iter())
327
                .collect::<Arguments>();
328

            
329
7
            let r = execute_command(
330
                program_data,
331
                &translation_unit_cmd_args,
332
7
                &flyweight_data.env_vars,
333
7
            );
334
7
            translation_unit_cmd.status = TranslationUnitStatus::from(&r);
335

            
336
7
            if let Err(e) = r {
337
                return Err(e);
338
7
            } else if !r.as_ref().unwrap().success() {
339
                let err = eyre!(
340
                    "Ending the program, because the build of: {:?} failed",
341
                    translation_unit_cmd.filename
342
                );
343
                return Err(err);
344
            }
345
9
        }
346

            
347
2
        Ok(())
348
2
    }
349

            
350
2
    pub(crate) fn get_std_modules_commands<'a, 'b>(
351
        generated_commands: &'b mut ModulesCommands<'a>,
352
    ) -> Vec<&'b mut SourceCommandLine<'a>> {
353
2
        let cpp_stdlib = generated_commands.cpp_stdlib.as_mut_slice().iter_mut();
354
2
        let c_compat_stdlib = generated_commands.c_compat_stdlib.as_mut_slice().iter_mut();
355

            
356
2
        cpp_stdlib
357
            .chain(c_compat_stdlib)
358
            .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild))
359
            .collect::<Vec<&mut SourceCommandLine>>()
360
2
    }
361

            
362
2
    pub(crate) fn get_user_modules_translation_units_commands<'a, 'b>(
363
        generated_commands: &'b mut ModulesCommands<'a>,
364
    ) -> Vec<&'b mut SourceCommandLine<'a>> {
365
2
        let system_modules = generated_commands.system_modules.as_mut_slice().iter_mut();
366
2
        let interfaces = generated_commands.interfaces.as_mut_slice().iter_mut();
367
2
        let implementations = generated_commands.implementations.as_mut_slice().iter_mut();
368

            
369
2
        system_modules
370
            .chain(interfaces)
371
            .chain(implementations)
372
7
            .filter(|scl| scl.status.eq(&TranslationUnitStatus::PendingToBuild))
373
            .collect::<Vec<&mut SourceCommandLine>>()
374
2
    }
375
}