cu29_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use std::fs::read_to_string;
4use syn::meta::parser;
5use syn::Fields::{Named, Unnamed};
6use syn::{
7    parse_macro_input, parse_quote, parse_str, Field, Fields, ItemImpl, ItemStruct, LitStr, Type,
8    TypeTuple,
9};
10
11#[cfg(feature = "macro_debug")]
12use crate::format::rustfmt_generated_code;
13use crate::utils::config_id_to_enum;
14use cu29_runtime::config::CuConfig;
15use cu29_runtime::config::{read_configuration, CuGraph};
16use cu29_runtime::curuntime::{
17    compute_runtime_plan, find_task_type_for_id, CuExecutionLoop, CuExecutionUnit, CuTaskType,
18};
19use cu29_traits::CuResult;
20use proc_macro2::{Ident, Span};
21
22mod format;
23mod utils;
24
25// TODO: this needs to be determined when the runtime is sizing itself.
26const DEFAULT_CLNB: usize = 10;
27
28#[inline]
29fn int2sliceindex(i: u32) -> syn::Index {
30    syn::Index::from(i as usize)
31}
32
33#[inline(always)]
34fn return_error(msg: String) -> TokenStream {
35    syn::Error::new(Span::call_site(), msg)
36        .to_compile_error()
37        .into()
38}
39
40/// Generates the CopperList content type from a config.
41/// gen_cumsgs!("path/to/config.toml")
42/// It will create a new type called CuStampedDataSet you can pass to the log reader for decoding:
43#[proc_macro]
44pub fn gen_cumsgs(config_path_lit: TokenStream) -> TokenStream {
45    let config = parse_macro_input!(config_path_lit as LitStr).value();
46    if !std::path::Path::new(&config_full_path(&config)).exists() {
47        return return_error(format!(
48            "The configuration file `{config}` does not exist. Please provide a valid path."
49        ));
50    }
51    #[cfg(feature = "macro_debug")]
52    eprintln!("[gen culist support with {config:?}]");
53    let cuconfig = match read_config(&config) {
54        Ok(cuconfig) => cuconfig,
55        Err(e) => return return_error(e.to_string()),
56    };
57    let graph = cuconfig
58        .get_graph(None) // FIXME(gbin): Multimission
59        .expect("Could not find the specified mission for gen_cumsgs");
60    let runtime_plan: CuExecutionLoop = match compute_runtime_plan(graph) {
61        Ok(plan) => plan,
62        Err(e) => return return_error(format!("Could not compute runtime plan: {e}")),
63    };
64
65    // Give a name compatible with a struct to match the task ids to their output in the CuStampedDataSet tuple.
66    let all_tasks_member_ids: Vec<String> = graph
67        .get_all_nodes()
68        .iter()
69        .map(|(_, node)| utils::config_id_to_struct_member(node.get_id().as_str()))
70        .collect();
71
72    // All accesses are linear on the culist but the id of the tasks is random (determined by the Ron declaration order).
73    // This records the task ids in call order.
74    let taskid_order: Vec<usize> = runtime_plan
75        .steps
76        .iter()
77        .filter_map(|unit| match unit {
78            CuExecutionUnit::Step(step) => Some(step.node_id as usize),
79            _ => None,
80        })
81        .collect();
82
83    #[cfg(feature = "macro_debug")]
84    eprintln!(
85        "[The CuStampedDataSet matching tasks ids are {:?}]",
86        taskid_order
87            .iter()
88            .map(|i| all_tasks_member_ids[*i].clone())
89            .collect::<Vec<_>>()
90    );
91
92    let support = gen_culist_support(&runtime_plan, &taskid_order, &all_tasks_member_ids);
93
94    let with_uses = quote! {
95        mod cumsgs {
96            use cu29::bincode::Encode;
97            use cu29::bincode::enc::Encoder;
98            use cu29::bincode::error::EncodeError;
99            use cu29::bincode::Decode;
100            use cu29::bincode::de::Decoder;
101            use cu29::bincode::error::DecodeError;
102            use cu29::copperlist::CopperList;
103            use cu29::prelude::CuStampedData;
104            use cu29::prelude::ErasedCuStampedData;
105            use cu29::prelude::ErasedCuStampedDataSet;
106            use cu29::prelude::MatchingTasks;
107            use cu29::prelude::Serialize;
108            use cu29::prelude::CuMsg;
109            use cu29::prelude::CuMsgMetadata;
110            use cu29::prelude::CuListZeroedInit;
111            use cu29::prelude::CuCompactString;
112            #support
113        }
114        use cumsgs::CuStampedDataSet;
115        type CuMsgs=CuStampedDataSet;
116    };
117    with_uses.into()
118}
119
120/// Build the inner support of the copper list.
121fn gen_culist_support(
122    runtime_plan: &CuExecutionLoop,
123    taskid_call_order: &[usize],
124    all_tasks_as_struct_member_name: &Vec<String>,
125) -> proc_macro2::TokenStream {
126    #[cfg(feature = "macro_debug")]
127    eprintln!("[Extract msgs types]");
128    let all_msgs_types_in_culist_order = extract_msg_types(runtime_plan);
129
130    let culist_size = all_msgs_types_in_culist_order.len();
131    let task_indices: Vec<_> = taskid_call_order
132        .iter()
133        .map(|i| syn::Index::from(*i))
134        .collect();
135
136    #[cfg(feature = "macro_debug")]
137    eprintln!("[build the copperlist struct]");
138    let msgs_types_tuple: TypeTuple = build_culist_tuple(&all_msgs_types_in_culist_order);
139
140    #[cfg(feature = "macro_debug")]
141    eprintln!("[build the copperlist tuple bincode support]");
142    let msgs_types_tuple_encode = build_culist_tuple_encode(&all_msgs_types_in_culist_order);
143    let msgs_types_tuple_decode = build_culist_tuple_decode(&all_msgs_types_in_culist_order);
144
145    #[cfg(feature = "macro_debug")]
146    eprintln!("[build the copperlist tuple debug support]");
147    let msgs_types_tuple_debug = build_culist_tuple_debug(&all_msgs_types_in_culist_order);
148
149    #[cfg(feature = "macro_debug")]
150    eprintln!("[build the copperlist tuple serialize support]");
151    let msgs_types_tuple_serialize = build_culist_tuple_serialize(&all_msgs_types_in_culist_order);
152
153    #[cfg(feature = "macro_debug")]
154    eprintln!("[build the default tuple support]");
155    let msgs_types_tuple_default = build_culist_tuple_default(&all_msgs_types_in_culist_order);
156
157    #[cfg(feature = "macro_debug")]
158    eprintln!("[build erasedcumsgs]");
159
160    let erasedmsg_trait_impl = build_culist_erasedcumsgs(&all_msgs_types_in_culist_order);
161
162    let collect_metadata_function = quote! {
163        pub fn collect_metadata<'a>(culist: &'a CuList) -> [&'a CuMsgMetadata; #culist_size] {
164            [#( &culist.msgs.0.#task_indices.metadata, )*]
165        }
166    };
167
168    let methods = itertools::multizip((all_tasks_as_struct_member_name, taskid_call_order)).map(
169        |(name, output_position)| {
170            let fn_name = format_ident!("get_{}_output", name);
171            let payload_type = all_msgs_types_in_culist_order[*output_position].clone();
172            let index = syn::Index::from(*output_position);
173            quote! {
174                #[allow(dead_code)]
175                pub fn #fn_name(&self) -> &CuMsg<#payload_type> {
176                    &self.0.#index
177                }
178            }
179        },
180    );
181
182    // This generates a way to get the metadata of every single message of a culist at low cost
183    quote! {
184        #collect_metadata_function
185
186        pub struct CuStampedDataSet(pub #msgs_types_tuple);
187
188        pub type CuList = CopperList<CuStampedDataSet>;
189
190        impl CuStampedDataSet {
191            #(#methods)*
192
193            #[allow(dead_code)]
194            fn get_tuple(&self) -> &#msgs_types_tuple {
195                &self.0
196            }
197
198            #[allow(dead_code)]
199            fn get_tuple_mut(&mut self) -> &mut #msgs_types_tuple {
200                &mut self.0
201            }
202        }
203
204        impl MatchingTasks for CuStampedDataSet {
205            #[allow(dead_code)]
206            fn get_all_task_ids() -> &'static [&'static str] {
207                &[#(#all_tasks_as_struct_member_name),*]
208            }
209        }
210
211        // Adds the bincode support for the copper list tuple
212        #msgs_types_tuple_encode
213        #msgs_types_tuple_decode
214
215        // Adds the debug support
216        #msgs_types_tuple_debug
217
218        // Adds the serialization support
219        #msgs_types_tuple_serialize
220
221        // Adds the default support
222        #msgs_types_tuple_default
223
224        // Adds the type erased CuStampedDataSet support (to help generic serialized conversions)
225        #erasedmsg_trait_impl
226
227        impl CuListZeroedInit for CuStampedDataSet {
228            fn init_zeroed(&mut self) {
229                #(self.0.#task_indices.metadata.status_txt = CuCompactString::default();)*
230            }
231        }
232    }
233}
234
235fn gen_sim_support(runtime_plan: &CuExecutionLoop) -> proc_macro2::TokenStream {
236    #[cfg(feature = "macro_debug")]
237    eprintln!("[Sim: Build SimEnum]");
238    let plan_enum: Vec<proc_macro2::TokenStream> = runtime_plan
239        .steps
240        .iter()
241        .map(|unit| match unit {
242            CuExecutionUnit::Step(step) => {
243                let enum_entry_name = config_id_to_enum(step.node.get_id().as_str());
244                let enum_ident = Ident::new(&enum_entry_name, Span::call_site());
245                let inputs: Vec<Type> = step
246                    .input_msg_indices_types
247                    .iter()
248                    .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap())
249                    .collect();
250                let output: Option<Type> = step
251                    .output_msg_index_type
252                    .as_ref()
253                    .map(|(_, t)| parse_str::<Type>(format!("CuMsg<{t}>").as_str()).unwrap());
254                let no_output = parse_str::<Type>("CuMsg<()>").unwrap();
255                let output = output.as_ref().unwrap_or(&no_output);
256
257                let inputs_type = if inputs.is_empty() {
258                    quote! { () }
259                } else if inputs.len() == 1 {
260                    let input = inputs.first().unwrap();
261                    quote! { &'a #input }
262                } else {
263                    quote! { &'a (#(&'a #inputs),*) }
264                };
265
266                quote! {
267                    #enum_ident(cu29::simulation::CuTaskCallbackState<#inputs_type, &'a mut #output>)
268                }
269            }
270            CuExecutionUnit::Loop(_) => {
271                todo!("Needs to be implemented")
272            }
273        })
274        .collect();
275    quote! {
276        // not used if sim is not generated but this is ok.
277        #[allow(dead_code)]
278        pub enum SimStep<'a> {
279            #(#plan_enum),*
280        }
281    }
282}
283
284/// Adds #[copper_runtime(config = "path", sim_mode = false/true)] to your application struct to generate the runtime.
285/// if sim_mode is omitted, it is set to false.
286/// This will add a "runtime" field to your struct and implement the "new" and "run" methods.
287#[proc_macro_attribute]
288pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
289    #[cfg(feature = "macro_debug")]
290    eprintln!("[entry]");
291    let mut application_struct = parse_macro_input!(input as ItemStruct);
292
293    let application_name = &application_struct.ident;
294    let builder_name = format_ident!("{}Builder", application_name);
295
296    let mut config_file: Option<LitStr> = None;
297    let mut sim_mode = false;
298
299    // Custom parser for the attribute arguments
300    let attribute_config_parser = parser(|meta| {
301        if meta.path.is_ident("config") {
302            config_file = Some(meta.value()?.parse()?);
303            Ok(())
304        } else if meta.path.is_ident("sim_mode") {
305            // Check if `sim_mode` has an explicit value (true/false)
306            if meta.input.peek(syn::Token![=]) {
307                meta.input.parse::<syn::Token![=]>()?;
308                let value: syn::LitBool = meta.input.parse()?;
309                sim_mode = value.value();
310                Ok(())
311            } else {
312                // If no value is provided, default to true
313                sim_mode = true;
314                Ok(())
315            }
316        } else {
317            Err(meta.error("unsupported property"))
318        }
319    });
320
321    #[cfg(feature = "macro_debug")]
322    eprintln!("[parse]");
323    // Parse the provided args with the custom parser
324    parse_macro_input!(args with attribute_config_parser);
325
326    // Check if the config file was provided
327    let config_file = match config_file {
328        Some(file) => file.value(),
329        None => {
330            return return_error(
331                "Expected config file attribute like #[CopperRuntime(config = \"path\")]"
332                    .to_string(),
333            )
334        }
335    };
336
337    if !std::path::Path::new(&config_full_path(&config_file)).exists() {
338        return return_error(format!(
339            "The configuration file `{config_file}` does not exist. Please provide a valid path."
340        ));
341    }
342
343    let copper_config = match read_config(&config_file) {
344        Ok(cuconfig) => cuconfig,
345        Err(e) => return return_error(e.to_string()),
346    };
347    let copper_config_content = match read_to_string(config_full_path(config_file.as_str())) {
348        Ok(ok) => ok,
349        Err(e) => return return_error(format!("Could not read the config file (should not happen because we just succeeded just before). {e}"))
350    };
351
352    #[cfg(feature = "macro_debug")]
353    eprintln!("[build monitor type]");
354    let monitor_type = if let Some(monitor_config) = copper_config.get_monitor_config() {
355        let monitor_type = parse_str::<Type>(monitor_config.get_type())
356            .expect("Could not transform the monitor type name into a Rust type.");
357        quote! { #monitor_type }
358    } else {
359        quote! { NoMonitor }
360    };
361
362    // This is common for all the mission as it will be inserted in the respective modules with their local CuTasks, CuStampedDataSet etc...
363    #[cfg(feature = "macro_debug")]
364    eprintln!("[build runtime field]");
365    // add that to a new field
366    let runtime_field: Field = if sim_mode {
367        parse_quote! {
368            copper_runtime: cu29::curuntime::CuRuntime<CuSimTasks, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
369        }
370    } else {
371        parse_quote! {
372            copper_runtime: cu29::curuntime::CuRuntime<CuTasks, CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>
373        }
374    };
375
376    #[cfg(feature = "macro_debug")]
377    eprintln!("[match struct anonymity]");
378    match &mut application_struct.fields {
379        Named(fields_named) => {
380            fields_named.named.push(runtime_field);
381        }
382        Unnamed(fields_unnamed) => {
383            fields_unnamed.unnamed.push(runtime_field);
384        }
385        Fields::Unit => {
386            panic!("This struct is a unit struct, it should have named or unnamed fields. use struct Something {{}} and not struct Something;")
387        }
388    };
389
390    let all_missions = copper_config.graphs.get_all_missions_graphs();
391    let mut all_missions_tokens = Vec::<proc_macro2::TokenStream>::new();
392    for (mission, graph) in &all_missions {
393        let mission_mod = parse_str::<Ident>(mission.as_str())
394            .expect("Could not make an identifier of the mission name");
395
396        #[cfg(feature = "macro_debug")]
397        eprintln!("[extract tasks ids & types]");
398        let task_specs = CuTaskSpecSet::from_graph(graph);
399
400        #[cfg(feature = "macro_debug")]
401        eprintln!("[runtime plan for mission {mission}]");
402        let runtime_plan: CuExecutionLoop = match compute_runtime_plan(graph) {
403            Ok(plan) => plan,
404            Err(e) => return return_error(format!("Could not compute runtime plan: {e}")),
405        };
406
407        #[cfg(feature = "macro_debug")]
408        eprintln!("{runtime_plan:?}");
409
410        let all_sim_tasks_types: Vec<Type> = task_specs.ids
411            .iter()
412            .zip(&task_specs.cutypes)
413            .zip(&task_specs.sim_task_types)
414            .zip(&task_specs.background_flags)
415            .map(|(((task_id, cutype), stype), background)| {
416                match cutype {
417                    CuTaskType::Source => {
418                        if *background {
419                            panic!("CuSrcTask {task_id} cannot be a background task, it should be a regular task.");
420                        }
421                        let msg_type = graph
422                            .get_node_output_msg_type(task_id.as_str())
423                            .unwrap_or_else(|| panic!("CuSrcTask {task_id} should have an outgoing connection with a valid output msg type"));
424                        let sim_task_name = format!("cu29::simulation::CuSimSrcTask<{msg_type}>");
425                        parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
426                    }
427                    CuTaskType::Regular => {
428                        // TODO: wrap that correctly in a background task if background is true.
429                        stype.clone()
430                    },
431                    CuTaskType::Sink => {
432                        if *background {
433                            panic!("CuSinkTask {task_id} cannot be a background task, it should be a regular task.");
434                        }
435                        let msg_type = graph
436                            .get_node_input_msg_type(task_id.as_str())
437                            .unwrap_or_else(|| panic!("CuSinkTask {task_id} should have an incoming connection with a valid input msg type"));
438                        let sim_task_name = format!("cu29::simulation::CuSimSinkTask<{msg_type}>");
439                        parse_str(sim_task_name.as_str()).unwrap_or_else(|_| panic!("Could not build the placeholder for simulation: {sim_task_name}"))
440                    }
441                }
442    })
443    .collect();
444
445        #[cfg(feature = "macro_debug")]
446        eprintln!("[build task tuples]");
447
448        let task_types = &task_specs.task_types;
449        // Build the tuple of all those types
450        // note the extraneous, at the end is to make the tuple work even if this is only one element
451        let task_types_tuple: TypeTuple = parse_quote! {
452            (#(#task_types),*,)
453        };
454
455        let task_types_tuple_sim: TypeTuple = parse_quote! {
456            (#(#all_sim_tasks_types),*,)
457        };
458
459        #[cfg(feature = "macro_debug")]
460        eprintln!("[gen instances]");
461        // FIXME: implement here the threadpool emulation.
462        let task_sim_instances_init_code = all_sim_tasks_types.iter().enumerate().map(|(index, ty)| {
463            let additional_error_info = format!(
464                "Failed to get create instance for {}, instance index {}.",
465                task_specs.type_names[index], index
466            );
467
468            quote! {
469                <#ty>::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
470            }
471        }).collect::<Vec<_>>();
472
473        let task_instances_init_code = task_specs.instantiation_types.iter().zip(&task_specs.background_flags).enumerate().map(|(index, (task_type, background))| {
474            let additional_error_info = format!(
475                "Failed to get create instance for {}, instance index {}.",
476                task_specs.type_names[index], index
477            );
478            if *background {
479                quote! {
480                    #task_type::new(all_instances_configs[#index], threadpool).map_err(|e| e.add_cause(#additional_error_info))?
481                }
482            } else {
483                quote! {
484                    #task_type::new(all_instances_configs[#index]).map_err(|e| e.add_cause(#additional_error_info))?
485                }
486            }
487        }).collect::<Vec<_>>();
488
489        // Generate the code to create instances of the nodes
490        // It maps the types to their index
491        let (
492            task_restore_code,
493            start_calls,
494            stop_calls,
495            preprocess_calls,
496            postprocess_calls,
497            ): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = itertools::multiunzip(
498            (0..task_specs.task_types.len())
499            .map(|index| {
500                let task_index = int2sliceindex(index as u32);
501                let task_tuple_index = syn::Index::from(index);
502                let task_enum_name = config_id_to_enum(&task_specs.ids[index]);
503                let enum_name = Ident::new(&task_enum_name, Span::call_site());
504                (
505                    // Tasks keyframe restore code
506                    quote! {
507                        tasks.#task_tuple_index.thaw(&mut decoder).map_err(|e| CuError::new_with_cause("Failed to thaw", e))?
508                    },
509                    {  // Start calls
510                        let monitoring_action = quote! {
511                            let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Start, &error);
512                            match decision {
513                                Decision::Abort => {
514                                    debug!("Start: ABORT decision from monitoring. Task '{}' errored out \
515                                during start. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
516                                    return Ok(());
517
518                                }
519                                Decision::Ignore => {
520                                    debug!("Start: IGNORE decision from monitoring. Task '{}' errored out \
521                                during start. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
522                                }
523                                Decision::Shutdown => {
524                                    debug!("Start: SHUTDOWN decision from monitoring. Task '{}' errored out \
525                                during start. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
526                                    return Err(CuError::new_with_cause("Task errored out during start.", error));
527                                }
528                            }
529                        };
530
531                        let call_sim_callback = if sim_mode {
532                            quote! {
533                                // Ask the sim if this task should be executed or overridden by the sim.
534                                let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Start));
535
536                                let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
537                                    let error: CuError = reason.into();
538                                    #monitoring_action
539                                    false
540                               }
541                               else {
542                                    ovr == cu29::simulation::SimOverride::ExecuteByRuntime
543                               };
544                            }
545                        } else {
546                            quote! {
547                                let doit = true;  // in normal mode always execute the steps in the runtime.
548                            }
549                        };
550
551
552                        quote! {
553                            #call_sim_callback
554                            if doit {
555                                let task = &mut self.copper_runtime.tasks.#task_index;
556                                if let Err(error) = task.start(&self.copper_runtime.clock) {
557                                    #monitoring_action
558                                }
559                            }
560                        }
561                    },
562                    {  // Stop calls
563                        let monitoring_action = quote! {
564                                    let decision = self.copper_runtime.monitor.process_error(#index, CuTaskState::Stop, &error);
565                                    match decision {
566                                        Decision::Abort => {
567                                            debug!("Stop: ABORT decision from monitoring. Task '{}' errored out \
568                                    during stop. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
569                                            return Ok(());
570
571                                        }
572                                        Decision::Ignore => {
573                                            debug!("Stop: IGNORE decision from monitoring. Task '{}' errored out \
574                                    during stop. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
575                                        }
576                                        Decision::Shutdown => {
577                                            debug!("Stop: SHUTDOWN decision from monitoring. Task '{}' errored out \
578                                    during stop. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
579                                            return Err(CuError::new_with_cause("Task errored out during stop.", error));
580                                        }
581                                    }
582                            };
583                        let call_sim_callback = if sim_mode {
584                            quote! {
585                                // Ask the sim if this task should be executed or overridden by the sim.
586                                let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Stop));
587
588                                let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
589                                    let error: CuError = reason.into();
590                                    #monitoring_action
591                                    false
592                               }
593                               else {
594                                    ovr == cu29::simulation::SimOverride::ExecuteByRuntime
595                               };
596                            }
597                        } else {
598                            quote! {
599                                let doit = true;  // in normal mode always execute the steps in the runtime.
600                            }
601                        };
602                        quote! {
603                            #call_sim_callback
604                            if doit {
605                                let task = &mut self.copper_runtime.tasks.#task_index;
606                                if let Err(error) = task.stop(&self.copper_runtime.clock) {
607                                    #monitoring_action
608                                }
609                            }
610                        }
611                    },
612                    {  // Preprocess calls
613                        let monitoring_action = quote! {
614                            let decision = monitor.process_error(#index, CuTaskState::Preprocess, &error);
615                            match decision {
616                                Decision::Abort => {
617                                    debug!("Preprocess: ABORT decision from monitoring. Task '{}' errored out \
618                                during preprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
619                                    return Ok(());
620
621                                }
622                                Decision::Ignore => {
623                                    debug!("Preprocess: IGNORE decision from monitoring. Task '{}' errored out \
624                                during preprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
625                                }
626                                Decision::Shutdown => {
627                                    debug!("Preprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
628                                during preprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
629                                    return Err(CuError::new_with_cause("Task errored out during preprocess.", error));
630                                }
631                            }
632                        };
633                        let call_sim_callback = if sim_mode {
634                            quote! {
635                                // Ask the sim if this task should be executed or overridden by the sim.
636                                let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Preprocess));
637
638                                let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
639                                    let error: CuError = reason.into();
640                                    #monitoring_action
641                                    false
642                                } else {
643                                    ovr == cu29::simulation::SimOverride::ExecuteByRuntime
644                                };
645                            }
646                        } else {
647                            quote! {
648                                let doit = true;  // in normal mode always execute the steps in the runtime.
649                            }
650                        };
651                        quote! {
652                            #call_sim_callback
653                            if doit {
654                                if let Err(error) = tasks.#task_index.preprocess(clock) {
655                                    #monitoring_action
656                                }
657                            }
658                        }
659                    },
660                    {  // Postprocess calls
661                        let monitoring_action = quote! {
662                            let decision = monitor.process_error(#index, CuTaskState::Postprocess, &error);
663                            match decision {
664                                Decision::Abort => {
665                                    debug!("Postprocess: ABORT decision from monitoring. Task '{}' errored out \
666                                during postprocess. Aborting all the other starts.", #mission_mod::TASKS_IDS[#index]);
667                                    return Ok(());
668
669                                }
670                                Decision::Ignore => {
671                                    debug!("Postprocess: IGNORE decision from monitoring. Task '{}' errored out \
672                                during postprocess. The runtime will continue.", #mission_mod::TASKS_IDS[#index]);
673                                }
674                                Decision::Shutdown => {
675                                    debug!("Postprocess: SHUTDOWN decision from monitoring. Task '{}' errored out \
676                                during postprocess. The runtime cannot continue.", #mission_mod::TASKS_IDS[#index]);
677                                    return Err(CuError::new_with_cause("Task errored out during postprocess.", error));
678                                }
679                            }
680                        };
681                        let call_sim_callback = if sim_mode {
682                            quote! {
683                                // Ask the sim if this task should be executed or overridden by the sim.
684                                let ovr = sim_callback(SimStep::#enum_name(cu29::simulation::CuTaskCallbackState::Postprocess));
685
686                                let doit = if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
687                                    let error: CuError = reason.into();
688                                    #monitoring_action
689                                    false
690                                } else {
691                                    ovr == cu29::simulation::SimOverride::ExecuteByRuntime
692                                };
693                            }
694                        } else {
695                            quote! {
696                                let doit = true;  // in normal mode always execute the steps in the runtime.
697                            }
698                        };
699                        quote! {
700                            #call_sim_callback
701                            if doit {
702                                if let Err(error) = tasks.#task_index.postprocess(clock) {
703                                    #monitoring_action
704                                }
705                            }
706                        }
707                    }
708                )
709            })
710        );
711
712        // All accesses are linear on the culist but the id of the tasks is random (determined by the Ron declaration order).
713        // This records the task ids in call order.
714        let mut taskid_call_order: Vec<usize> = Vec::new();
715
716        let runtime_plan_code_and_logging: Vec<(proc_macro2::TokenStream, proc_macro2::TokenStream)> = runtime_plan.steps
717            .iter()
718            .map(|unit| {
719                match unit {
720                    CuExecutionUnit::Step(step) => {
721                        #[cfg(feature = "macro_debug")]
722                        eprintln!(
723                            "{} -> {} as {:?}. task_id: {} Input={:?}, Output={:?}",
724                            step.node.get_id(),
725                            step.node.get_type(),
726                            step.task_type,
727                            step.node_id,
728                            step.input_msg_indices_types,
729                            step.output_msg_index_type
730                        );
731
732                        let node_index = int2sliceindex(step.node_id);
733                        let task_instance = quote! { tasks.#node_index };
734                        let comment_str = format!(
735                            "DEBUG ->> {} ({:?}) Id:{} I:{:?} O:{:?}",
736                            step.node.get_id(),
737                            step.task_type,
738                            step.node_id,
739                            step.input_msg_indices_types,
740                            step.output_msg_index_type
741                        );
742                        let comment_tokens = quote! {
743                            {
744                                let _ = stringify!(#comment_str);
745                            }
746                        };
747                        // let comment_tokens: proc_macro2::TokenStream = parse_str(&comment_str).unwrap();
748                        let tid = step.node_id as usize;
749                        taskid_call_order.push(tid);
750
751                        let task_enum_name = config_id_to_enum(&task_specs.ids[tid]);
752                        let enum_name = Ident::new(&task_enum_name, proc_macro2::Span::call_site());
753
754                        let (process_call, preprocess_logging) = match step.task_type {
755                            CuTaskType::Source => {
756                                if let Some((output_index, _)) = &step.output_msg_index_type {
757                                    let output_culist_index = int2sliceindex(*output_index);
758
759                                    let monitoring_action = quote! {
760                                        debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
761                                        let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
762                                        match decision {
763                                            Decision::Abort => {
764                                                debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
765                                            during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
766                                                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
767                                                cl_manager.end_of_processing(clid)?;
768                                                return Ok(()); // this returns early from the one iteration call.
769
770                                            }
771                                            Decision::Ignore => {
772                                                debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
773                                            during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
774                                                let cumsg_output = &mut msgs.#output_culist_index;
775                                                cumsg_output.clear_payload();
776                                            }
777                                            Decision::Shutdown => {
778                                                debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
779                                            during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
780                                                return Err(CuError::new_with_cause("Task errored out during process.", error));
781                                            }
782                                        }
783                                    };
784                                    let call_sim_callback = if sim_mode {
785                                        quote! {
786                                            let doit = {
787                                                let cumsg_output = &mut msgs.#output_culist_index;
788                                                let state = cu29::simulation::CuTaskCallbackState::Process((), cumsg_output);
789                                                let ovr = sim_callback(SimStep::#enum_name(state));
790                                                if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
791                                                    let error: CuError = reason.into();
792                                                    #monitoring_action
793                                                    false
794                                                } else {
795                                                    ovr == cu29::simulation::SimOverride::ExecuteByRuntime
796                                                }
797                                            };
798                                         }
799                                    } else {
800                                        quote! {
801                                            let  doit = true;  // in normal mode always execute the steps in the runtime.
802                                       }
803                                    };
804
805                                    (quote! {  // process call
806                                        {
807                                            #comment_tokens
808                                            {
809                                                // Maybe freeze the task if this is a "key frame"
810                                                kf_manager.freeze_task(clid, &#task_instance)?;
811                                                #call_sim_callback
812                                                let cumsg_output = &mut msgs.#output_culist_index;
813                                                cumsg_output.metadata.process_time.start = clock.now().into();
814                                                let maybe_error = if doit {
815                                                    #task_instance.process(clock, cumsg_output)
816                                                } else {
817                                                    Ok(())
818                                                };
819                                                cumsg_output.metadata.process_time.end = clock.now().into();
820                                                if let Err(error) = maybe_error {
821                                                    #monitoring_action
822                                                }
823                                            }
824                                        }
825                                    }, {  // logging preprocess
826                                        if !task_specs.logging_enabled[*output_index as usize] {
827
828                                            #[cfg(feature = "macro_debug")]
829                                            eprintln!(
830                                                "{} -> Logging Disabled",
831                                                step.node.get_id(),
832                                            );
833
834
835                                            let output_culist_index = int2sliceindex(*output_index);
836                                            quote! {
837                                                let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
838                                                cumsg_output.clear_payload();
839                                            }
840                                        } else {
841                                            #[cfg(feature = "macro_debug")]
842                                            eprintln!(
843                                                "{} -> Logging Enabled",
844                                                step.node.get_id(),
845                                            );
846                                            quote!() // do nothing
847                                        }
848                                    }
849                                    )
850                                } else {
851                                    panic!("Source task should have an output message index.");
852                                }
853                            }
854                            CuTaskType::Sink => {
855                                // collect the indices
856                                let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
857                                if let Some((output_index, _)) = &step.output_msg_index_type {
858                                    let output_culist_index = int2sliceindex(*output_index);
859
860                                    let monitoring_action = quote! {
861                                        debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
862                                        let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
863                                        match decision {
864                                            Decision::Abort => {
865                                                debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
866                                            during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
867                                                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
868                                                cl_manager.end_of_processing(clid)?;
869                                                return Ok(()); // this returns early from the one iteration call.
870
871                                            }
872                                            Decision::Ignore => {
873                                                debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
874                                            during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
875                                                let cumsg_output = &mut msgs.#output_culist_index;
876                                                cumsg_output.clear_payload();
877                                            }
878                                            Decision::Shutdown => {
879                                                debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
880                                            during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
881                                                return Err(CuError::new_with_cause("Task errored out during process.", error));
882                                            }
883                                        }
884                                    };
885
886                                    let call_sim_callback = if sim_mode {
887                                        let inputs_type = if indices.len() == 1 {
888                                            // Not a tuple for a single input
889                                            quote! { #(msgs.#indices)* }
890                                        } else {
891                                            // A tuple for multiple inputs
892                                            quote! { (#(&msgs.#indices),*) }
893                                        };
894
895                                        quote! {
896                                            let doit = {
897                                                let cumsg_input = &#inputs_type;
898                                                // This is the virtual output for the sink
899                                                let cumsg_output = &mut msgs.#output_culist_index;
900                                                let state = cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output);
901                                                let ovr = sim_callback(SimStep::#enum_name(state));
902
903                                                if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
904                                                    let error: CuError = reason.into();
905                                                    #monitoring_action
906                                                    false
907                                                } else {
908                                                    ovr == cu29::simulation::SimOverride::ExecuteByRuntime
909                                                }
910                                            };
911                                         }
912                                    } else {
913                                        quote! {
914                                            let doit = true;  // in normal mode always execute the steps in the runtime.
915                                       }
916                                    };
917
918                                    let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
919
920                                    let inputs_type = if indices.len() == 1 {
921                                        // Not a tuple for a single input
922                                        quote! { #(msgs.#indices)* }
923                                    } else {
924                                        // A tuple for multiple inputs
925                                        quote! { (#(&msgs.#indices),*) }
926                                    };
927
928                                    (quote! {
929                                        {
930                                            #comment_tokens
931                                            // Maybe freeze the task if this is a "key frame"
932                                            kf_manager.freeze_task(clid, &#task_instance)?;
933                                            #call_sim_callback
934                                            let cumsg_input = &#inputs_type;
935                                            // This is the virtual output for the sink
936                                            let cumsg_output = &mut msgs.#output_culist_index;
937                                            cumsg_output.metadata.process_time.start = clock.now().into();
938                                            let maybe_error = if doit {#task_instance.process(clock, cumsg_input)} else {Ok(())};
939                                            cumsg_output.metadata.process_time.end = clock.now().into();
940                                            if let Err(error) = maybe_error {
941                                                #monitoring_action
942                                            }
943                                        }
944                                    }, {
945                                        quote!() // do nothing for logging
946                                    })
947                                } else {
948                                    panic!("Sink tasks should have a virtual output message index.");
949                                }
950                            }
951                            CuTaskType::Regular => {
952                                let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
953                                if let Some((output_index, _)) = &step.output_msg_index_type {
954                                    let output_culist_index = int2sliceindex(*output_index);
955
956                                    let monitoring_action = quote! {
957                                        debug!("Task {}: Error during process: {}", #mission_mod::TASKS_IDS[#tid], &error);
958                                        let decision = monitor.process_error(#tid, CuTaskState::Process, &error);
959                                        match decision {
960                                            Decision::Abort => {
961                                                debug!("Process: ABORT decision from monitoring. Task '{}' errored out \
962                                            during process. Skipping the processing of CL {}.", #mission_mod::TASKS_IDS[#tid], clid);
963                                                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
964                                                cl_manager.end_of_processing(clid)?;
965                                                return Ok(()); // this returns early from the one iteration call.
966
967                                            }
968                                            Decision::Ignore => {
969                                                debug!("Process: IGNORE decision from monitoring. Task '{}' errored out \
970                                            during process. The runtime will continue with a forced empty message.", #mission_mod::TASKS_IDS[#tid]);
971                                                let cumsg_output = &mut msgs.#output_culist_index;
972                                                cumsg_output.clear_payload();
973                                            }
974                                            Decision::Shutdown => {
975                                                debug!("Process: SHUTDOWN decision from monitoring. Task '{}' errored out \
976                                            during process. The runtime cannot continue.", #mission_mod::TASKS_IDS[#tid]);
977                                                return Err(CuError::new_with_cause("Task errored out during process.", error));
978                                            }
979                                        }
980                                    };
981
982                                    let call_sim_callback = if sim_mode {
983                                        let inputs_type = if indices.len() == 1 {
984                                            // Not a tuple for a single input
985                                            quote! { #(msgs.#indices)* }
986                                        } else {
987                                            // A tuple for multiple inputs
988                                            quote! { (#(&msgs.#indices),*) }
989                                        };
990
991                                        quote! {
992                                            let doit = {
993                                                let cumsg_input = &#inputs_type;
994                                                let cumsg_output = &mut msgs.#output_culist_index;
995                                                let state = cu29::simulation::CuTaskCallbackState::Process(cumsg_input, cumsg_output);
996                                                let ovr = sim_callback(SimStep::#enum_name(state));
997
998                                                if let cu29::simulation::SimOverride::Errored(reason) = ovr  {
999                                                    let error: CuError = reason.into();
1000                                                    #monitoring_action
1001                                                    false
1002                                                }
1003                                                else {
1004                                                    ovr == cu29::simulation::SimOverride::ExecuteByRuntime
1005                                                }
1006                                            };
1007                                         }
1008                                    } else {
1009                                        quote! {
1010                                            let doit = true;  // in normal mode always execute the steps in the runtime.
1011                                       }
1012                                    };
1013
1014                                    let indices = step.input_msg_indices_types.iter().map(|(index, _)| int2sliceindex(*index));
1015                                    let inputs_type = if indices.len() == 1 {
1016                                        // Not a tuple for a single input
1017                                        quote! { #(msgs.#indices)* }
1018                                    } else {
1019                                        // A tuple for multiple inputs
1020                                        quote! { (#(&msgs.#indices),*) }
1021                                    };
1022
1023                                    (quote! {
1024                                        {
1025                                            #comment_tokens
1026                                            // Maybe freeze the task if this is a "key frame"
1027                                            kf_manager.freeze_task(clid, &#task_instance)?;
1028                                            #call_sim_callback
1029                                            let cumsg_input = &#inputs_type;
1030                                            let cumsg_output = &mut msgs.#output_culist_index;
1031                                            cumsg_output.metadata.process_time.start = clock.now().into();
1032                                            let maybe_error = if doit {#task_instance.process(clock, cumsg_input, cumsg_output)} else {Ok(())};
1033                                            cumsg_output.metadata.process_time.end = clock.now().into();
1034                                            if let Err(error) = maybe_error {
1035                                                #monitoring_action
1036                                            }
1037                                        }
1038                                    }, {
1039
1040                                    if !task_specs.logging_enabled[*output_index as usize] {
1041                                        #[cfg(feature = "macro_debug")]
1042                                        eprintln!(
1043                                            "{} -> Logging Disabled",
1044                                            step.node.get_id(),
1045                                        );
1046                                        let output_culist_index = int2sliceindex(*output_index);
1047                                        quote! {
1048                                                let mut cumsg_output = &mut culist.msgs.0.#output_culist_index;
1049                                                cumsg_output.clear_payload();
1050                                            }
1051                                   } else {
1052                                        #[cfg(feature = "macro_debug")]
1053                                        eprintln!(
1054                                            "{} -> Logging Enabled",
1055                                            step.node.get_id(),
1056                                        );
1057                                         quote!() // do nothing logging is enabled
1058                                   }
1059                                })
1060                                } else {
1061                                    panic!("Regular task should have an output message index.");
1062                                }
1063                            }
1064                        };
1065
1066                        (process_call, preprocess_logging)
1067                    }
1068                    CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1069                }
1070            }).collect();
1071        #[cfg(feature = "macro_debug")]
1072        eprintln!("[Culist access order:  {taskid_call_order:?}]");
1073
1074        // Give a name compatible with a struct to match the task ids to their output in the CuStampedDataSet tuple.
1075        let all_tasks_member_ids: Vec<String> = task_specs
1076            .ids
1077            .iter()
1078            .map(|name| utils::config_id_to_struct_member(name.as_str()))
1079            .collect();
1080
1081        #[cfg(feature = "macro_debug")]
1082        eprintln!("[build the copperlist support]");
1083        let culist_support: proc_macro2::TokenStream =
1084            gen_culist_support(&runtime_plan, &taskid_call_order, &all_tasks_member_ids);
1085
1086        #[cfg(feature = "macro_debug")]
1087        eprintln!("[build the sim support]");
1088        let sim_support: proc_macro2::TokenStream = gen_sim_support(&runtime_plan);
1089
1090        let (new, run_one_iteration, start_all_tasks, stop_all_tasks, run) = if sim_mode {
1091            (
1092                quote! {
1093                    fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<Self>
1094                },
1095                quote! {
1096                    fn run_one_iteration(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1097                },
1098                quote! {
1099                    fn start_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1100                },
1101                quote! {
1102                    fn stop_all_tasks(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1103                },
1104                quote! {
1105                    fn run(&mut self, sim_callback: &mut impl FnMut(SimStep) -> cu29::simulation::SimOverride) -> CuResult<()>
1106                },
1107            )
1108        } else {
1109            (
1110                quote! {
1111                    fn new(clock:RobotClock, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>, config_override: Option<CuConfig>) -> CuResult<Self>
1112                },
1113                quote! {
1114                    fn run_one_iteration(&mut self) -> CuResult<()>
1115                },
1116                quote! {
1117                    fn start_all_tasks(&mut self) -> CuResult<()>
1118                },
1119                quote! {
1120                    fn stop_all_tasks(&mut self) -> CuResult<()>
1121                },
1122                quote! {
1123                    fn run(&mut self) -> CuResult<()>
1124                },
1125            )
1126        };
1127
1128        let sim_callback_arg = if sim_mode {
1129            Some(quote!(sim_callback))
1130        } else {
1131            None
1132        };
1133
1134        let sim_callback_on_new_calls = task_specs.ids.iter().enumerate().map(|(i, id)| {
1135            let enum_name = config_id_to_enum(id);
1136            let enum_ident = Ident::new(&enum_name, Span::call_site());
1137            quote! {
1138                // the answer is ignored, we have to instantiate the tasks anyway.
1139                sim_callback(SimStep::#enum_ident(cu29::simulation::CuTaskCallbackState::New(all_instances_configs[#i].cloned())));
1140            }
1141        });
1142
1143        let sim_callback_on_new = if sim_mode {
1144            Some(quote! {
1145                let graph = config.get_graph(Some(#mission)).expect("Could not find the mission #mission");
1146                let all_instances_configs: Vec<Option<&ComponentConfig>> = graph
1147                    .get_all_nodes()
1148                    .iter()
1149                    .map(|(_, node)| node.get_instance_config())
1150                    .collect();
1151                #(#sim_callback_on_new_calls)*
1152            })
1153        } else {
1154            None
1155        };
1156
1157        let (runtime_plan_code, preprocess_logging_calls): (Vec<_>, Vec<_>) =
1158            itertools::multiunzip(runtime_plan_code_and_logging);
1159
1160        #[cfg(feature = "macro_debug")]
1161        eprintln!("[build the run methods]");
1162        let run_methods = quote! {
1163
1164            #run_one_iteration {
1165
1166                // Pre-explode the runtime to avoid complexity with partial borrowing in the generated code.
1167                let runtime = &mut self.copper_runtime;
1168                let clock = &runtime.clock;
1169                let monitor = &mut runtime.monitor;
1170                let tasks = &mut runtime.tasks;
1171                let cl_manager = &mut runtime.copperlists_manager;
1172                let kf_manager = &mut runtime.keyframes_manager;
1173
1174                // Preprocess calls can happen at any time, just packed them up front.
1175                #(#preprocess_calls)*
1176
1177                let culist = cl_manager.inner.create().expect("Ran out of space for copper lists"); // FIXME: error handling
1178                let clid = culist.id;
1179                kf_manager.reset(clid, clock); // beginning of processing, we empty the serialized frozen states of the tasks.
1180                culist.change_state(cu29::copperlist::CopperListState::Processing);
1181                culist.msgs.init_zeroed();
1182                {
1183                    let msgs = &mut culist.msgs.0;
1184                    #(#runtime_plan_code)*
1185                } // drop(msgs);
1186                monitor.process_copperlist(&#mission_mod::collect_metadata(&culist))?;
1187
1188                // here drop the payloads if we don't want them to be logged.
1189                #(#preprocess_logging_calls)*
1190
1191                cl_manager.end_of_processing(clid)?;
1192                kf_manager.end_of_processing(clid)?;
1193
1194                // Postprocess calls can happen at any time, just packed them up at the end.
1195                #(#postprocess_calls)*
1196                Ok(())
1197            }
1198
1199            fn restore_keyframe(&mut self, keyframe: &KeyFrame) -> CuResult<()> {
1200                let runtime = &mut self.copper_runtime;
1201                let clock = &runtime.clock;
1202                let tasks = &mut runtime.tasks;
1203                let config = cu29::bincode::config::standard();
1204                let reader = cu29::bincode::de::read::SliceReader::new(&keyframe.serialized_tasks);
1205                let mut decoder = DecoderImpl::new(reader, config, ());
1206                #(#task_restore_code);*;
1207                Ok(())
1208            }
1209
1210            #start_all_tasks {
1211                #(#start_calls)*
1212                self.copper_runtime.monitor.start(&self.copper_runtime.clock)?;
1213                Ok(())
1214            }
1215
1216            #stop_all_tasks {
1217                #(#stop_calls)*
1218                self.copper_runtime.monitor.stop(&self.copper_runtime.clock)?;
1219                Ok(())
1220            }
1221
1222            #run {
1223                static STOP_FLAG: AtomicBool = AtomicBool::new(false);
1224                ctrlc::set_handler(move || {
1225                    STOP_FLAG.store(true, Ordering::SeqCst);
1226                }).expect("Error setting Ctrl-C handler");
1227
1228                self.start_all_tasks(#sim_callback_arg)?;
1229                let result = loop  {
1230                    let iter_start = self.copper_runtime.clock.now();
1231                    let result = self.run_one_iteration(#sim_callback_arg);
1232
1233                    if let Some(rate) = self.copper_runtime.runtime_config.rate_target_hz {
1234                        let period = 1_000_000_000u64 / rate;
1235                        let elapsed = self.copper_runtime.clock.now() - iter_start;
1236                        if elapsed.as_nanos() < period {
1237                            std::thread::sleep(std::time::Duration::from_nanos(period - elapsed.as_nanos()));
1238                        }
1239                    }
1240
1241                    if STOP_FLAG.load(Ordering::SeqCst) || result.is_err() {
1242                        break result;
1243                    }
1244                };
1245                if result.is_err() {
1246                    error!("A task errored out: {}", &result);
1247                }
1248                self.stop_all_tasks(#sim_callback_arg)?;
1249                result
1250            }
1251        };
1252
1253        let tasks_type = if sim_mode {
1254            quote!(CuSimTasks)
1255        } else {
1256            quote!(CuTasks)
1257        };
1258
1259        let tasks_instanciator_fn = if sim_mode {
1260            quote!(tasks_instanciator_sim)
1261        } else {
1262            quote!(tasks_instanciator)
1263        };
1264
1265        let app_impl_decl = if sim_mode {
1266            quote!(impl CuSimApplication for #application_name)
1267        } else {
1268            quote!(impl CuApplication for #application_name)
1269        };
1270        let simstep_type_decl = if sim_mode {
1271            quote!(
1272                type Step<'z> = SimStep<'z>;
1273            )
1274        } else {
1275            quote!()
1276        };
1277
1278        #[cfg(feature = "macro_debug")]
1279        eprintln!("[build result]");
1280        let application_impl = quote! {
1281            #app_impl_decl {
1282                #simstep_type_decl
1283
1284                #new {
1285                    let config_filename = #config_file;
1286                    let config = if config_override.is_some() {
1287                        let overridden_config = config_override.unwrap();
1288                        debug!("CuConfig: Overridden programmatically: {}", &overridden_config.serialize_ron());
1289                        overridden_config
1290                    } else if std::path::Path::new(config_filename).exists() {
1291                        debug!("CuConfig: Reading configuration from file: {}", config_filename);
1292                        cu29::config::read_configuration(config_filename)?
1293                    } else {
1294                        let original_config = Self::get_original_config();
1295                        debug!("CuConfig: Using the original configuration the project was compiled with: {}", &original_config);
1296                        cu29::config::read_configuration_str(original_config, None)?
1297                    };
1298
1299                    // For simple cases we can say the section is just a bunch of Copper Lists.
1300                    // But we can now have allocations outside of it so we can override it from the config.
1301                    let mut default_section_size = std::mem::size_of::<super::#mission_mod::CuList>() * 64;
1302                    // Check if there is a logging configuration with section_size_mib
1303                    if let Some(section_size_mib) = config.logging.as_ref().and_then(|l| l.section_size_mib) {
1304                        // Convert MiB to bytes
1305                        default_section_size = section_size_mib as usize * 1024usize * 1024usize;
1306                    }
1307                    let copperlist_stream = stream_write::<#mission_mod::CuList>(
1308                        unified_logger.clone(),
1309                        UnifiedLogType::CopperList,
1310                        default_section_size,
1311                        // the 2 sizes are not directly related as we encode the CuList but we can
1312                        // assume the encoded size is close or lower than the non encoded one
1313                        // This is to be sure we have the size of at least a Culist and some.
1314                    );
1315
1316                    let keyframes_stream = stream_write::<KeyFrame>(
1317                        unified_logger.clone(),
1318                        UnifiedLogType::FrozenTasks,
1319                        1024 * 1024 * 10, // 10 MiB
1320                    );
1321
1322
1323                    let application = Ok(#application_name {
1324                        copper_runtime: CuRuntime::<#mission_mod::#tasks_type, #mission_mod::CuStampedDataSet, #monitor_type, #DEFAULT_CLNB>::new(
1325                            clock,
1326                            &config,
1327                            Some(#mission),
1328                            #mission_mod::#tasks_instanciator_fn,
1329                            #mission_mod::monitor_instanciator,
1330                            copperlist_stream,
1331                            keyframes_stream)?, // FIXME: gbin
1332                    });
1333
1334                    #sim_callback_on_new
1335
1336                    application
1337                }
1338
1339                fn get_original_config() -> String {
1340                    #copper_config_content.to_string()
1341                }
1342
1343                #run_methods
1344            }
1345        };
1346
1347        let (
1348            builder_struct,
1349            builder_new,
1350            builder_impl,
1351            builder_sim_callback_method,
1352            builder_build_sim_callback_arg,
1353        ) = if sim_mode {
1354            (
1355                quote! {
1356                    #[allow(dead_code)]
1357                    pub struct #builder_name <'a, F> {
1358                        clock: Option<RobotClock>,
1359                        unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1360                        config_override: Option<CuConfig>,
1361                        sim_callback: Option<&'a mut F>
1362                    }
1363                },
1364                quote! {
1365                    #[allow(dead_code)]
1366                    pub fn new() -> Self {
1367                        Self {
1368                            clock: None,
1369                            unified_logger: None,
1370                            config_override: None,
1371                            sim_callback: None,
1372                        }
1373                    }
1374                },
1375                quote! {
1376                    impl<'a, F> #builder_name <'a, F>
1377                    where
1378                        F: FnMut(SimStep) -> cu29::simulation::SimOverride,
1379                },
1380                Some(quote! {
1381                    pub fn with_sim_callback(mut self, sim_callback: &'a mut F) -> Self
1382                    {
1383                        self.sim_callback = Some(sim_callback);
1384                        self
1385                    }
1386                }),
1387                Some(quote! {
1388                    self.sim_callback
1389                        .ok_or(CuError::from("Sim callback missing from builder"))?,
1390                }),
1391            )
1392        } else {
1393            (
1394                quote! {
1395                    #[allow(dead_code)]
1396                    pub struct #builder_name {
1397                        clock: Option<RobotClock>,
1398                        unified_logger: Option<Arc<Mutex<UnifiedLoggerWrite>>>,
1399                        config_override: Option<CuConfig>,
1400                    }
1401                },
1402                quote! {
1403                    #[allow(dead_code)]
1404                    pub fn new() -> Self {
1405                        Self {
1406                            clock: None,
1407                            unified_logger: None,
1408                            config_override: None,
1409                        }
1410                    }
1411                },
1412                quote! {
1413                    impl #builder_name
1414                },
1415                None,
1416                None,
1417            )
1418        };
1419
1420        let application_builder = quote! {
1421            #builder_struct
1422
1423            #builder_impl
1424            {
1425                #builder_new
1426
1427                #[allow(dead_code)]
1428                pub fn with_clock(mut self, clock: RobotClock) -> Self {
1429                    self.clock = Some(clock);
1430                    self
1431                }
1432
1433                #[allow(dead_code)]
1434                pub fn with_unified_logger(mut self, unified_logger: Arc<Mutex<UnifiedLoggerWrite>>) -> Self {
1435                    self.unified_logger = Some(unified_logger);
1436                    self
1437                }
1438
1439                #[allow(dead_code)]
1440                pub fn with_context(mut self, copper_ctx: &CopperContext) -> Self {
1441                    self.clock = Some(copper_ctx.clock.clone());
1442                    self.unified_logger = Some(copper_ctx.unified_logger.clone());
1443                    self
1444                }
1445
1446                #[allow(dead_code)]
1447                pub fn with_config(mut self, config_override: CuConfig) -> Self {
1448                        self.config_override = Some(config_override);
1449                        self
1450                }
1451
1452                #builder_sim_callback_method
1453
1454                #[allow(dead_code)]
1455                pub fn build(self) -> CuResult<#application_name> {
1456                    #application_name::new(
1457                        self.clock
1458                            .ok_or(CuError::from("Clock missing from builder"))?,
1459                        self.unified_logger
1460                            .ok_or(CuError::from("Unified logger missing from builder"))?,
1461                        self.config_override,
1462                        #builder_build_sim_callback_arg
1463                    )
1464                }
1465            }
1466        };
1467
1468        let ids = task_specs.ids;
1469        // Convert the modified struct back into a TokenStream
1470        let mission_mod_tokens = quote! {
1471            mod #mission_mod {
1472                use super::*;  // import the modules the main app did.
1473
1474                use cu29::bincode::Encode;
1475                use cu29::bincode::enc::Encoder;
1476                use cu29::bincode::error::EncodeError;
1477                use cu29::bincode::Decode;
1478                use cu29::bincode::de::Decoder;
1479                use cu29::bincode::de::DecoderImpl;
1480                use cu29::bincode::error::DecodeError;
1481                use cu29::rayon::ThreadPool;
1482                use cu29::clock::RobotClock;
1483                use cu29::config::CuConfig;
1484                use cu29::config::ComponentConfig;
1485                use cu29::cuasynctask::CuAsyncTask;
1486                use cu29::curuntime::CuRuntime;
1487                use cu29::curuntime::KeyFrame;
1488                use cu29::curuntime::CopperContext;
1489                use cu29::CuResult;
1490                use cu29::CuError;
1491                use cu29::cutask::CuSrcTask;
1492                use cu29::cutask::CuSinkTask;
1493                use cu29::cutask::CuTask;
1494                use cu29::cutask::CuMsg;
1495                use cu29::cutask::CuMsgMetadata;
1496                use cu29::copperlist::CopperList;
1497                use cu29::monitoring::CuMonitor; // Trait import.
1498                use cu29::monitoring::CuTaskState;
1499                use cu29::monitoring::Decision;
1500                use cu29::prelude::app::CuApplication;
1501                use cu29::prelude::debug;
1502                use cu29::prelude::stream_write;
1503                use cu29::prelude::UnifiedLoggerWrite;
1504                use cu29::prelude::UnifiedLogType;
1505                use std::sync::Arc;
1506                use std::sync::Mutex;
1507                use std::sync::atomic::{AtomicBool, Ordering};
1508
1509                // Not used if the used doesn't generate Sim.
1510                #[allow(unused_imports)]
1511                use cu29::prelude::app::CuSimApplication;
1512
1513                // Not used if a monitor is present
1514                #[allow(unused_imports)]
1515                use cu29::monitoring::NoMonitor;
1516
1517                // This is the heart of everything.
1518                // CuTasks is the list of all the tasks types.
1519                // CuList is a CopperList with the list of all the messages types as msgs.
1520                pub type CuTasks = #task_types_tuple;
1521
1522                // This is the variation with stubs for the sources and sinks in simulation mode.
1523                // Not used if the used doesn't generate Sim.
1524                #[allow(dead_code)]
1525                pub type CuSimTasks = #task_types_tuple_sim;
1526
1527                pub const TASKS_IDS: &'static [&'static str] = &[#( #ids ),*];
1528
1529                #culist_support
1530
1531                #sim_support
1532
1533                pub fn tasks_instanciator<'c>(all_instances_configs: Vec<Option<&'c ComponentConfig>>, threadpool: Arc<ThreadPool>) -> CuResult<CuTasks> {
1534                    Ok(( #(#task_instances_init_code),*, ))
1535                }
1536
1537                #[allow(dead_code)]
1538                pub fn tasks_instanciator_sim(all_instances_configs: Vec<Option<&ComponentConfig>>, _threadpool: Arc<ThreadPool>) -> CuResult<CuSimTasks> {
1539                    Ok(( #(#task_sim_instances_init_code),*, ))
1540                }
1541
1542                pub fn monitor_instanciator(config: &CuConfig) -> #monitor_type {
1543                    #monitor_type::new(config, #mission_mod::TASKS_IDS).expect("Failed to create the given monitor.")
1544                }
1545
1546                // The application for this mission
1547                pub #application_struct
1548
1549                #application_impl
1550
1551                #application_builder
1552            }
1553
1554        };
1555        all_missions_tokens.push(mission_mod_tokens);
1556    }
1557
1558    let default_application_tokens = if all_missions.contains_key("default") {
1559        quote! {
1560        // you can bypass the builder and not use it
1561        #[allow(unused_imports)]
1562        use default::#builder_name;
1563
1564        #[allow(unused_imports)]
1565        use default::#application_name;
1566        }
1567    } else {
1568        quote!() // do nothing
1569    };
1570
1571    let result: proc_macro2::TokenStream = quote! {
1572        #(#all_missions_tokens)*
1573        #default_application_tokens
1574    };
1575
1576    // Print and format the generated code using rustfmt
1577    #[cfg(feature = "macro_debug")]
1578    {
1579        let formatted_code = rustfmt_generated_code(result.to_string());
1580        eprintln!("\n     ===    Gen. Runtime ===\n");
1581        eprintln!("{formatted_code}");
1582        // if you need colors back: eprintln!("{}", highlight_rust_code(formatted_code)); was disabled for cubuild.
1583        // or simply use cargo expand
1584        eprintln!("\n     === === === === === ===\n");
1585    }
1586    result.into()
1587}
1588
1589fn read_config(config_file: &str) -> CuResult<CuConfig> {
1590    let filename = config_full_path(config_file);
1591
1592    read_configuration(filename.as_str())
1593}
1594
1595fn config_full_path(config_file: &str) -> String {
1596    let mut config_full_path = utils::caller_crate_root();
1597    config_full_path.push(config_file);
1598    let filename = config_full_path
1599        .as_os_str()
1600        .to_str()
1601        .expect("Could not interpret the config file name");
1602    filename.to_string()
1603}
1604
1605fn extract_tasks_output_types(graph: &CuGraph) -> Vec<Option<Type>> {
1606    let result = graph
1607        .get_all_nodes()
1608        .iter()
1609        .map(|(_, node)| {
1610            let id = node.get_id();
1611            let type_str = graph.get_node_output_msg_type(id.as_str());
1612            let result = type_str.map(|type_str| {
1613                let result = parse_str::<Type>(type_str.as_str())
1614                    .expect("Could not parse output message type.");
1615                result
1616            });
1617            result
1618        })
1619        .collect();
1620    result
1621}
1622
1623struct CuTaskSpecSet {
1624    pub ids: Vec<String>,
1625    pub cutypes: Vec<CuTaskType>,
1626    pub background_flags: Vec<bool>,
1627    pub logging_enabled: Vec<bool>,
1628    pub type_names: Vec<String>,
1629    pub task_types: Vec<Type>,
1630    pub instantiation_types: Vec<Type>,
1631    pub sim_task_types: Vec<Type>,
1632    #[allow(dead_code)]
1633    pub output_types: Vec<Option<Type>>,
1634}
1635
1636impl CuTaskSpecSet {
1637    pub fn from_graph(graph: &CuGraph) -> Self {
1638        let all_id_nodes = graph.get_all_nodes();
1639
1640        let ids = all_id_nodes
1641            .iter()
1642            .map(|(_, node)| node.get_id().to_string())
1643            .collect();
1644
1645        let cutypes = all_id_nodes
1646            .iter()
1647            .map(|(id, _)| find_task_type_for_id(graph, *id))
1648            .collect();
1649
1650        let background_flags: Vec<bool> = all_id_nodes
1651            .iter()
1652            .map(|(_, node)| node.is_background())
1653            .collect();
1654
1655        let logging_enabled: Vec<bool> = all_id_nodes
1656            .iter()
1657            .map(|(_, node)| node.is_logging_enabled())
1658            .collect();
1659
1660        let type_names: Vec<String> = all_id_nodes
1661            .iter()
1662            .map(|(_, node)| node.get_type().to_string())
1663            .collect();
1664
1665        let output_types = extract_tasks_output_types(graph);
1666
1667        let task_types = type_names
1668            .iter()
1669            .zip(background_flags.iter())
1670            .zip(output_types.iter())
1671            .map(|((name, &background), output_type)| {
1672                let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
1673                    panic!("Could not transform {name} into a Task Rust type: {error}");
1674                });
1675                if background {
1676                    if let Some(output_type) = output_type {
1677                        parse_quote!(cu29::cuasynctask::CuAsyncTask<#name_type, #output_type>)
1678                    } else {
1679                        panic!("{name}: If a task is background, it has to have an output");
1680                    }
1681                } else {
1682                    name_type
1683                }
1684            })
1685            .collect();
1686
1687        let instantiation_types = type_names
1688            .iter()
1689            .zip(background_flags.iter())
1690            .zip(output_types.iter())
1691            .map(|((name, &background), output_type)| {
1692                let name_type = parse_str::<Type>(name).unwrap_or_else(|error| {
1693                    panic!("Could not transform {name} into a Task Rust type: {error}");
1694                });
1695                if background {
1696                    if let Some(output_type) = output_type {
1697                        parse_quote!(cu29::cuasynctask::CuAsyncTask::<#name_type, #output_type>)
1698                    } else {
1699                        panic!("{name}: If a task is background, it has to have an output");
1700                    }
1701                } else {
1702                    name_type
1703                }
1704            })
1705            .collect();
1706
1707        let sim_task_types = type_names
1708            .iter()
1709            .map(|name| {
1710                parse_str::<Type>(name).unwrap_or_else(|err| {
1711                    eprintln!("Could not transform {name} into a Task Rust type.");
1712                    panic!("{err}")
1713                })
1714            })
1715            .collect();
1716
1717        Self {
1718            ids,
1719            cutypes,
1720            background_flags,
1721            logging_enabled,
1722            type_names,
1723            task_types,
1724            instantiation_types,
1725            sim_task_types,
1726            output_types,
1727        }
1728    }
1729}
1730
1731fn extract_msg_types(runtime_plan: &CuExecutionLoop) -> Vec<Type> {
1732    runtime_plan
1733        .steps
1734        .iter()
1735        .filter_map(|unit| match unit {
1736            CuExecutionUnit::Step(step) => {
1737                if let Some((_, output_msg_type)) = &step.output_msg_index_type {
1738                    Some(
1739                        parse_str::<Type>(output_msg_type.as_str()).unwrap_or_else(|_| {
1740                            panic!(
1741                                "Could not transform {output_msg_type} into a message Rust type."
1742                            )
1743                        }),
1744                    )
1745                } else {
1746                    None
1747                }
1748            }
1749            CuExecutionUnit::Loop(_) => todo!("Needs to be implemented"),
1750        })
1751        .collect()
1752}
1753
1754/// Builds the tuple of the CuList as a tuple off all the messages types.
1755fn build_culist_tuple(all_msgs_types_in_culist_order: &[Type]) -> TypeTuple {
1756    if all_msgs_types_in_culist_order.is_empty() {
1757        parse_quote! { () }
1758    } else {
1759        parse_quote! {
1760            ( #( CuMsg<#all_msgs_types_in_culist_order> ),* )
1761        }
1762    }
1763}
1764
1765/// This is the bincode encoding part of the CuStampedDataSet
1766fn build_culist_tuple_encode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1767    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1768
1769    // Generate the `self.#i.encode(encoder)?` for each tuple index, including `()` types
1770    let encode_fields: Vec<_> = indices
1771        .iter()
1772        .map(|i| {
1773            let idx = syn::Index::from(*i);
1774            quote! { self.0.#idx.encode(encoder)?; }
1775        })
1776        .collect();
1777
1778    parse_quote! {
1779        impl Encode for CuStampedDataSet {
1780            fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
1781                #(#encode_fields)*
1782                Ok(())
1783            }
1784        }
1785    }
1786}
1787
1788/// This is the bincode decoding part of the CuStampedDataSet
1789fn build_culist_tuple_decode(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1790    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1791
1792    // Generate the `CuStampedData::<T>::decode(decoder)?` for each tuple index
1793    let decode_fields: Vec<_> = indices
1794        .iter()
1795        .map(|i| {
1796            let t = &all_msgs_types_in_culist_order[*i];
1797            quote! { CuMsg::<#t>::decode(decoder)? }
1798        })
1799        .collect();
1800
1801    parse_quote! {
1802        impl Decode<()> for CuStampedDataSet {
1803            fn decode<D: Decoder<Context=()>>(decoder: &mut D) -> Result<Self, DecodeError> {
1804                Ok(CuStampedDataSet ((
1805                    #(#decode_fields),*
1806                )))
1807            }
1808        }
1809    }
1810}
1811
1812fn build_culist_erasedcumsgs(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1813    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1814    let casted_fields: Vec<_> = indices
1815        .iter()
1816        .map(|i| {
1817            let idx = syn::Index::from(*i);
1818            quote! { &self.0.#idx as &dyn ErasedCuStampedData }
1819        })
1820        .collect();
1821    parse_quote! {
1822        impl ErasedCuStampedDataSet for CuStampedDataSet {
1823            fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
1824                vec![
1825                    #(#casted_fields),*
1826                ]
1827            }
1828        }
1829    }
1830}
1831
1832fn build_culist_tuple_debug(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1833    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1834
1835    let debug_fields: Vec<_> = indices
1836        .iter()
1837        .map(|i| {
1838            let idx = syn::Index::from(*i);
1839            quote! { .field(&self.0.#idx) }
1840        })
1841        .collect();
1842
1843    parse_quote! {
1844        impl std::fmt::Debug for CuStampedDataSet {
1845            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1846                f.debug_tuple("CuStampedDataSet")
1847                    #(#debug_fields)*
1848                    .finish()
1849            }
1850        }
1851    }
1852}
1853
1854/// This is the serde serialization part of the CuStampedDataSet
1855fn build_culist_tuple_serialize(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1856    let indices: Vec<usize> = (0..all_msgs_types_in_culist_order.len()).collect();
1857    let tuple_len = all_msgs_types_in_culist_order.len();
1858
1859    // Generate the serialization for each tuple field
1860    let serialize_fields: Vec<_> = indices
1861        .iter()
1862        .map(|i| {
1863            let idx = syn::Index::from(*i);
1864            quote! { &self.0.#idx }
1865        })
1866        .collect();
1867
1868    parse_quote! {
1869        impl Serialize for CuStampedDataSet {
1870            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1871            where
1872                S: serde::Serializer,
1873            {
1874                use serde::ser::SerializeTuple;
1875                let mut tuple = serializer.serialize_tuple(#tuple_len)?;
1876                #(tuple.serialize_element(#serialize_fields)?;)*
1877                tuple.end()
1878            }
1879        }
1880    }
1881}
1882
1883/// This is the default implementation for CuStampedDataSet
1884fn build_culist_tuple_default(all_msgs_types_in_culist_order: &[Type]) -> ItemImpl {
1885    // Generate the serialization for each tuple field
1886    let default_fields: Vec<_> = all_msgs_types_in_culist_order
1887        .iter()
1888        .map(|msg_type| quote! { CuStampedData::<#msg_type, CuMsgMetadata>::default() })
1889        .collect();
1890
1891    parse_quote! {
1892        impl Default for CuStampedDataSet {
1893            fn default() -> CuStampedDataSet
1894            {
1895                CuStampedDataSet((
1896                    #(#default_fields),*
1897                ))
1898            }
1899        }
1900    }
1901}
1902
1903#[cfg(test)]
1904mod tests {
1905    // See tests/compile_file directory for more information
1906    #[test]
1907    fn test_compile_fail() {
1908        let t = trybuild::TestCases::new();
1909        t.compile_fail("tests/compile_fail/*/*.rs");
1910    }
1911}