1#[cfg(all(not(target_arch = "wasm32"), feature = "cli"))]
8use std::fs;
9#[cfg(all(not(target_arch = "wasm32"), feature = "cli"))]
10use std::path::Path;
11use std::path::PathBuf;
12use std::sync::OnceLock;
13
14use indexmap::{IndexMap, IndexSet};
15
16#[cfg(all(not(target_arch = "wasm32"), feature = "cli"))]
17use crate::config::file::{detect_config_format, ConfigFileFormat};
18use crate::error::{Error, Result};
19
20use super::{
21 has_ascii_uppercase, CommandForm, CommandFormOverride, CommandSpec, CommandSpecOverride,
22 KwargSpec, KwargSpecOverride, LayoutOverrides, LayoutOverridesOverride, SpecFile, SpecMetadata,
23 SpecOverrideFile,
24};
25
26const BUILTINS_PATH: &str = "src/spec/builtins.yaml";
51const BUILTINS_MSGPACK: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins.msgpack"));
52const MODULES_PATH: &str = "src/spec/modules.yaml";
53const MODULES_MSGPACK: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/modules.msgpack"));
54
55#[derive(Debug, Clone)]
76pub struct CommandRegistry {
77 metadata: SpecMetadata,
78 builtin_commands: IndexSet<String>,
79 commands: IndexMap<String, CommandSpec>,
80 fallback: CommandSpec,
81}
82
83impl CommandRegistry {
84 pub fn load() -> Result<Self> {
90 Self::load_builtins_impl()
91 }
92
93 pub fn builtins() -> &'static Self {
99 static BUILTINS: OnceLock<CommandRegistry> = OnceLock::new();
100 BUILTINS.get_or_init(|| {
101 Self::load_builtins_impl()
102 .expect("embedded built-in command registry should deserialize")
103 })
104 }
105
106 #[cfg(all(not(target_arch = "wasm32"), feature = "cli"))]
107 fn load_builtins_impl() -> Result<Self> {
108 Self::from_builtins_and_overrides(None::<&Path>)
109 }
110
111 #[cfg(any(target_arch = "wasm32", not(feature = "cli")))]
112 fn load_builtins_impl() -> Result<Self> {
113 Ok(Self::from_spec_file(parse_embedded_spec()?))
114 }
115
116 #[cfg(all(not(target_arch = "wasm32"), feature = "cli"))]
118 pub fn from_builtins_and_overrides(path: Option<impl AsRef<Path>>) -> Result<Self> {
119 let mut registry = Self::from_spec_file(parse_embedded_spec()?);
120
121 if let Some(path) = path {
122 registry.merge_override_file(path.as_ref())?;
123 }
124
125 Ok(registry)
126 }
127
128 pub(crate) fn from_spec_file(mut spec_file: SpecFile) -> Self {
130 normalize_spec_file(&mut spec_file);
131 let builtin_commands = spec_file.commands.keys().cloned().collect();
132 Self {
133 metadata: spec_file.metadata,
134 builtin_commands,
135 commands: spec_file.commands,
136 fallback: CommandSpec::Single(CommandForm::default()),
137 }
138 }
139
140 pub fn merge_toml_overrides(&mut self, toml_source: &str) -> Result<()> {
169 let mut overrides: SpecOverrideFile = toml::from_str(toml_source)
170 .map_err(|e| Error::Formatter(format!("spec TOML error: {e}")))?;
171 self.apply_overrides(&mut overrides);
172 Ok(())
173 }
174
175 pub fn merge_yaml_overrides(&mut self, yaml_source: &str) -> Result<()> {
203 let mut overrides: SpecOverrideFile = serde_yaml::from_str(yaml_source)
204 .map_err(|e| Error::Formatter(format!("spec YAML error: {e}")))?;
205 self.apply_overrides(&mut overrides);
206 Ok(())
207 }
208
209 fn apply_overrides(&mut self, overrides: &mut SpecOverrideFile) {
210 normalize_override_file(overrides);
211 let commands = std::mem::take(&mut overrides.commands);
212 for (name, override_spec) in commands {
213 match self.commands.get_mut(&name) {
214 Some(existing) => merge_command_spec(existing, override_spec),
215 None => {
216 self.commands.insert(name, override_spec.into_full_spec());
217 }
218 }
219 }
220 }
221
222 #[cfg(all(not(target_arch = "wasm32"), feature = "cli"))]
232 #[cfg_attr(docsrs, doc(cfg(feature = "cli")))]
233 pub fn merge_override_file(&mut self, path: &Path) -> Result<()> {
234 let source = fs::read_to_string(path)?;
235 self.merge_override_source(&source, path.to_path_buf(), detect_config_format(path)?)
236 }
237
238 #[cfg(all(not(target_arch = "wasm32"), feature = "cli"))]
248 #[cfg_attr(docsrs, doc(cfg(feature = "cli")))]
249 pub fn merge_override_str(&mut self, source: &str, path: impl Into<PathBuf>) -> Result<()> {
250 self.merge_override_source(source, path.into(), ConfigFileFormat::Toml)
251 }
252
253 #[cfg(all(not(target_arch = "wasm32"), feature = "cli"))]
254 fn merge_override_source(
255 &mut self,
256 source: &str,
257 path: PathBuf,
258 format: ConfigFileFormat,
259 ) -> Result<()> {
260 let mut overrides: SpecOverrideFile = match format {
261 ConfigFileFormat::Toml => toml::from_str(source).map_err(|toml_err| {
262 let (line, column) = crate::config::file::toml_line_col(
263 source,
264 toml_err.span().map(|span| span.start),
265 );
266 Error::Spec(crate::error::SpecError::new(
267 path.clone(),
268 format.as_str(),
269 toml_err.to_string(),
270 line,
271 column,
272 ))
273 })?,
274 ConfigFileFormat::Yaml => serde_yaml::from_str(source).map_err(|yaml_err| {
275 let location = yaml_err.location();
276 Error::Spec(crate::error::SpecError::new(
277 path.clone(),
278 format.as_str(),
279 yaml_err.to_string(),
280 location.as_ref().map(|loc| loc.line()),
281 location.as_ref().map(|loc| loc.column()),
282 ))
283 })?,
284 };
285 normalize_override_file(&mut overrides);
286
287 for (name, override_spec) in overrides.commands {
288 match self.commands.get_mut(&name) {
289 Some(existing) => merge_command_spec(existing, override_spec),
290 None => {
291 self.commands.insert(name, override_spec.into_full_spec());
292 }
293 }
294 }
295
296 Ok(())
297 }
298
299 pub fn get(&self, command_name: &str) -> &CommandSpec {
308 if let Some(spec) = self.commands.get(command_name) {
309 return spec;
310 }
311
312 if !has_ascii_uppercase(command_name) {
313 return &self.fallback;
314 }
315
316 self.commands
317 .get(&command_name.to_ascii_lowercase())
318 .unwrap_or(&self.fallback)
319 }
320
321 pub fn contains(&self, command_name: &str) -> bool {
324 self.commands.contains_key(command_name)
325 || (has_ascii_uppercase(command_name)
326 && self
327 .commands
328 .contains_key(&command_name.to_ascii_lowercase()))
329 }
330
331 pub fn contains_builtin(&self, command_name: &str) -> bool {
333 self.builtin_commands.contains(command_name)
334 || (has_ascii_uppercase(command_name)
335 && self
336 .builtin_commands
337 .contains(&command_name.to_ascii_lowercase()))
338 }
339
340 pub fn audited_cmake_version(&self) -> &str {
346 &self.metadata.cmake_version
347 }
348
349 pub fn builtin_command_names(&self) -> impl Iterator<Item = &str> {
355 self.builtin_commands.iter().map(String::as_str)
356 }
357}
358
359fn parse_embedded_spec() -> Result<SpecFile> {
368 let mut spec = parse_msgpack_spec(BUILTINS_MSGPACK, BUILTINS_PATH)?;
369 let modules = parse_msgpack_spec(MODULES_MSGPACK, MODULES_PATH)?;
370 spec.commands.extend(modules.commands);
371 Ok(spec)
372}
373
374fn parse_msgpack_spec(bytes: &[u8], path: &str) -> Result<SpecFile> {
375 let mut spec: SpecFile = rmp_serde::from_slice(bytes).map_err(|source| {
376 Error::Spec(crate::error::SpecError::new(
377 PathBuf::from(path),
378 "MessagePack",
379 source.to_string(),
380 None,
381 None,
382 ))
383 })?;
384 normalize_spec_file(&mut spec);
385 Ok(spec)
386}
387
388fn normalize_spec_file(spec: &mut SpecFile) {
389 spec.commands = std::mem::take(&mut spec.commands)
390 .into_iter()
391 .map(|(name, mut command)| {
392 normalize_command_spec(&mut command);
393 (name.to_ascii_lowercase(), command)
394 })
395 .collect();
396}
397
398fn normalize_override_file(spec: &mut SpecOverrideFile) {
399 spec.commands = std::mem::take(&mut spec.commands)
400 .into_iter()
401 .map(|(name, mut command)| {
402 normalize_command_override(&mut command);
403 (name.to_ascii_lowercase(), command)
404 })
405 .collect();
406}
407
408fn normalize_command_spec(spec: &mut CommandSpec) {
409 match spec {
410 CommandSpec::Single(form) => normalize_form(form),
411 CommandSpec::Discriminated { forms, fallback } => {
412 *forms = std::mem::take(forms)
413 .into_iter()
414 .map(|(name, mut form)| {
415 normalize_form(&mut form);
416 (name.to_ascii_uppercase(), form)
417 })
418 .collect();
419
420 if let Some(fallback) = fallback {
421 normalize_form(fallback);
422 }
423 }
424 }
425}
426
427fn normalize_command_override(spec: &mut CommandSpecOverride) {
428 match spec {
429 CommandSpecOverride::Single(form) => normalize_form_override(form),
430 CommandSpecOverride::Discriminated { forms, fallback } => {
431 *forms = std::mem::take(forms)
432 .into_iter()
433 .map(|(name, mut form)| {
434 normalize_form_override(&mut form);
435 (name.to_ascii_uppercase(), form)
436 })
437 .collect();
438
439 if let Some(fallback) = fallback {
440 normalize_form_override(fallback);
441 }
442 }
443 }
444}
445
446fn normalize_form(form: &mut CommandForm) {
447 form.kwargs = std::mem::take(&mut form.kwargs)
448 .into_iter()
449 .map(|(name, mut kwarg)| {
450 normalize_kwarg(&mut kwarg);
451 (name.to_ascii_uppercase(), kwarg)
452 })
453 .collect();
454
455 form.flags = std::mem::take(&mut form.flags)
456 .into_iter()
457 .map(|flag| flag.to_ascii_uppercase())
458 .collect();
459}
460
461fn normalize_form_override(form: &mut CommandFormOverride) {
462 form.kwargs = std::mem::take(&mut form.kwargs)
463 .into_iter()
464 .map(|(name, mut kwarg)| {
465 normalize_kwarg_override(&mut kwarg);
466 (name.to_ascii_uppercase(), kwarg)
467 })
468 .collect();
469
470 form.flags = std::mem::take(&mut form.flags)
471 .into_iter()
472 .map(|flag| flag.to_ascii_uppercase())
473 .collect();
474}
475
476fn normalize_kwarg(spec: &mut KwargSpec) {
477 spec.kwargs = std::mem::take(&mut spec.kwargs)
478 .into_iter()
479 .map(|(name, mut kwarg)| {
480 normalize_kwarg(&mut kwarg);
481 (name.to_ascii_uppercase(), kwarg)
482 })
483 .collect();
484
485 spec.flags = std::mem::take(&mut spec.flags)
486 .into_iter()
487 .map(|flag| flag.to_ascii_uppercase())
488 .collect();
489}
490
491fn normalize_kwarg_override(spec: &mut KwargSpecOverride) {
492 spec.kwargs = std::mem::take(&mut spec.kwargs)
493 .into_iter()
494 .map(|(name, mut kwarg)| {
495 normalize_kwarg_override(&mut kwarg);
496 (name.to_ascii_uppercase(), kwarg)
497 })
498 .collect();
499
500 spec.flags = std::mem::take(&mut spec.flags)
501 .into_iter()
502 .map(|flag| flag.to_ascii_uppercase())
503 .collect();
504}
505
506fn merge_command_spec(base: &mut CommandSpec, override_spec: CommandSpecOverride) {
507 match (base, override_spec) {
508 (CommandSpec::Single(base_form), CommandSpecOverride::Single(override_form)) => {
509 merge_form(base_form, override_form);
510 }
511 (
512 CommandSpec::Discriminated {
513 forms: base_forms,
514 fallback: base_fallback,
515 },
516 CommandSpecOverride::Discriminated {
517 forms: override_forms,
518 fallback: override_fallback,
519 },
520 ) => {
521 for (name, override_form) in override_forms {
522 match base_forms.get_mut(&name) {
523 Some(base_form) => merge_form(base_form, override_form),
524 None => {
525 base_forms.insert(name, override_form.into_full_form());
526 }
527 }
528 }
529
530 if let Some(override_fallback) = override_fallback {
531 match base_fallback {
532 Some(base_fallback) => merge_form(base_fallback, override_fallback),
533 None => {
534 *base_fallback = Some(override_fallback.into_full_form());
535 }
536 }
537 }
538 }
539 (base_spec, override_spec) => {
540 *base_spec = override_spec.into_full_spec();
541 }
542 }
543}
544
545fn merge_form(base: &mut CommandForm, override_form: CommandFormOverride) {
546 if let Some(pargs) = override_form.pargs {
547 base.pargs = pargs;
548 }
549
550 merge_flags(&mut base.flags, override_form.flags);
551
552 for (name, override_kwarg) in override_form.kwargs {
553 match base.kwargs.get_mut(&name) {
554 Some(base_kwarg) => merge_kwarg(base_kwarg, override_kwarg),
555 None => {
556 base.kwargs.insert(name, override_kwarg.into_full_spec());
557 }
558 }
559 }
560
561 if let Some(layout) = override_form.layout {
562 merge_layout(
563 base.layout.get_or_insert_with(LayoutOverrides::default),
564 layout,
565 );
566 }
567}
568
569fn merge_kwarg(base: &mut KwargSpec, override_kwarg: KwargSpecOverride) {
570 if let Some(nargs) = override_kwarg.nargs {
571 base.nargs = nargs;
572 }
573
574 merge_flags(&mut base.flags, override_kwarg.flags);
575
576 for (name, nested_override) in override_kwarg.kwargs {
577 match base.kwargs.get_mut(&name) {
578 Some(base_nested) => merge_kwarg(base_nested, nested_override),
579 None => {
580 base.kwargs.insert(name, nested_override.into_full_spec());
581 }
582 }
583 }
584}
585
586fn merge_layout(base: &mut LayoutOverrides, override_layout: LayoutOverridesOverride) {
587 let LayoutOverridesOverride {
591 line_width,
592 tab_size,
593 dangle_parens,
594 always_wrap,
595 max_pargs_hwrap,
596 wrap_after_first_arg,
597 continuation_align,
598 } = override_layout;
599
600 if let Some(value) = line_width {
601 base.line_width = Some(value);
602 }
603 if let Some(value) = tab_size {
604 base.tab_size = Some(value);
605 }
606 if let Some(value) = dangle_parens {
607 base.dangle_parens = Some(value);
608 }
609 if let Some(value) = always_wrap {
610 base.always_wrap = Some(value);
611 }
612 if let Some(value) = max_pargs_hwrap {
613 base.max_pargs_hwrap = Some(value);
614 }
615 if let Some(value) = wrap_after_first_arg {
616 base.wrap_after_first_arg = Some(value);
617 }
618 if let Some(value) = continuation_align {
619 base.continuation_align = Some(value);
620 }
621}
622
623fn merge_flags(base: &mut IndexSet<String>, override_flags: IndexSet<String>) {
624 for flag in override_flags {
625 base.insert(flag);
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632 use crate::spec::NArgs;
633 use std::fs;
634
635 #[test]
636 fn registry_has_target_link_libraries_keywords() {
637 let registry = CommandRegistry::load().unwrap();
638 let CommandSpec::Single(form) = registry.get("target_link_libraries") else {
639 panic!()
640 };
641 assert!(form.kwargs.contains_key("PUBLIC"));
642 assert!(form.kwargs.contains_key("PRIVATE"));
643 assert!(form.kwargs.contains_key("INTERFACE"));
644 }
645
646 #[test]
647 fn registry_has_install_forms() {
648 let registry = CommandRegistry::load().unwrap();
649 assert!(matches!(
650 registry.get("install"),
651 CommandSpec::Discriminated { .. }
652 ));
653 }
654
655 #[test]
656 fn registry_unknown_command_uses_fallback() {
657 let registry = CommandRegistry::load().unwrap();
658 let spec = registry.get("my_unknown_command");
659 let CommandSpec::Single(form) = spec else {
660 panic!()
661 };
662 assert_eq!(form.pargs, NArgs::ZeroOrMore);
663 assert!(form.kwargs.is_empty());
664 assert!(form.flags.is_empty());
665 }
666
667 #[test]
668 fn registry_knows_builtin_surface() {
669 let registry = CommandRegistry::load().unwrap();
670 assert!(registry.contains_builtin("cmake_minimum_required"));
671 assert!(registry.contains_builtin("target_sources"));
672 assert!(registry.contains_builtin("while"));
673 assert!(registry.contains_builtin("external_project_add"));
674 }
675
676 #[test]
677 fn registry_reports_audited_cmake_version() {
678 let registry = CommandRegistry::load().unwrap();
679 assert_eq!(registry.audited_cmake_version(), "4.3.1");
680 }
681
682 #[test]
683 fn registry_knows_project_43_keywords() {
684 let registry = CommandRegistry::load().unwrap();
685 let CommandSpec::Single(form) = registry.get("project") else {
686 panic!()
687 };
688 assert!(form.flags.contains("COMPAT_VERSION"));
689 assert!(form.flags.contains("SPDX_LICENSE"));
690 }
691
692 #[test]
693 fn registry_knows_export_package_info_form() {
694 let registry = CommandRegistry::load().unwrap();
695 let CommandSpec::Discriminated { .. } = registry.get("export") else {
696 panic!()
697 };
698 let form = registry.get("export").form_for(Some("PACKAGE_INFO"));
699 assert_eq!(form.pargs, NArgs::Fixed(1));
700 assert!(form.kwargs.contains_key("EXPORT"));
701 assert!(form.kwargs.contains_key("CXX_MODULES_DIRECTORY"));
702 }
703
704 #[test]
705 fn registry_knows_install_package_info_form() {
706 let registry = CommandRegistry::load().unwrap();
707 let form = registry.get("install").form_for(Some("PACKAGE_INFO"));
708 assert_eq!(form.pargs, NArgs::Fixed(1));
709 assert!(form.kwargs.contains_key("DESTINATION"));
710 assert!(form.kwargs.contains_key("COMPAT_VERSION"));
711 }
712
713 #[test]
714 fn registry_knows_install_export_namespace_keyword() {
715 let registry = CommandRegistry::load().unwrap();
716 let form = registry.get("install").form_for(Some("EXPORT"));
717 assert!(form.kwargs.contains_key("DESTINATION"));
718 assert!(form.kwargs.contains_key("NAMESPACE"));
719 assert!(form.kwargs.contains_key("FILE"));
720 assert!(form.flags.contains("EXCLUDE_FROM_ALL"));
721 }
722
723 #[test]
724 fn registry_knows_install_targets_export_and_includes_sections() {
725 let registry = CommandRegistry::load().unwrap();
726 let form = registry.get("install").form_for(Some("TARGETS"));
727 assert!(form.kwargs.contains_key("EXPORT"));
728 assert!(form.kwargs.contains_key("INCLUDES"));
729 assert!(form
730 .kwargs
731 .get("INCLUDES")
732 .is_some_and(|spec| spec.kwargs.contains_key("DESTINATION")));
733 assert!(form.kwargs.contains_key("RUNTIME_DEPENDENCY_SET"));
734 }
735
736 #[test]
737 fn install_targets_artifact_kinds_are_kwargs_with_subgroups() {
738 let registry = CommandRegistry::load().unwrap();
739 let form = registry.get("install").form_for(Some("TARGETS"));
740
741 for kind in [
742 "ARCHIVE",
743 "LIBRARY",
744 "RUNTIME",
745 "OBJECTS",
746 "FRAMEWORK",
747 "BUNDLE",
748 "PRIVATE_HEADER",
749 "PUBLIC_HEADER",
750 "RESOURCE",
751 "FILE_SET",
752 "CXX_MODULES_BMI",
753 ] {
754 let spec = form
755 .kwargs
756 .get(kind)
757 .unwrap_or_else(|| panic!("install(TARGETS) missing artifact kind {kind}"));
758 for sub in [
759 "DESTINATION",
760 "PERMISSIONS",
761 "CONFIGURATIONS",
762 "COMPONENT",
763 "NAMELINK_COMPONENT",
764 ] {
765 assert!(
766 spec.kwargs.contains_key(sub),
767 "{kind} missing subkwarg {sub}"
768 );
769 }
770 for flag in [
771 "OPTIONAL",
772 "EXCLUDE_FROM_ALL",
773 "NAMELINK_ONLY",
774 "NAMELINK_SKIP",
775 ] {
776 assert!(spec.flags.contains(flag), "{kind} missing subflag {flag}");
777 }
778 assert!(
779 !form.flags.contains(kind),
780 "{kind} should not appear as an outer flag"
781 );
782 }
783 }
784
785 #[test]
786 fn install_targets_file_set_takes_positional_set_name() {
787 let registry = CommandRegistry::load().unwrap();
788 let form = registry.get("install").form_for(Some("TARGETS"));
789 let file_set = form.kwargs.get("FILE_SET").unwrap();
790 assert_eq!(file_set.nargs, crate::spec::NArgs::Fixed(1));
791 }
792
793 #[test]
794 fn install_targets_artifact_option_flags_are_not_outer_flags() {
795 let registry = CommandRegistry::load().unwrap();
796 let form = registry.get("install").form_for(Some("TARGETS"));
797 for flag in [
798 "OPTIONAL",
799 "EXCLUDE_FROM_ALL",
800 "NAMELINK_ONLY",
801 "NAMELINK_SKIP",
802 ] {
803 assert!(
804 !form.flags.contains(flag),
805 "{flag} should not appear at the outer TARGETS level"
806 );
807 }
808 }
809
810 #[test]
811 fn install_targets_runtime_dependencies_is_kwarg_group() {
812 let registry = CommandRegistry::load().unwrap();
813 let form = registry.get("install").form_for(Some("TARGETS"));
814 let rd = form.kwargs.get("RUNTIME_DEPENDENCIES").unwrap();
815 for sub in [
816 "DIRECTORIES",
817 "PRE_INCLUDE_REGEXES",
818 "PRE_EXCLUDE_REGEXES",
819 "POST_INCLUDE_REGEXES",
820 "POST_EXCLUDE_REGEXES",
821 "POST_INCLUDE_FILES",
822 "POST_EXCLUDE_FILES",
823 ] {
824 assert!(
825 rd.kwargs.contains_key(sub),
826 "RUNTIME_DEPENDENCIES missing subkwarg {sub}"
827 );
828 }
829 }
830
831 #[test]
832 fn install_imported_runtime_artifacts_artifact_kinds_are_kwargs() {
833 let registry = CommandRegistry::load().unwrap();
834 let form = registry
835 .get("install")
836 .form_for(Some("IMPORTED_RUNTIME_ARTIFACTS"));
837
838 for kind in ["LIBRARY", "RUNTIME", "FRAMEWORK", "BUNDLE"] {
839 let spec = form
840 .kwargs
841 .get(kind)
842 .unwrap_or_else(|| panic!("IMPORTED_RUNTIME_ARTIFACTS missing {kind}"));
843 for sub in ["DESTINATION", "PERMISSIONS", "CONFIGURATIONS", "COMPONENT"] {
844 assert!(
845 spec.kwargs.contains_key(sub),
846 "{kind} missing subkwarg {sub}"
847 );
848 }
849 for flag in ["OPTIONAL", "EXCLUDE_FROM_ALL"] {
850 assert!(spec.flags.contains(flag), "{kind} missing subflag {flag}");
851 }
852 assert!(!form.flags.contains(kind));
853 }
854 }
855
856 #[test]
857 fn install_files_has_type_rename_and_exclude_from_all() {
858 let registry = CommandRegistry::load().unwrap();
859 let form = registry.get("install").form_for(Some("FILES"));
860 assert!(form.kwargs.contains_key("TYPE"));
861 assert!(form.kwargs.contains_key("RENAME"));
862 assert!(form.flags.contains("EXCLUDE_FROM_ALL"));
863 }
864
865 #[test]
866 fn install_directory_has_full_option_coverage() {
867 let registry = CommandRegistry::load().unwrap();
868 let form = registry.get("install").form_for(Some("DIRECTORY"));
869 for kw in [
870 "TYPE",
871 "DESTINATION",
872 "FILE_PERMISSIONS",
873 "DIRECTORY_PERMISSIONS",
874 "CONFIGURATIONS",
875 "COMPONENT",
876 "PATTERN",
877 "REGEX",
878 ] {
879 assert!(form.kwargs.contains_key(kw), "DIRECTORY missing kwarg {kw}");
880 }
881 assert!(
884 !form.kwargs.contains_key("PERMISSIONS"),
885 "PERMISSIONS must not be a top-level DIRECTORY kwarg"
886 );
887 for flag in [
888 "OPTIONAL",
889 "USE_SOURCE_PERMISSIONS",
890 "MESSAGE_NEVER",
891 "EXCLUDE_FROM_ALL",
892 "FILES_MATCHING",
893 ] {
894 assert!(form.flags.contains(flag), "DIRECTORY missing flag {flag}");
895 }
896 }
897
898 #[test]
899 fn install_directory_pattern_and_regex_open_subgroup() {
900 let registry = CommandRegistry::load().unwrap();
901 let form = registry.get("install").form_for(Some("DIRECTORY"));
902 for name in ["PATTERN", "REGEX"] {
903 let spec = form.kwargs.get(name).unwrap();
904 assert_eq!(spec.nargs, crate::spec::NArgs::Fixed(1));
905 assert!(spec.flags.contains("EXCLUDE"), "{name} missing EXCLUDE");
906 assert!(
907 spec.kwargs.contains_key("PERMISSIONS"),
908 "{name} missing PERMISSIONS subkwarg"
909 );
910 }
911 }
912
913 #[test]
914 fn install_programs_mirrors_files_form() {
915 let registry = CommandRegistry::load().unwrap();
916 let form = registry.get("install").form_for(Some("PROGRAMS"));
917 for kw in [
918 "TYPE",
919 "DESTINATION",
920 "PERMISSIONS",
921 "CONFIGURATIONS",
922 "COMPONENT",
923 "RENAME",
924 ] {
925 assert!(form.kwargs.contains_key(kw), "PROGRAMS missing kwarg {kw}");
926 }
927 assert!(form.flags.contains("OPTIONAL"));
928 assert!(form.flags.contains("EXCLUDE_FROM_ALL"));
929 }
930
931 #[test]
932 fn install_script_and_code_accept_component_and_flags() {
933 let registry = CommandRegistry::load().unwrap();
934 for disc in ["SCRIPT", "CODE"] {
935 let form = registry.get("install").form_for(Some(disc));
936 assert!(
937 form.kwargs.contains_key("COMPONENT"),
938 "{disc} missing COMPONENT"
939 );
940 assert!(
941 form.flags.contains("ALL_COMPONENTS"),
942 "{disc} missing ALL_COMPONENTS"
943 );
944 assert!(
945 form.flags.contains("EXCLUDE_FROM_ALL"),
946 "{disc} missing EXCLUDE_FROM_ALL"
947 );
948 }
949 }
950
951 #[test]
952 fn install_runtime_dependency_set_has_filter_kwargs_and_artifact_kinds() {
953 let registry = CommandRegistry::load().unwrap();
954 let form = registry
955 .get("install")
956 .form_for(Some("RUNTIME_DEPENDENCY_SET"));
957
958 for sub in [
959 "DIRECTORIES",
960 "PRE_INCLUDE_REGEXES",
961 "PRE_EXCLUDE_REGEXES",
962 "POST_INCLUDE_REGEXES",
963 "POST_EXCLUDE_REGEXES",
964 "POST_INCLUDE_FILES",
965 "POST_EXCLUDE_FILES",
966 ] {
967 assert!(
968 form.kwargs.contains_key(sub),
969 "RUNTIME_DEPENDENCY_SET missing {sub}"
970 );
971 }
972
973 for kind in ["LIBRARY", "RUNTIME", "FRAMEWORK"] {
974 let spec = form
975 .kwargs
976 .get(kind)
977 .unwrap_or_else(|| panic!("RUNTIME_DEPENDENCY_SET missing {kind}"));
978 for k in [
979 "DESTINATION",
980 "PERMISSIONS",
981 "CONFIGURATIONS",
982 "COMPONENT",
983 "NAMELINK_COMPONENT",
984 ] {
985 assert!(spec.kwargs.contains_key(k), "{kind} missing subkwarg {k}");
986 }
987 for f in [
988 "OPTIONAL",
989 "EXCLUDE_FROM_ALL",
990 "NAMELINK_ONLY",
991 "NAMELINK_SKIP",
992 ] {
993 assert!(spec.flags.contains(f), "{kind} missing subflag {f}");
994 }
995 }
996 }
997
998 #[test]
999 fn registry_knows_cmake_language_trace_form() {
1000 let registry = CommandRegistry::load().unwrap();
1001 let form = registry.get("cmake_language").form_for(Some("TRACE"));
1002 assert!(form.flags.contains("ON"));
1003 assert!(form.flags.contains("OFF"));
1004 assert!(form.flags.contains("EXPAND"));
1005 }
1006
1007 #[test]
1008 fn registry_knows_cmake_pkg_config_import_keywords() {
1009 let registry = CommandRegistry::load().unwrap();
1010 let form = registry.get("cmake_pkg_config").form_for(Some("IMPORT"));
1011 assert!(form.kwargs.contains_key("NAME"));
1012 assert!(form.kwargs.contains_key("BIND_PC_REQUIRES"));
1013 }
1014
1015 #[test]
1016 fn registry_knows_file_archive_create_threads() {
1017 let registry = CommandRegistry::load().unwrap();
1018 let form = registry.get("file").form_for(Some("ARCHIVE_CREATE"));
1019 assert!(form.kwargs.contains_key("THREADS"));
1020 assert!(form.kwargs.contains_key("COMPRESSION_LEVEL"));
1021 }
1022
1023 #[test]
1024 fn registry_knows_file_strings_keywords() {
1025 let registry = CommandRegistry::load().unwrap();
1026 let form = registry.get("file").form_for(Some("STRINGS"));
1027 assert_eq!(form.pargs, NArgs::Fixed(2));
1028 assert!(form.kwargs.contains_key("REGEX"));
1029 assert!(form.kwargs.contains_key("LIMIT_COUNT"));
1030 }
1031
1032 #[test]
1033 fn registry_knows_cmake_package_config_helpers_commands() {
1034 let registry = CommandRegistry::load().unwrap();
1035 let configure = registry.get("configure_package_config_file").form_for(None);
1036 assert!(configure.kwargs.contains_key("INSTALL_DESTINATION"));
1037 assert!(configure.kwargs.contains_key("PATH_VARS"));
1038
1039 let version = registry
1040 .get("write_basic_package_version_file")
1041 .form_for(None);
1042 assert!(version.kwargs.contains_key("COMPATIBILITY"));
1043 assert!(version.kwargs.contains_key("VERSION"));
1044 }
1045
1046 #[test]
1047 fn registry_knows_utility_module_commands() {
1048 let registry = CommandRegistry::load().unwrap();
1049 assert_eq!(
1050 registry.get("cmake_dependent_option").form_for(None).pargs,
1051 NArgs::Fixed(5)
1052 );
1053 assert_eq!(
1054 registry.get("check_language").form_for(None).pargs,
1055 NArgs::Fixed(1)
1056 );
1057 assert_eq!(
1058 registry.get("check_include_file").form_for(None).pargs,
1059 NArgs::AtLeast(2)
1060 );
1061 assert_eq!(
1062 registry.get("check_compiler_flag").form_for(None).pargs,
1063 NArgs::Fixed(3)
1064 );
1065 assert_eq!(
1066 registry
1067 .get("check_objc_compiler_flag")
1068 .form_for(None)
1069 .pargs,
1070 NArgs::Fixed(2)
1071 );
1072 assert_eq!(
1073 registry.get("check_cxx_symbol_exists").form_for(None).pargs,
1074 NArgs::Fixed(3)
1075 );
1076 assert!(registry
1077 .get("cmake_push_check_state")
1078 .form_for(None)
1079 .flags
1080 .contains("RESET"));
1081 let print_props = registry.get("cmake_print_properties").form_for(None);
1082 assert!(print_props.kwargs.contains_key("TARGETS"));
1083 assert!(print_props.kwargs.contains_key("PROPERTIES"));
1084 let pie = registry.get("check_pie_supported").form_for(None);
1085 assert!(pie.kwargs.contains_key("OUTPUT_VARIABLE"));
1086 assert!(pie.kwargs.contains_key("LANGUAGES"));
1087 let source_compiles = registry.get("check_source_compiles").form_for(None);
1088 assert!(source_compiles.kwargs.contains_key("SRC_EXT"));
1089 assert!(source_compiles.kwargs.contains_key("FAIL_REGEX"));
1090 let find_dependency = registry.get("find_dependency").form_for(None);
1091 assert!(find_dependency.flags.contains("REQUIRED"));
1092 assert!(find_dependency.kwargs.contains_key("COMPONENTS"));
1093 }
1094
1095 #[test]
1096 fn registry_knows_supported_deprecated_module_commands() {
1097 let registry = CommandRegistry::load().unwrap();
1098 let version = registry
1099 .get("write_basic_config_version_file")
1100 .form_for(None);
1101 assert_eq!(version.pargs, NArgs::Fixed(1));
1102 assert!(version.kwargs.contains_key("COMPATIBILITY"));
1103 assert!(version.flags.contains("ARCH_INDEPENDENT"));
1104 assert_eq!(
1105 registry.get("check_cxx_accepts_flag").form_for(None).pargs,
1106 NArgs::Fixed(2)
1107 );
1108 }
1109
1110 #[test]
1111 fn registry_knows_fetchcontent_commands() {
1112 let registry = CommandRegistry::load().unwrap();
1113 let declare = registry.get("fetchcontent_declare").form_for(None);
1114 assert_eq!(declare.pargs, NArgs::Fixed(1));
1115 assert!(declare.flags.contains("EXCLUDE_FROM_ALL"));
1116 assert!(declare.kwargs.contains_key("FIND_PACKAGE_ARGS"));
1117
1118 let get_properties = registry.get("fetchcontent_getproperties").form_for(None);
1119 assert!(get_properties.kwargs.contains_key("SOURCE_DIR"));
1120 assert!(get_properties.kwargs.contains_key("BINARY_DIR"));
1121 assert!(get_properties.kwargs.contains_key("POPULATED"));
1122
1123 let populate = registry.get("fetchcontent_populate").form_for(None);
1124 assert!(populate.flags.contains("QUIET"));
1125 assert!(populate.kwargs.contains_key("SUBBUILD_DIR"));
1126 }
1127
1128 #[test]
1129 fn registry_knows_common_test_and_package_helper_modules() {
1130 let registry = CommandRegistry::load().unwrap();
1131
1132 let google_add = registry.get("gtest_add_tests").form_for(None);
1133 assert!(google_add.kwargs.contains_key("TARGET"));
1134 assert!(google_add.kwargs.contains_key("SOURCES"));
1135 assert!(google_add.flags.contains("SKIP_DEPENDENCY"));
1136
1137 let google_discover = registry.get("gtest_discover_tests").form_for(None);
1138 assert!(google_discover.kwargs.contains_key("DISCOVERY_MODE"));
1139 assert!(google_discover.kwargs.contains_key("XML_OUTPUT_DIR"));
1140 assert!(google_discover.flags.contains("NO_PRETTY_TYPES"));
1141
1142 assert_eq!(
1143 registry.get("processorcount").form_for(None).pargs,
1144 NArgs::Fixed(1)
1145 );
1146
1147 let fp_hsa = registry
1148 .get("find_package_handle_standard_args")
1149 .form_for(None);
1150 assert!(fp_hsa.flags.contains("DEFAULT_MSG"));
1151 assert!(fp_hsa.kwargs.contains_key("REQUIRED_VARS"));
1152 assert!(fp_hsa.kwargs.contains_key("VERSION_VAR"));
1153
1154 let fp_check = registry.get("find_package_check_version").form_for(None);
1155 assert_eq!(fp_check.pargs, NArgs::Fixed(2));
1156 assert!(fp_check.flags.contains("HANDLE_VERSION_RANGE"));
1157 }
1158
1159 #[test]
1160 fn registry_knows_externalproject_helper_commands() {
1161 let registry = CommandRegistry::load().unwrap();
1162 let step = registry.get("externalproject_add_step").form_for(None);
1163 assert_eq!(step.pargs, NArgs::Fixed(2));
1164 assert!(step.kwargs.contains_key("COMMAND"));
1165 assert!(step.kwargs.contains_key("DEPENDEES"));
1166 assert!(step.kwargs.contains_key("ENVIRONMENT_MODIFICATION"));
1167
1168 let targets = registry
1169 .get("externalproject_add_steptargets")
1170 .form_for(None);
1171 assert_eq!(targets.pargs, NArgs::AtLeast(2));
1172 assert!(targets.flags.contains("NO_DEPENDS"));
1173
1174 let deps = registry
1175 .get("externalproject_add_stepdependencies")
1176 .form_for(None);
1177 assert_eq!(deps.pargs, NArgs::AtLeast(3));
1178
1179 let props = registry.get("externalproject_get_property").form_for(None);
1180 assert_eq!(props.pargs, NArgs::AtLeast(2));
1181 }
1182
1183 #[test]
1184 fn registry_knows_packaging_and_find_helper_module_commands() {
1185 let registry = CommandRegistry::load().unwrap();
1186
1187 assert_eq!(
1188 registry.get("find_package_message").form_for(None).pargs,
1189 NArgs::Fixed(3)
1190 );
1191 assert_eq!(
1192 registry
1193 .get("select_library_configurations")
1194 .form_for(None)
1195 .pargs,
1196 NArgs::Fixed(1)
1197 );
1198
1199 let component = registry.get("cpack_add_component").form_for(None);
1200 assert!(component.flags.contains("HIDDEN"));
1201 assert!(component.kwargs.contains_key("DISPLAY_NAME"));
1202 assert!(component.kwargs.contains_key("DEPENDS"));
1203
1204 let group = registry.get("cpack_add_component_group").form_for(None);
1205 assert!(group.flags.contains("EXPANDED"));
1206 assert!(group.kwargs.contains_key("PARENT_GROUP"));
1207
1208 let downloads = registry.get("cpack_configure_downloads").form_for(None);
1209 assert_eq!(downloads.pargs, NArgs::Fixed(1));
1210 assert!(downloads.kwargs.contains_key("UPLOAD_DIRECTORY"));
1211 }
1212
1213 #[test]
1214 fn registry_knows_export_header_module_commands() {
1215 let registry = CommandRegistry::load().unwrap();
1216 let export_header = registry.get("generate_export_header").form_for(None);
1217 assert_eq!(export_header.pargs, NArgs::Fixed(1));
1218 assert!(export_header.flags.contains("DEFINE_NO_DEPRECATED"));
1219 assert!(export_header.kwargs.contains_key("EXPORT_FILE_NAME"));
1220 assert!(export_header.kwargs.contains_key("PREFIX_NAME"));
1221
1222 assert_eq!(
1223 registry
1224 .get("add_compiler_export_flags")
1225 .form_for(None)
1226 .pargs,
1227 NArgs::Optional
1228 );
1229 }
1230
1231 #[test]
1232 fn registry_knows_remaining_utility_module_commands() {
1233 let registry = CommandRegistry::load().unwrap();
1234
1235 for command in [
1236 "android_add_test_data",
1237 "add_file_dependencies",
1238 "cmake_add_fortran_subdirectory",
1239 "cmake_expand_imported_targets",
1240 "cmake_force_c_compiler",
1241 "cmake_force_cxx_compiler",
1242 "cmake_force_fortran_compiler",
1243 "ctest_coverage_collect_gcov",
1244 "copy_and_fixup_bundle",
1245 "fixup_bundle",
1246 "fixup_bundle_item",
1247 "verify_app",
1248 "verify_bundle_prerequisites",
1249 "verify_bundle_symlinks",
1250 "get_bundle_main_executable",
1251 "get_dotapp_dir",
1252 "get_bundle_and_executable",
1253 "get_bundle_all_executables",
1254 "get_bundle_keys",
1255 "get_item_key",
1256 "get_item_rpaths",
1257 "clear_bundle_keys",
1258 "set_bundle_key_values",
1259 "copy_resolved_framework_into_bundle",
1260 "copy_resolved_item_into_bundle",
1261 "cpack_ifw_add_package_resources",
1262 "cpack_ifw_add_repository",
1263 "cpack_ifw_configure_component",
1264 "cpack_ifw_configure_component_group",
1265 "cpack_ifw_update_repository",
1266 "cpack_ifw_configure_file",
1267 "csharp_set_windows_forms_properties",
1268 "csharp_set_designer_cs_properties",
1269 "csharp_set_xaml_cs_properties",
1270 "csharp_get_filename_keys",
1271 "csharp_get_filename_key_base",
1272 "csharp_get_dependentupon_name",
1273 "externaldata_expand_arguments",
1274 "externaldata_add_test",
1275 "externaldata_add_target",
1276 "fortrancinterface_header",
1277 "fortrancinterface_verify",
1278 "fetchcontent_setpopulated",
1279 "gnuinstalldirs_get_absolute_install_dir",
1280 "find_jar",
1281 "add_jar",
1282 "install_jar",
1283 "install_jar_exports",
1284 "export_jars",
1285 "create_javadoc",
1286 "create_javah",
1287 "install_jni_symlink",
1288 "swig_add_library",
1289 "swig_link_libraries",
1290 "print_enabled_features",
1291 "print_disabled_features",
1292 "set_feature_info",
1293 "set_package_info",
1294 ] {
1295 assert!(
1296 registry.contains_builtin(command),
1297 "missing built-in {command}"
1298 );
1299 }
1300
1301 assert_eq!(
1302 registry
1303 .get("ctest_coverage_collect_gcov")
1304 .form_for(None)
1305 .pargs,
1306 NArgs::ZeroOrMore
1307 );
1308 assert_eq!(
1309 registry
1310 .get("fortrancinterface_verify")
1311 .form_for(None)
1312 .pargs,
1313 NArgs::ZeroOrMore
1314 );
1315 assert_eq!(
1316 registry.get("add_jar").form_for(None).pargs,
1317 NArgs::AtLeast(2)
1318 );
1319 assert_eq!(
1320 registry
1321 .get("cpack_ifw_configure_file")
1322 .form_for(None)
1323 .pargs,
1324 NArgs::Fixed(2)
1325 );
1326 assert_eq!(
1327 registry
1328 .get("gnuinstalldirs_get_absolute_install_dir")
1329 .form_for(None)
1330 .pargs,
1331 NArgs::AtLeast(3)
1332 );
1333 }
1334
1335 #[test]
1336 fn registry_knows_string_json_43_modes() {
1337 let registry = CommandRegistry::load().unwrap();
1338 let form = registry.get("string").form_for(Some("JSON"));
1339 assert!(form.flags.contains("GET_RAW"));
1340 assert!(form.flags.contains("STRING_ENCODE"));
1341 assert!(form.kwargs.contains_key("ERROR_VARIABLE"));
1342 }
1343
1344 #[test]
1345 fn user_override_entries_merge_with_builtins() {
1346 let mut registry = CommandRegistry::load().unwrap();
1347 let overrides = r#"
1348[commands.target_link_libraries.layout]
1349always_wrap = true
1350
1351[commands.target_link_libraries.kwargs.LINKER_LANGUAGE]
1352nargs = 1
1353"#;
1354
1355 registry
1356 .merge_override_str(overrides, PathBuf::from("test-overrides.toml"))
1357 .unwrap();
1358
1359 let CommandSpec::Single(form) = registry.get("target_link_libraries") else {
1360 panic!()
1361 };
1362 assert_eq!(
1363 form.layout.as_ref().and_then(|layout| layout.always_wrap),
1364 Some(true)
1365 );
1366 assert!(form.kwargs.contains_key("PUBLIC"));
1367 assert_eq!(form.kwargs["LINKER_LANGUAGE"].nargs, NArgs::Fixed(1));
1368 }
1369
1370 #[test]
1371 fn merge_layout_override_applies_wrap_after_first_arg() {
1372 let mut registry = CommandRegistry::load().unwrap();
1377
1378 let CommandSpec::Single(form) = registry.get("set") else {
1379 panic!("set should be a single-form command")
1380 };
1381 assert_eq!(
1382 form.layout.as_ref().and_then(|l| l.wrap_after_first_arg),
1383 Some(true),
1384 "precondition: builtin set has wrap_after_first_arg = true"
1385 );
1386
1387 registry
1388 .merge_override_str(
1389 r#"
1390[commands.set.layout]
1391wrap_after_first_arg = false
1392"#,
1393 PathBuf::from("test-overrides.toml"),
1394 )
1395 .unwrap();
1396
1397 let CommandSpec::Single(form) = registry.get("set") else {
1398 panic!("set should still be a single-form command")
1399 };
1400 assert_eq!(
1401 form.layout.as_ref().and_then(|l| l.wrap_after_first_arg),
1402 Some(false),
1403 "user override of wrap_after_first_arg must win over the builtin"
1404 );
1405 }
1406
1407 #[test]
1408 fn uppercase_lookup_uses_builtin_normalization() {
1409 let registry = CommandRegistry::load().unwrap();
1410 assert!(registry.contains_builtin("TARGET_LINK_LIBRARIES"));
1411 let CommandSpec::Single(form) = registry.get("TARGET_LINK_LIBRARIES") else {
1412 panic!()
1413 };
1414 assert!(form.kwargs.contains_key("PUBLIC"));
1415 assert!(form.kwargs.contains_key("PRIVATE"));
1416 }
1417
1418 #[test]
1419 fn contains_builtin_excludes_user_added_commands_after_merge() {
1420 let mut registry = CommandRegistry::load().unwrap();
1421 registry
1422 .merge_toml_overrides(
1423 r#"
1424[commands.my_custom_command]
1425pargs = 1
1426"#,
1427 )
1428 .unwrap();
1429
1430 assert!(!registry.contains_builtin("my_custom_command"));
1431 assert!(!registry.contains_builtin("MY_CUSTOM_COMMAND"));
1432 assert!(matches!(
1433 registry.get("my_custom_command"),
1434 CommandSpec::Single(_)
1435 ));
1436 }
1437
1438 #[test]
1439 fn from_builtins_and_yaml_override_file_merges_entries() {
1440 let dir = tempfile::tempdir().unwrap();
1441 let overrides = dir.path().join("override.yaml");
1442 fs::write(
1443 &overrides,
1444 r#"
1445commands:
1446 target_link_libraries:
1447 kwargs:
1448 linker_language:
1449 nargs: 1
1450"#,
1451 )
1452 .unwrap();
1453
1454 let registry = CommandRegistry::from_builtins_and_overrides(Some(&overrides)).unwrap();
1455 let CommandSpec::Single(form) = registry.get("target_link_libraries") else {
1456 panic!()
1457 };
1458 assert_eq!(form.kwargs["LINKER_LANGUAGE"].nargs, NArgs::Fixed(1));
1459 }
1460
1461 #[test]
1462 fn merge_override_file_reports_structured_toml_parse_errors() {
1463 let mut registry = CommandRegistry::load().unwrap();
1464 let dir = tempfile::tempdir().unwrap();
1465 let path = dir.path().join("override.toml");
1466 fs::write(&path, "[commands.bad]\npargs = [\n").unwrap();
1467
1468 let err = registry.merge_override_file(&path).unwrap_err();
1469 match err {
1470 Error::Spec(spec_err) => {
1471 let details = &spec_err.details;
1472 assert_eq!(details.format, "TOML");
1473 assert!(details.line.is_some());
1474 assert!(details.column.is_some());
1475 }
1476 other => panic!("expected spec parse error, got {other:?}"),
1477 }
1478 }
1479
1480 #[test]
1481 fn merge_override_file_reports_structured_yaml_parse_errors() {
1482 let mut registry = CommandRegistry::load().unwrap();
1483 let dir = tempfile::tempdir().unwrap();
1484 let path = dir.path().join("override.yaml");
1485 fs::write(&path, "commands:\n target_link_libraries: [\n").unwrap();
1486
1487 let err = registry.merge_override_file(&path).unwrap_err();
1488 match err {
1489 Error::Spec(spec_err) => {
1490 let details = &spec_err.details;
1491 assert_eq!(details.format, "YAML");
1492 assert!(details.line.is_some());
1493 assert!(details.column.is_some());
1494 }
1495 other => panic!("expected spec parse error, got {other:?}"),
1496 }
1497 }
1498
1499 #[test]
1500 fn override_with_mismatched_shape_replaces_base_command_spec() {
1501 let mut registry = CommandRegistry::load().unwrap();
1502 registry
1503 .merge_override_str(
1504 r#"
1505[commands.cmake_minimum_required.forms.VERSION]
1506pargs = 1
1507"#,
1508 PathBuf::from("override.toml"),
1509 )
1510 .unwrap();
1511
1512 let CommandSpec::Discriminated { .. } = registry.get("cmake_minimum_required") else {
1513 panic!("expected discriminated command after mismatched override")
1514 };
1515 assert_eq!(
1516 registry
1517 .get("cmake_minimum_required")
1518 .form_for(Some("VERSION"))
1519 .pargs,
1520 NArgs::Fixed(1)
1521 );
1522 }
1523}