From 8cffeb441ebbc3e79380b5b8e133d40456194ae4 Mon Sep 17 00:00:00 2001 From: "Sung, Po Han" Date: Tue, 30 Dec 2025 14:37:59 +0800 Subject: [PATCH 1/4] Fix parse_install_targets to use parse_install_targets_sub parse_install_targets_sub was defined but never called. The code incorrectly used parse_install_targets recursively for designated kwargs (ARCHIVE, LIBRARY, RUNTIME, etc.), which was overly permissive since it allowed top-level keywords inside nested groups. --- cmakelang/parse/funs/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmakelang/parse/funs/install.py b/cmakelang/parse/funs/install.py index b91bb73..7f3cc05 100644 --- a/cmakelang/parse/funs/install.py +++ b/cmakelang/parse/funs/install.py @@ -139,7 +139,7 @@ def parse_install_targets(ctx, tokens, breakstack): word = get_normalized_kwarg(tokens[0]) if word in designated_kwargs: subtree = KeywordGroupNode.parse( - ctx, tokens, word, parse_install_targets, subtree_breakstack) + ctx, tokens, word, parse_install_targets_sub, subtree_breakstack) elif word in kwargs: subtree = KeywordGroupNode.parse( ctx, tokens, word, kwargs[word], kwarg_breakstack) From 26b2eb1c20cc5b98d816acea4bf0a9dceb49adf8 Mon Sep 17 00:00:00 2001 From: "Sung, Po Han" Date: Tue, 30 Dec 2025 14:39:20 +0800 Subject: [PATCH 2/4] Add FILE_SET formatting support (CMake 3.23+) Add support for the FILE_SET keyword used in target_sources() and install(TARGETS) commands. FILE_SET requires a set name argument followed by optional TYPE, BASE_DIRS, FILES, DESTINATION, etc. kwargs. - Add FileSetNode class for parsing FILE_SET syntax - Update target_sources() to handle FILE_SET keyword - Update install(TARGETS) to handle FILE_SET with its specific subparser - Add tests for adding new keywords --- cmakelang/command_tests/__init__.py | 2 +- cmakelang/command_tests/install_tests.cmake | 13 +++++++ cmakelang/command_tests/misc_tests.cmake | 27 +++++++++++++ cmakelang/command_tests/misc_tests.py | 2 +- cmakelang/parse/additional_nodes.py | 26 +++++++++++++ cmakelang/parse/funs/install.py | 43 ++++++++++++++++++--- cmakelang/parse/funs/random.py | 7 +++- 7 files changed, 111 insertions(+), 9 deletions(-) diff --git a/cmakelang/command_tests/__init__.py b/cmakelang/command_tests/__init__.py index f0a1bf4..5407a2d 100644 --- a/cmakelang/command_tests/__init__.py +++ b/cmakelang/command_tests/__init__.py @@ -533,7 +533,7 @@ class TestInstall(TestBase): """ Test various examples of the install command """ - kExpectNumSidecarTests = 3 + kExpectNumSidecarTests = 5 class TestSetTargetProperties(TestBase): diff --git a/cmakelang/command_tests/install_tests.cmake b/cmakelang/command_tests/install_tests.cmake index 91f3ce5..9aea013 100644 --- a/cmakelang/command_tests/install_tests.cmake +++ b/cmakelang/command_tests/install_tests.cmake @@ -92,3 +92,16 @@ install( TARGETS target # CONFIGURATIONS Debug RUNTIME DESTINATION Debug/bin) + +# test: install_file_set +install(TARGETS mylib FILE_SET myheaders DESTINATION include/mylib) + +# test: install_file_set_with_component +install( + TARGETS mylib + FILE_SET HEADERS + DESTINATION include + COMPONENT Development + FILE_SET mymodules + DESTINATION lib/cmake + COMPONENT Development) diff --git a/cmakelang/command_tests/misc_tests.cmake b/cmakelang/command_tests/misc_tests.cmake index 7217f84..20a0b3a 100644 --- a/cmakelang/command_tests/misc_tests.cmake +++ b/cmakelang/command_tests/misc_tests.cmake @@ -898,3 +898,30 @@ use_tabchars = True if(TRUE) message("Hello world") endif() + +# test: target_sources_file_set +target_sources( + mylib + PUBLIC + FILE_SET HEADERS + BASE_DIRS include + FILES include/mylib.h include/mylib_config.h) + +# test: target_sources_file_set_with_type +target_sources( + mylib + PUBLIC + FILE_SET mymodules + TYPE CXX_MODULES + BASE_DIRS src + FILES src/mymodule.cppm) + +# test: target_sources_mixed +target_sources( + mylib + PRIVATE src/impl.cpp + PUBLIC + FILE_SET HEADERS + BASE_DIRS include + FILES include/mylib.h + INTERFACE some_interface.h) diff --git a/cmakelang/command_tests/misc_tests.py b/cmakelang/command_tests/misc_tests.py index 96eec91..38a714e 100644 --- a/cmakelang/command_tests/misc_tests.py +++ b/cmakelang/command_tests/misc_tests.py @@ -15,7 +15,7 @@ class TestMiscFormatting(TestBase): """ Ensure that various inputs format the way we want them to """ - kExpectNumSidecarTests = 86 + kExpectNumSidecarTests = 89 def test_config_hashruler_minlength(self): diff --git a/cmakelang/parse/additional_nodes.py b/cmakelang/parse/additional_nodes.py index d843353..d3e899d 100644 --- a/cmakelang/parse/additional_nodes.py +++ b/cmakelang/parse/additional_nodes.py @@ -162,6 +162,32 @@ def parse(cls, ctx, tokens, breakstack): ) +class FileSetNode(StandardArgTree): + """File sets are children of a `FILE_SET` keyword argument and are used + in target_sources and install commands (CMake 3.23+).""" + + @classmethod + def parse(cls, ctx, tokens, breakstack): + """ + :: + + FILE_SET [TYPE ] [BASE_DIRS ...] [FILES ...] + + :see: https://cmake.org/cmake/help/latest/command/target_sources.html + """ + return super(FileSetNode, cls).parse( + ctx, tokens, + npargs=1, # The set name + kwargs={ + "TYPE": PositionalParser(1), + "BASE_DIRS": PositionalParser('+'), + "FILES": PositionalParser('+'), + }, + flags=[], + breakstack=breakstack + ) + + class FlagGroupNode(PositionalGroupNode): """A positinal group where each argument is a flag.""" diff --git a/cmakelang/parse/funs/install.py b/cmakelang/parse/funs/install.py index 7f3cc05..be09720 100644 --- a/cmakelang/parse/funs/install.py +++ b/cmakelang/parse/funs/install.py @@ -5,7 +5,7 @@ from cmakelang.parse.simple_nodes import CommentNode from cmakelang.parse.argument_nodes import ( KeywordGroupNode, PositionalGroupNode, PositionalParser, StandardArgTree) -from cmakelang.parse.additional_nodes import PatternNode +from cmakelang.parse.additional_nodes import FileSetNode, PatternNode from cmakelang.parse.util import ( WHITESPACE_TOKENS, get_first_semantic_token, @@ -40,13 +40,38 @@ def parse_install_targets_sub(ctx, tokens, breakstack): breakstack=breakstack) +def parse_install_file_set_sub(ctx, tokens, breakstack): + """ + Parse the inner kwargs of an ``install(TARGETS ... FILE_SET)`` command. + FILE_SET requires a set name followed by optional destination kwargs. + :see: https://cmake.org/cmake/help/latest/command/install.html#targets + """ + return StandardArgTree.parse( + ctx, tokens, + npargs=1, # The file set name + kwargs={ + "DESTINATION": PositionalParser(1), + "PERMISSIONS": PositionalParser('+'), + "CONFIGURATIONS": PositionalParser('+'), + "COMPONENT": PositionalParser(1), + "NAMELINK_COMPONENT": PositionalParser(1), + }, + flags=[ + "OPTIONAL", + "EXCLUDE_FROM_ALL", + "NAMELINK_ONLY", + "NAMELINK_SKIP" + ], + breakstack=breakstack) + + def parse_install_targets(ctx, tokens, breakstack): """ :: install(TARGETS targets... [EXPORT ] [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE| - PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE] + PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE|FILE_SET ] [DESTINATION ] [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] @@ -58,7 +83,7 @@ def parse_install_targets(ctx, tokens, breakstack): [INCLUDES DESTINATION [ ...]] ) - :see: https://cmake.org/cmake/help/v3.14/command/install.html#targets + :see: https://cmake.org/cmake/help/latest/command/install.html#targets """ kwargs = { "TARGETS": PositionalParser('+'), @@ -79,7 +104,8 @@ def parse_install_targets(ctx, tokens, breakstack): ) designated_kwargs = ( "ARCHIVE", "LIBRARY", "RUNTIME", "OBJECTS", "FRAMEWORK", - "BUNDLE", "PRIVATE_HEADER", "PUBLIC_HEADER", "RESOURCE" + "BUNDLE", "PRIVATE_HEADER", "PUBLIC_HEADER", "RESOURCE", + "FILE_SET" ) # NOTE(josh): from here on, code is essentially StandardArgTree.parse(), @@ -138,8 +164,13 @@ def parse_install_targets(ctx, tokens, breakstack): # just make sure we check flags first. word = get_normalized_kwarg(tokens[0]) if word in designated_kwargs: - subtree = KeywordGroupNode.parse( - ctx, tokens, word, parse_install_targets_sub, subtree_breakstack) + # FILE_SET requires a set name argument, so use a different subparser + if word == "FILE_SET": + subtree = KeywordGroupNode.parse( + ctx, tokens, word, parse_install_file_set_sub, subtree_breakstack) + else: + subtree = KeywordGroupNode.parse( + ctx, tokens, word, parse_install_targets_sub, subtree_breakstack) elif word in kwargs: subtree = KeywordGroupNode.parse( ctx, tokens, word, kwargs[word], kwarg_breakstack) diff --git a/cmakelang/parse/funs/random.py b/cmakelang/parse/funs/random.py index 3ee54aa..c38cb6d 100644 --- a/cmakelang/parse/funs/random.py +++ b/cmakelang/parse/funs/random.py @@ -2,6 +2,7 @@ PositionalParser, StandardArgTree, ) +from cmakelang.parse.additional_nodes import FileSetNode from cmakelang.parse.util import ( iter_semantic_tokens, ) @@ -357,7 +358,10 @@ def parse_target_sources(ctx, tokens, breakstack): target_sources( [items1...] - [ [items2...] ...]) + [ [items2...] ...] + [ + [FILE_SET [TYPE ] [BASE_DIRS ...] [FILES ...]] + ...]) :see: https://cmake.org/cmake/help/latest/command/target_sources.html """ @@ -365,6 +369,7 @@ def parse_target_sources(ctx, tokens, breakstack): "INTERFACE": PositionalParser("+"), "PUBLIC": PositionalParser("+"), "PRIVATE": PositionalParser("+"), + "FILE_SET": FileSetNode.parse, } return StandardArgTree.parse(ctx, tokens, 1, kwargs, [], breakstack) From 4f20617a6d60b7f09958d1ba91bf28cb8a10c9a5 Mon Sep 17 00:00:00 2001 From: "Sung, Po Han" Date: Tue, 30 Dec 2025 14:58:27 +0800 Subject: [PATCH 3/4] Add install RUNTIME_DEPENDENCIES support (CMake 3.21+) Add support for runtime dependency handling in install(): - RUNTIME_DEPENDENCY_SET as standalone install form - RUNTIME_DEPENDENCIES and RUNTIME_DEPENDENCY_SET in install(TARGETS) Both support filtering options (PRE/POST_INCLUDE/EXCLUDE_REGEXES, POST_INCLUDE/EXCLUDE_FILES, DIRECTORIES). --- cmakelang/command_tests/__init__.py | 2 +- cmakelang/command_tests/install_tests.cmake | 24 +++++++ cmakelang/parse/funs/install.py | 76 ++++++++++++++++++++- 3 files changed, 99 insertions(+), 3 deletions(-) diff --git a/cmakelang/command_tests/__init__.py b/cmakelang/command_tests/__init__.py index 5407a2d..dac7ab9 100644 --- a/cmakelang/command_tests/__init__.py +++ b/cmakelang/command_tests/__init__.py @@ -533,7 +533,7 @@ class TestInstall(TestBase): """ Test various examples of the install command """ - kExpectNumSidecarTests = 5 + kExpectNumSidecarTests = 8 class TestSetTargetProperties(TestBase): diff --git a/cmakelang/command_tests/install_tests.cmake b/cmakelang/command_tests/install_tests.cmake index 9aea013..2aa2b45 100644 --- a/cmakelang/command_tests/install_tests.cmake +++ b/cmakelang/command_tests/install_tests.cmake @@ -105,3 +105,27 @@ install( FILE_SET mymodules DESTINATION lib/cmake COMPONENT Development) + +# test: install_runtime_dependency_set +install( + RUNTIME_DEPENDENCY_SET mydeps + LIBRARY DESTINATION lib COMPONENT runtime + RUNTIME DESTINATION bin COMPONENT runtime + PRE_EXCLUDE_REGEXES "api-ms-" "ext-ms-" + POST_EXCLUDE_REGEXES ".*system32/.*\\.dll" + DIRECTORIES ${CMAKE_INSTALL_PREFIX}/bin) + +# test: install_targets_with_runtime_dependency_set +install( + TARGETS myapp + RUNTIME_DEPENDENCY_SET mydeps + RUNTIME DESTINATION bin COMPONENT runtime) + +# test: install_targets_with_runtime_dependencies +install( + TARGETS myapp + RUNTIME_DEPENDENCIES + PRE_EXCLUDE_REGEXES "api-ms-" "ext-ms-" + POST_EXCLUDE_REGEXES ".*system32/.*\\.dll" + DIRECTORIES ${CMAKE_INSTALL_PREFIX}/bin + RUNTIME DESTINATION bin) diff --git a/cmakelang/parse/funs/install.py b/cmakelang/parse/funs/install.py index be09720..3d7afb8 100644 --- a/cmakelang/parse/funs/install.py +++ b/cmakelang/parse/funs/install.py @@ -70,6 +70,7 @@ def parse_install_targets(ctx, tokens, breakstack): :: install(TARGETS targets... [EXPORT ] + [RUNTIME_DEPENDENCIES ...|RUNTIME_DEPENDENCY_SET ] [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE| PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE|FILE_SET ] [DESTINATION ] @@ -88,6 +89,8 @@ def parse_install_targets(ctx, tokens, breakstack): kwargs = { "TARGETS": PositionalParser('+'), "EXPORT": PositionalParser(1), + "RUNTIME_DEPENDENCY_SET": PositionalParser(1), + "RUNTIME_DEPENDENCIES": parse_install_runtime_dependencies_sub, "INCLUDES": PositionalParser('+', flags=["DESTINATION"]), # Common kwargs "DESTINATION": PositionalParser(1), @@ -311,6 +314,73 @@ def parse_install_export(ctx, tokens, breakstack): breakstack=breakstack) +def parse_install_runtime_dependencies_sub(ctx, tokens, breakstack): + """ + Parse the inner kwargs of ``RUNTIME_DEPENDENCIES`` within install(TARGETS). + These are the filtering options for runtime dependency resolution. + + :see: https://cmake.org/cmake/help/latest/command/install.html#targets + """ + return StandardArgTree.parse( + ctx, tokens, + npargs='*', + kwargs={ + "PRE_INCLUDE_REGEXES": PositionalParser('+'), + "PRE_EXCLUDE_REGEXES": PositionalParser('+'), + "POST_INCLUDE_REGEXES": PositionalParser('+'), + "POST_EXCLUDE_REGEXES": PositionalParser('+'), + "POST_INCLUDE_FILES": PositionalParser('+'), + "POST_EXCLUDE_FILES": PositionalParser('+'), + "DIRECTORIES": PositionalParser('+'), + }, + flags=[], + breakstack=breakstack) + + +def parse_install_runtime_dependency_set(ctx, tokens, breakstack): + """ + :: + + install(RUNTIME_DEPENDENCY_SET + [[LIBRARY|RUNTIME|FRAMEWORK] + [DESTINATION ] + [PERMISSIONS permissions...] + [CONFIGURATIONS [Debug|Release|...]] + [COMPONENT ] + [NAMELINK_COMPONENT ] + [OPTIONAL] [EXCLUDE_FROM_ALL] + ] [...] + [PRE_INCLUDE_REGEXES regex...] + [PRE_EXCLUDE_REGEXES regex...] + [POST_INCLUDE_REGEXES regex...] + [POST_EXCLUDE_REGEXES regex...] + [POST_INCLUDE_FILES file...] + [POST_EXCLUDE_FILES file...] + [DIRECTORIES dir...] + ) + + :see: https://cmake.org/cmake/help/latest/command/install.html#runtime-dependency-set + """ + return StandardArgTree.parse( + ctx, tokens, + npargs='*', + kwargs={ + "RUNTIME_DEPENDENCY_SET": PositionalParser(1), + "LIBRARY": parse_install_targets_sub, + "RUNTIME": parse_install_targets_sub, + "FRAMEWORK": parse_install_targets_sub, + "PRE_INCLUDE_REGEXES": PositionalParser('+'), + "PRE_EXCLUDE_REGEXES": PositionalParser('+'), + "POST_INCLUDE_REGEXES": PositionalParser('+'), + "POST_EXCLUDE_REGEXES": PositionalParser('+'), + "POST_INCLUDE_FILES": PositionalParser('+'), + "POST_EXCLUDE_FILES": PositionalParser('+'), + "DIRECTORIES": PositionalParser('+'), + }, + flags=[], + breakstack=breakstack) + + def parse_install(ctx, tokens, breakstack): """ The ``install()`` command has multiple different forms, implemented @@ -323,8 +393,9 @@ def parse_install(ctx, tokens, breakstack): * SCRIPT * CODE * EXPORT + * RUNTIME_DEPENDENCY_SET - :see: https://cmake.org/cmake/help/v3.0/command/install.html + :see: https://cmake.org/cmake/help/latest/command/install.html """ descriminator_token = get_first_semantic_token(tokens) @@ -341,7 +412,8 @@ def parse_install(ctx, tokens, breakstack): "DIRECTORY": parse_install_directory, "SCRIPT": parse_install_script, "CODE": parse_install_script, - "EXPORT": parse_install_export + "EXPORT": parse_install_export, + "RUNTIME_DEPENDENCY_SET": parse_install_runtime_dependency_set, } if descriminator not in parsemap: logger.warning("Invalid install form \"%s\" at %s", descriminator, From 38945f85016f4cba23ce4f03f06446afc693897a Mon Sep 17 00:00:00 2001 From: "Sung, Po Han" Date: Sun, 18 Jan 2026 10:28:43 +0800 Subject: [PATCH 4/4] Add CXX_MODULES_BMI support in install(TARGETS) (CMake 3.28+) CXX_MODULES_BMI is an artifact kind for installing Binary Module Interface files from C++20 modules. It supports the same options as other artifact kinds: DESTINATION, PERMISSIONS, CONFIGURATIONS, COMPONENT, OPTIONAL, EXCLUDE_FROM_ALL. --- cmakelang/command_tests/__init__.py | 2 +- cmakelang/command_tests/install_tests.cmake | 6 ++++++ cmakelang/parse/funs/install.py | 5 +++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cmakelang/command_tests/__init__.py b/cmakelang/command_tests/__init__.py index dac7ab9..55bdbfb 100644 --- a/cmakelang/command_tests/__init__.py +++ b/cmakelang/command_tests/__init__.py @@ -533,7 +533,7 @@ class TestInstall(TestBase): """ Test various examples of the install command """ - kExpectNumSidecarTests = 8 + kExpectNumSidecarTests = 9 class TestSetTargetProperties(TestBase): diff --git a/cmakelang/command_tests/install_tests.cmake b/cmakelang/command_tests/install_tests.cmake index 2aa2b45..31a4dff 100644 --- a/cmakelang/command_tests/install_tests.cmake +++ b/cmakelang/command_tests/install_tests.cmake @@ -129,3 +129,9 @@ install( POST_EXCLUDE_REGEXES ".*system32/.*\\.dll" DIRECTORIES ${CMAKE_INSTALL_PREFIX}/bin RUNTIME DESTINATION bin) + +# test: install_cxx_modules_bmi +install( + TARGETS mymodule + FILE_SET CXX_MODULES DESTINATION lib/cxx/miu + CXX_MODULES_BMI DESTINATION lib/cxx/bmi COMPONENT Development) diff --git a/cmakelang/parse/funs/install.py b/cmakelang/parse/funs/install.py index 3d7afb8..fd4fe7e 100644 --- a/cmakelang/parse/funs/install.py +++ b/cmakelang/parse/funs/install.py @@ -72,7 +72,8 @@ def parse_install_targets(ctx, tokens, breakstack): install(TARGETS targets... [EXPORT ] [RUNTIME_DEPENDENCIES ...|RUNTIME_DEPENDENCY_SET ] [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE| - PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE|FILE_SET ] + PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE| + FILE_SET |CXX_MODULES_BMI] [DESTINATION ] [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] @@ -108,7 +109,7 @@ def parse_install_targets(ctx, tokens, breakstack): designated_kwargs = ( "ARCHIVE", "LIBRARY", "RUNTIME", "OBJECTS", "FRAMEWORK", "BUNDLE", "PRIVATE_HEADER", "PUBLIC_HEADER", "RESOURCE", - "FILE_SET" + "FILE_SET", "CXX_MODULES_BMI" ) # NOTE(josh): from here on, code is essentially StandardArgTree.parse(),