1use crate::ast::Module;
9use crate::naming::module_file_stem;
10use crate::naming::to_pascal_case;
11use std::fmt::Write;
12
13#[derive(Debug, Clone, Default)]
15pub struct CMakeConfig {
16 pub synta_root: Option<String>,
21 pub shared_library: bool,
23}
24
25pub fn generate_cmake(
31 modules: &[Module],
32 order: &[usize],
33 config: CMakeConfig,
34) -> Result<String, Box<dyn std::error::Error>> {
35 let mut out = String::new();
36
37 let module_names: Vec<&str> = order.iter().map(|&i| modules[i].name.as_str()).collect();
39 let project_name = if modules.len() == 1 {
40 to_pascal_case(modules[0].name.as_str())
41 } else {
42 to_pascal_case(modules[*order.last().unwrap()].name.as_str())
45 };
46
47 writeln!(
48 out,
49 "# Generated from ASN.1 module{} {}",
50 if modules.len() == 1 { "" } else { "s" },
51 module_names.join(", ")
52 )?;
53 writeln!(out, "# DO NOT EDIT - auto-generated code")?;
54 writeln!(out)?;
55 writeln!(out, "cmake_minimum_required(VERSION 3.10)")?;
56 writeln!(out, "project({} C)", project_name)?;
57 writeln!(out)?;
58 writeln!(out, "set(CMAKE_C_STANDARD 99)")?;
59 writeln!(out, "set(CMAKE_C_STANDARD_REQUIRED ON)")?;
60 writeln!(out)?;
61 writeln!(out, "if(MSVC)")?;
62 writeln!(out, " add_compile_options(/W4)")?;
63 writeln!(out, "else()")?;
64 writeln!(out, " add_compile_options(-Wall -Wextra)")?;
65 writeln!(out, "endif()")?;
66 writeln!(out)?;
67
68 writeln!(out, "# Locate the synta library.")?;
70 writeln!(
71 out,
72 "# If the parent project already defines a Synta::Synta imported target,"
73 )?;
74 writeln!(out, "# this block is skipped automatically.")?;
75 writeln!(out, "if(NOT TARGET Synta::Synta)")?;
76
77 if let Some(ref root) = config.synta_root {
78 writeln!(out, " set(_synta_root \"{}\")", root)?;
79 } else {
80 writeln!(out, " if(NOT DEFINED SYNTA_ROOT)")?;
81 writeln!(out, " set(SYNTA_ROOT \"\" CACHE PATH")?;
82 writeln!(out, " \"Root of the synta source tree (contains include/ and target/release/)\")")?;
83 writeln!(out, " endif()")?;
84 writeln!(out, " if(\"${{SYNTA_ROOT}}\" STREQUAL \"\")")?;
85 writeln!(out, " message(FATAL_ERROR")?;
86 writeln!(
87 out,
88 " \"SYNTA_ROOT is not set. Pass it on the cmake command line:\\n\""
89 )?;
90 writeln!(
91 out,
92 " \" cmake -DSYNTA_ROOT=/path/to/synta -S . -B build\")"
93 )?;
94 writeln!(out, " endif()")?;
95 writeln!(out, " set(_synta_root \"${{SYNTA_ROOT}}\")")?;
96 }
97
98 writeln!(out, " find_library(SYNTA_LIBRARY")?;
99 writeln!(out, " NAMES synta")?;
100 writeln!(out, " PATHS \"${{_synta_root}}/target/release\"")?;
101 writeln!(out, " NO_DEFAULT_PATH")?;
102 writeln!(out, " )")?;
103 writeln!(out, " if(NOT SYNTA_LIBRARY)")?;
104 writeln!(out, " message(FATAL_ERROR")?;
105 writeln!(
106 out,
107 " \"synta library not found under ${{_synta_root}}/target/release\\n\""
108 )?;
109 writeln!(out, " \"Build it first: cd ${{_synta_root}} && cargo build --release --features ffi\")")?;
110 writeln!(out, " endif()")?;
111 writeln!(out, " add_library(Synta::Synta UNKNOWN IMPORTED)")?;
112 writeln!(out, " set_target_properties(Synta::Synta PROPERTIES")?;
113 writeln!(out, " IMPORTED_LOCATION \"${{SYNTA_LIBRARY}}\"")?;
114 writeln!(
115 out,
116 " INTERFACE_INCLUDE_DIRECTORIES \"${{_synta_root}}/include\""
117 )?;
118 writeln!(out, " )")?;
119 writeln!(out, "endif()")?;
120 writeln!(out)?;
121
122 writeln!(out, "# Platform-specific libraries required by libcsynta.")?;
124 writeln!(out, "if(UNIX AND NOT APPLE)")?;
125 writeln!(out, " set(_synta_platform_libs pthread dl m)")?;
126 writeln!(out, "elseif(APPLE)")?;
127 writeln!(out, " set(_synta_platform_libs pthread)")?;
128 writeln!(out, "elseif(WIN32)")?;
129 writeln!(out, " set(_synta_platform_libs ws2_32 userenv bcrypt)")?;
130 writeln!(out, "endif()")?;
131 writeln!(out)?;
132
133 let lib_type = if config.shared_library {
135 "SHARED"
136 } else {
137 "STATIC"
138 };
139
140 let target_for: std::collections::HashMap<&str, String> = modules
142 .iter()
143 .map(|m| (m.name.as_str(), to_pascal_case(&m.name)))
144 .collect();
145
146 for &idx in order {
147 let module = &modules[idx];
148 let stem = module_file_stem(&module.name);
149 let target = to_pascal_case(&module.name);
150
151 writeln!(out, "# ASN.1 module: {}", module.name)?;
152 writeln!(out, "add_library({} {}", target, lib_type)?;
153 writeln!(out, " {}.c", stem)?;
154 writeln!(out, ")")?;
155 writeln!(out, "target_include_directories({} PUBLIC", target)?;
156 writeln!(out, " $<BUILD_INTERFACE:${{CMAKE_CURRENT_LIST_DIR}}>")?;
157 writeln!(out, " $<INSTALL_INTERFACE:include>")?;
158 writeln!(out, ")")?;
159
160 let module_deps: Vec<&str> = module
162 .imports
163 .iter()
164 .filter_map(|imp| target_for.get(imp.module_name.as_str()).map(|t| t.as_str()))
165 .collect();
166
167 write!(out, "target_link_libraries({} PUBLIC", target)?;
168 for dep in &module_deps {
169 write!(out, "\n {}", dep)?;
170 }
171 writeln!(out, "\n Synta::Synta")?;
172 writeln!(out, " ${{_synta_platform_libs}}")?;
173 writeln!(out, ")")?;
174 writeln!(out)?;
175 }
176
177 Ok(out)
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::parse;
184
185 #[test]
186 fn test_single_module_cmake() {
187 let m = parse("MyModule DEFINITIONS ::= BEGIN Foo ::= INTEGER END").unwrap();
188 let out = generate_cmake(&[m], &[0], CMakeConfig::default()).unwrap();
189
190 assert!(out.contains("cmake_minimum_required(VERSION 3.10)"));
191 assert!(out.contains("project(MyModule C)"));
192 assert!(out.contains("CMAKE_C_STANDARD 99"));
193 assert!(out.contains("add_library(MyModule STATIC"));
194 assert!(out.contains("my_module.c"));
195 assert!(out.contains("Synta::Synta"));
196 assert!(out.contains("SYNTA_ROOT"));
197 }
198
199 #[test]
200 fn test_cmake_with_synta_root() {
201 let m = parse("Cert DEFINITIONS ::= BEGIN END").unwrap();
202 let config = CMakeConfig {
203 synta_root: Some("/opt/synta".to_string()),
204 shared_library: false,
205 };
206 let out = generate_cmake(&[m], &[0], config).unwrap();
207
208 assert!(out.contains("set(_synta_root \"/opt/synta\")"));
209 assert!(!out.contains("CACHE PATH"));
211 }
212
213 #[test]
214 fn test_cmake_shared_library() {
215 let m = parse("Foo DEFINITIONS ::= BEGIN END").unwrap();
216 let config = CMakeConfig {
217 shared_library: true,
218 ..Default::default()
219 };
220 let out = generate_cmake(&[m], &[0], config).unwrap();
221 assert!(out.contains("add_library(Foo SHARED"));
222 }
223
224 #[test]
225 fn test_multi_module_cmake_deps() {
226 let a = parse("ModA DEFINITIONS ::= BEGIN IMPORTS X FROM ModB; END").unwrap();
227 let b = parse("ModB DEFINITIONS ::= BEGIN END").unwrap();
228 let order = vec![1usize, 0usize];
230 let out = generate_cmake(&[a, b], &order, CMakeConfig::default()).unwrap();
231
232 assert!(out.contains("add_library(ModB STATIC"));
234 assert!(out.contains("add_library(ModA STATIC"));
236 assert!(out.contains("target_link_libraries(ModA PUBLIC\n ModB"));
238 }
239}