1mod cmake_parse;
2mod cmake_positional;
3pub mod command;
4mod command_scope;
5pub mod ros;
6mod token;
7
8use crate::CMakeListsTokens;
9
10pub use cmake_parse::CMakeParse;
11pub use cmake_positional::{CMakePositional, Keyword};
12use command::CommandParseError;
13
14pub use command::Command;
15pub use command_scope::{CommandScope, ToCommandScope};
16pub use ros::{AmentTargetDependencies, RosCommand};
17pub use token::{declarations_by_keywords, TextNodeDeclaration, Token, TokenDeclarations};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct RawCommand<'t> {
21 pub identifier: std::borrow::Cow<'t, [u8]>,
22 pub tokens: Vec<Token<'t>>,
23}
24
25pub struct Doc<'t> {
26 tokens: CMakeListsTokens<'t>,
27}
28
29impl<'t> Doc<'t> {
30 pub fn raw_commands<'a: 't>(&'a self) -> impl Iterator<Item = RawCommand<'t>> + 'a {
31 self.tokens.command_invocations().map(|ci| RawCommand {
32 identifier: ci.identifier(),
33 tokens: ci.to_text_nodes(),
34 })
35 }
36
37 pub fn ros_commands<'a: 't>(&'a self) -> impl Iterator<Item = RosCommand<'t>> + 'a {
38 self.raw_commands()
39 .filter_map(|raw| RosCommand::from_raw(&raw))
40 }
41
42 pub fn to_commands_iter<'a: 't>(
43 &'a self,
44 ) -> impl Iterator<Item = Result<Command<'t>, CommandParseError>> {
45 self.raw_commands()
46 .map(move |raw| (raw.identifier, raw.tokens))
47 .map(move |(identifier, tokens)| match &identifier[..] {
48 b"add_compile_definitions" => to_command(tokens, Command::AddCompileDefinitions),
49 b"add_compile_options" => to_command(tokens, Command::AddCompileOptions),
50 b"add_custom_command" => to_command(tokens, Command::AddCustomCommand),
51 b"add_custom_target" => to_command(tokens, Command::AddCustomTarget),
52 b"add_definitions" => to_command(tokens, Command::AddDefinitions),
53 b"add_dependencies" => to_command(tokens, Command::AddDependencies),
54 b"add_executable" => to_command(tokens, Command::AddExecutable),
55 b"add_library" => to_command(tokens, Command::AddLibrary),
56 b"add_link_options" => to_command(tokens, Command::AddLinkOptions),
57 b"add_subdirectory" => to_command(tokens, Command::AddSubdirectory),
58 b"add_test" => to_command(tokens, Command::AddTest),
59 b"aux_source_directory" => to_command(tokens, Command::AuxSourceDirectory),
60 b"build_command" => to_command(tokens, Command::BuildCommand),
61 b"create_test_sourcelist" => to_command(tokens, Command::CreateTestSourceList),
62 b"define_property" => to_command(tokens, Command::DefineProperty),
63 b"enable_language" => to_command(tokens, Command::EnableLanguage),
64 b"enable_testing" => Ok(Command::EnableTesting),
65 b"export" => to_command(tokens, Command::Export),
66 b"fltk_wrap_ui" => to_command(tokens, Command::FLTKWrapUI),
67 b"get_source_file_property" => to_command(tokens, Command::GetSourceFileProperty),
68 b"get_target_property" => to_command(tokens, Command::GetTargetProperty),
69 b"get_test_property" => to_command(tokens, Command::GetTestProperty),
70 b"include_directories" => to_command(tokens, Command::IncludeDirectories),
71 b"include_external_msproject" => {
72 to_command(tokens, Command::IncludeExternalMSProject)
73 }
74 b"include_regular_expression" => {
75 to_command(tokens, Command::IncludeRegularExpression)
76 }
77 b"install" => to_command(tokens, Command::Install),
78 b"link_directories" => to_command(tokens, Command::LinkDirectories),
79 b"link_libraries" => to_command(tokens, Command::LinkLibraries),
80 b"load_cache" => to_command(tokens, Command::LoadCache),
81 b"project" => to_command(tokens, Command::Project),
82 b"remove_definitions" => to_command(tokens, Command::RemoveDefinitions),
83 b"set_source_files_properties" => {
84 to_command(tokens, Command::SetSourceFileProperties)
85 }
86 b"set_target_properties" => to_command(tokens, Command::SetTargetProperties),
87 b"set_tests_properties" => to_command(tokens, Command::SetTestsProperties),
88 b"source_group" => to_command(tokens, Command::SourceGroup),
89 b"target_compile_definitions" => {
90 to_command(tokens, Command::TargetCompileDefinitions)
91 }
92 b"target_compile_features" => to_command(tokens, Command::TargetCompileFeatures),
93 b"target_compile_options" => to_command(tokens, Command::TargetCompileOptions),
94 b"target_include_directories" => {
95 to_command(tokens, Command::TargetIncludeDirectories)
96 }
97 b"target_link_directories" => to_command(tokens, Command::TargetLinkDirectories),
98 b"target_link_libraries" => to_command(tokens, Command::TargetLinkLibraries),
99 b"target_link_options" => to_command(tokens, Command::TargetLinkOptions),
100 b"target_precompile_headers" => {
101 to_command(tokens, Command::TargetPrecompileHeaders)
102 }
103 b"target_sources" => to_command(tokens, Command::TargetSources),
104 b"try_compile" => to_command(tokens, Command::TryCompile),
105 b"try_run" => to_command(tokens, Command::TryRun),
106 b"ctest_build" => to_command(tokens, Command::CTestBuild),
107 b"ctest_configure" => to_command(tokens, Command::CTestConfigure),
108 b"ctest_coverage" => to_command(tokens, Command::CTestCoverage),
109 b"ctest_empty_binary_directory" => {
110 to_command(tokens, Command::CTestEmptyBinaryDirectory)
111 }
112 b"ctest_memcheck" => to_command(tokens, Command::CTestMemCheck),
113 b"ctest_read_custom_files" => to_command(tokens, Command::CTestReadCustomFiles),
114 b"ctest_run_script" => to_command(tokens, Command::CTestRunScript),
115 b"ctest_sleep" => to_command(tokens, Command::CTestSleep),
116 b"ctest_start" => to_command(tokens, Command::CTestStart),
117 b"ctest_submit" => to_command(tokens, Command::CTestSubmit),
118 b"ctest_test" => to_command(tokens, Command::CTestTest),
119 b"ctest_update" => to_command(tokens, Command::CTestUpdate),
120 b"ctest_upload" => to_command(tokens, Command::CTestUpload),
121 b"build_name" => to_command(tokens, Command::BuildName),
122 b"exec_program" => to_command(tokens, Command::ExecProgram),
123 b"export_library_dependencies" => {
124 to_command(tokens, Command::ExportLibraryDependencies)
125 }
126 b"install_files" => to_command(tokens, Command::InstallFiles),
127 b"install_programs" => to_command(tokens, Command::InstallPrograms),
128 b"install_targets" => to_command(tokens, Command::InstallTargets),
129 b"load_command" => to_command(tokens, Command::LoadCommand),
130 b"make_directory" => to_command(tokens, Command::MakeDirectory),
131 b"output_required_files" => to_command(tokens, Command::OutputRequiredFiles),
132 b"qt_wrap_cpp" => to_command(tokens, Command::QtWrapCpp),
133 b"qt_wrap_ui" => to_command(tokens, Command::QtWrapUi),
134 b"remove" => to_command(tokens, Command::Remove),
135 b"subdir_depends" => to_command(tokens, Command::SubdirDepends),
136 b"subdirs" => to_command(tokens, Command::Subdirs),
137 b"use_mangled_mesa" => to_command(tokens, Command::UseMangledMesa),
138 b"utility_source" => to_command(tokens, Command::UtilitySource),
139 b"variable_requires" => to_command(tokens, Command::VariableRequires),
140 b"write_file" => to_command(tokens, Command::WriteFile),
141 b"block" => to_command(tokens, Command::Block),
142 b"break" => to_command(tokens, Command::Break),
143 b"cmake_host_system_information" => {
144 to_command(tokens, Command::CMakeHostSystemInformation)
145 }
146 b"cmake_language" => to_command(tokens, Command::CMakeLanguage),
147 b"cmake_minimum_required" => to_command(tokens, Command::CMakeMinimumRequired),
148 b"cmake_parse_arguments" => to_command(tokens, Command::CMakeParseArguments),
149 b"cmake_path" => to_command(tokens, Command::CMakePath),
150 b"cmake_policy" => to_command(tokens, Command::CMakePolicy),
151 b"configure_file" => to_command(tokens, Command::ConfigureFile),
152 b"continue" => to_command(tokens, Command::Continue),
153 b"else" => to_command(tokens, Command::Else),
154 b"elseif" => to_command(tokens, Command::ElseIf),
155 b"endblock" => to_command(tokens, Command::EndBlock),
156 b"endforeach" => to_command(tokens, Command::EndForEach),
157 b"endfunction" => to_command(tokens, Command::EndFunction),
158 b"endif" => to_command(tokens, Command::EndIf),
159 b"endmacro" => to_command(tokens, Command::EndMacro),
160 b"endwhile" => to_command(tokens, Command::EndWhile),
161 b"execute_process" => to_command(tokens, Command::ExecuteProcess),
162 b"file" => to_command(tokens, Command::File),
163 b"find_file" => to_command(tokens, Command::FindFile),
164 b"find_library" => to_command(tokens, Command::FindLibrary),
165 b"find_package" => to_command(tokens, Command::FindPackage),
166 b"find_path" => to_command(tokens, Command::FindPath),
167 b"find_program" => to_command(tokens, Command::FindProgram),
168 b"foreach" => to_command(tokens, Command::ForEach),
169 b"function" => to_command(tokens, Command::Function),
170 b"get_cmake_property" => to_command(tokens, Command::GetCMakeProperty),
171 b"get_directory_property" => to_command(tokens, Command::GetDirectoryProperty),
172 b"get_filename_component" => to_command(tokens, Command::GetFilenameComponent),
173 b"get_property" => to_command(tokens, Command::GetProperty),
174 b"if" => to_command(tokens, Command::If),
175 b"include" => to_command(tokens, Command::Include),
176 b"include_guard" => to_command(tokens, Command::IncludeGuard),
177 b"list" => to_command(tokens, Command::List),
178 b"macro" => to_command(tokens, Command::Macro),
179 b"mark_as_advanced" => to_command(tokens, Command::MarkAsAdvanced),
180 b"math" => to_command(tokens, Command::Math),
181 b"message" => to_command(tokens, Command::Message),
182 b"option" => to_command(tokens, Command::Option),
183 b"return" => to_command(tokens, Command::Return),
184 b"separate_arguments" => to_command(tokens, Command::SeparateArguments),
185 b"set" => to_command(tokens, Command::Set),
186 b"set_directory_properties" => to_command(tokens, Command::SetDirectoryProperties),
187 b"set_property" => to_command(tokens, Command::SetProperty),
188 b"site_name" => to_command(tokens, Command::SiteName),
189 b"string" => to_command(tokens, Command::String),
190 b"unset" => to_command(tokens, Command::Unset),
191 b"variable_watch" => to_command(tokens, Command::VariableWatch),
192 b"while" => to_command(tokens, Command::While),
193 unknown => Err(CommandParseError::UnknownCommand(
194 String::from_utf8_lossy(unknown).to_string(),
195 )),
196 })
197 }
198
199 pub fn commands<'a: 't>(&'a self) -> Result<Vec<Command<'t>>, CommandParseError> {
200 self.to_commands_iter().collect()
201 }
202}
203
204impl<'t> From<CMakeListsTokens<'t>> for Doc<'t> {
205 fn from(tokens: CMakeListsTokens<'t>) -> Self {
206 Self { tokens }
207 }
208}
209fn to_command<'t, C, F>(tokens: Vec<Token<'t>>, f: F) -> Result<Command<'t>, CommandParseError>
210where
211 C: CMakeParse<'t>,
212 F: Fn(Box<C>) -> Command<'t>,
213{
214 CMakeParse::complete(&tokens).map(f)
215}
216
217#[cfg(test)]
218mod tests {
219 use super::{Doc, RosCommand};
220 use crate::parse_cmakelists;
221
222 #[test]
223 fn raw_commands_expose_unknown_and_known_commands() {
224 let src = br#"
225find_package(rclcpp REQUIRED)
226ament_target_dependencies(my_node rclcpp std_msgs)
227ament_package()
228foo_custom_macro(bar baz)
229"#;
230
231 let parsed = parse_cmakelists(src).unwrap();
232 let doc = Doc::from(parsed);
233 let raw = doc.raw_commands().collect::<Vec<_>>();
234
235 assert_eq!(raw.len(), 4);
236 assert_eq!(raw[0].identifier.as_ref(), b"find_package");
237 assert_eq!(raw[1].identifier.as_ref(), b"ament_target_dependencies");
238 assert_eq!(raw[2].identifier.as_ref(), b"ament_package");
239 assert_eq!(raw[3].identifier.as_ref(), b"foo_custom_macro");
240 assert_eq!(
241 raw[1]
242 .tokens
243 .iter()
244 .map(ToString::to_string)
245 .collect::<Vec<_>>(),
246 vec!["my_node", "rclcpp", "std_msgs"]
247 );
248 }
249
250 #[test]
251 fn ros_commands_parse_key_ament_and_catkin_commands() {
252 let src = br#"
253ament_target_dependencies(my_node SYSTEM rclcpp PUBLIC std_msgs INTERFACE sensor_msgs)
254catkin_package()
255ament_package()
256foo_custom_macro(bar baz)
257"#;
258
259 let parsed = parse_cmakelists(src).unwrap();
260 let doc = Doc::from(parsed);
261 let ros = doc.ros_commands().collect::<Vec<_>>();
262
263 assert_eq!(ros.len(), 3);
264 match &ros[0] {
265 RosCommand::AmentTargetDependencies(dep) => {
266 assert_eq!(dep.target.to_string(), "my_node");
267 assert_eq!(
268 dep.dependencies
269 .iter()
270 .map(ToString::to_string)
271 .collect::<Vec<_>>(),
272 vec!["rclcpp", "std_msgs", "sensor_msgs"]
273 );
274 }
275 other => panic!("unexpected first ros command: {other:?}"),
276 }
277 assert!(matches!(ros[1], RosCommand::CatkinPackage));
278 assert!(matches!(ros[2], RosCommand::AmentPackage));
279 }
280
281 #[test]
282 fn commands_still_error_on_unknown_command() {
283 let src = br#"
284find_package(rclcpp REQUIRED)
285ament_package()
286"#;
287
288 let parsed = parse_cmakelists(src).unwrap();
289 let doc = Doc::from(parsed);
290 let err = doc.commands().unwrap_err();
291 assert_eq!(err.to_string(), "unknown command: ament_package");
292 }
293}