Skip to main content

project_expand/tests/common/
json_desc.rs

1use super::Language;
2use std::fs;
3use std::path::{Path, PathBuf};
4use yamake::c_nodes::{AFile, CFile, HFile, OFile};
5use yamake::command::log_build;
6use yamake::model::{Edge, ExpandError, ExpandResult, GNode};
7
8pub struct JsonDesc {
9    pub name: String,
10}
11
12impl JsonDesc {
13    pub fn new(name: &str) -> Self {
14        Self {
15            name: name.to_string(),
16        }
17    }
18}
19
20impl GNode for JsonDesc {
21    fn tag(&self) -> String {
22        "JsonDesc".to_string()
23    }
24
25    fn pathbuf(&self) -> PathBuf {
26        PathBuf::from(&self.name)
27    }
28
29    fn build(&self, sandbox: &Path, predecessors: &[&(dyn GNode + Send + Sync)]) -> bool {
30        assert!(
31            predecessors.len() == 1,
32            "JsonDesc expects exactly one predecessor, got {}",
33            predecessors.len()
34        );
35
36        let predecessor = predecessors[0];
37        assert!(
38            predecessor.tag() == "YmlDesc",
39            "JsonDesc predecessor must be YmlDesc, got {}",
40            predecessor.tag()
41        );
42
43        let input_path = sandbox.join(predecessor.pathbuf());
44        let yaml_content = fs::read_to_string(&input_path)
45            .unwrap_or_else(|e| panic!("Failed to read {}: {}", input_path.display(), e));
46
47        let languages: Vec<Language> = serde_yaml::from_str(&yaml_content)
48            .unwrap_or_else(|e| panic!("Failed to parse YAML {}: {}", input_path.display(), e));
49
50        let json_content = serde_json::to_string_pretty(&languages)
51            .unwrap_or_else(|e| panic!("Failed to serialize to JSON: {}", e));
52
53        let output_path = sandbox.join(&self.name);
54        if let Some(parent) = output_path.parent() {
55            fs::create_dir_all(parent).ok();
56        }
57
58        fs::write(&output_path, &json_content)
59            .unwrap_or_else(|e| panic!("Failed to write {}: {}", output_path.display(), e));
60
61        // Log the build operation
62        let description = format!(
63            "Convert YAML to JSON: {} -> {}",
64            input_path.display(),
65            output_path.display()
66        );
67        let stdout = format!(
68            "Read {} languages from {}\nWrote {} bytes to {}\n",
69            languages.len(),
70            input_path.display(),
71            json_content.len(),
72            output_path.display()
73        );
74        log_build(sandbox, &self.name, &description, &stdout, "");
75
76        true
77    }
78
79    fn expand(&self, sandbox: &Path, _predecessors: &[&(dyn GNode + Send + Sync)]) -> ExpandResult {
80        let input_path = sandbox.join(&self.name);
81        let json_content = fs::read_to_string(&input_path)
82            .map_err(|e| ExpandError::ReadError(input_path.clone(), e))?;
83
84        let languages: Vec<Language> = serde_json::from_str(&json_content).map_err(|e| {
85            ExpandError::ParseError(format!(
86                "Failed to parse JSON {}: {}",
87                input_path.display(),
88                e
89            ))
90        })?;
91
92        let generated_dir = sandbox.join("project_expand/generated");
93        fs::create_dir_all(&generated_dir).ok();
94
95        let mut nodes: Vec<Box<dyn GNode + Send + Sync>> = Vec::new();
96        let mut edges: Vec<Edge> = Vec::new();
97
98        let lib_path = "project_expand/generated/liblangs.a";
99
100        for lang in &languages {
101            // Generate C file
102            let c_path = format!("project_expand/generated/{}.c", lang.language);
103            let c_content = format!(
104                "const char* helloworld_{}=\"{}\" ;\nconst char* get_{}() {{ return helloworld_{} ; }}\n",
105                lang.language, lang.helloworld, lang.language, lang.language
106            );
107            let c_full_path = sandbox.join(&c_path);
108            fs::write(&c_full_path, &c_content)
109                .map_err(|e| ExpandError::WriteError(c_full_path, e))?;
110            nodes.push(Box::new(CFile::new(&c_path)));
111
112            // Generate H file
113            let h_path = format!("project_expand/generated/{}.h", lang.language);
114            let h_content = format!("const char* get_{}() ;\n", lang.language);
115            let h_full_path = sandbox.join(&h_path);
116            fs::write(&h_full_path, &h_content)
117                .map_err(|e| ExpandError::WriteError(h_full_path, e))?;
118            nodes.push(Box::new(HFile::new(&h_path)));
119
120            // Create OFile for this language
121            let o_path = format!("project_expand/generated/{}.o", lang.language);
122            nodes.push(Box::new(OFile::new(&o_path, vec![], vec![])));
123
124            // Add edges from JsonDesc to CFile and HFile (so they are not treated as root nodes)
125            edges.push(Edge {
126                nfrom: Box::new(JsonDesc::new(&self.name)),
127                nto: Box::new(CFile::new(&c_path)),
128            });
129            edges.push(Edge {
130                nfrom: Box::new(JsonDesc::new(&self.name)),
131                nto: Box::new(HFile::new(&h_path)),
132            });
133
134            // Add edges: CFile -> OFile -> AFile
135            edges.push(Edge {
136                nfrom: Box::new(CFile::new(&c_path)),
137                nto: Box::new(OFile::new(&o_path, vec![], vec![])),
138            });
139            edges.push(Edge {
140                nfrom: Box::new(OFile::new(&o_path, vec![], vec![])),
141                nto: Box::new(AFile::new(lib_path)),
142            });
143        }
144
145        // Generate languages.h with array of function pointers
146        let languages_h_path = PathBuf::from("project_expand/generated/languages.h");
147        let mut h_content = String::new();
148        h_content.push_str("#ifndef LANGUAGES_H\n");
149        h_content.push_str("#define LANGUAGES_H\n\n");
150
151        for lang in &languages {
152            h_content.push_str(&format!(
153                "#include \"project_expand/generated/{}.h\"\n",
154                lang.language
155            ));
156        }
157        h_content.push_str("\n");
158
159        h_content.push_str(&format!("#define N_languages {}\n\n", languages.len()));
160
161        h_content.push_str("typedef const char* (*get_language_fn)() ;\n\n");
162        h_content.push_str("get_language_fn languages[] = {\n");
163        for lang in &languages {
164            h_content.push_str(&format!("    get_{},\n", lang.language));
165        }
166        h_content.push_str("} ;\n\n");
167
168        h_content.push_str("#endif\n");
169
170        let languages_h_full_path = sandbox.join(&languages_h_path);
171        fs::write(&languages_h_full_path, &h_content)
172            .map_err(|e| ExpandError::WriteError(languages_h_full_path, e))?;
173        nodes.push(Box::new(HFile::new(&languages_h_path.to_string_lossy())));
174
175        // Add edge from JsonDesc to languages.h
176        edges.push(Edge {
177            nfrom: Box::new(JsonDesc::new(&self.name)),
178            nto: Box::new(HFile::new(&languages_h_path.to_string_lossy())),
179        });
180
181        Ok((nodes, edges))
182    }
183}