From 0bb13ba0e64c1a700d6448614a6ccce2e4eca94f Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Sun, 27 Oct 2024 11:21:52 +0200 Subject: [PATCH] VS: Add support for Visual Studio solution items Files listed in the `VS_SOLUTION_ITEMS` directory property of a project directory are added as solution items in the 'Solution Items' solution directory. If `source_group` is applied to the files listed in `VS_SOLUTION_ITEMS`, solution groups matching the names of the source groups are created outside of the default 'Solution Items' group. If not items are placed into the default group, it is not created. Solution items added to subprojects are not included in the top-level project. Closes: #26409 --- Help/manual/cmake-properties.7.rst | 1 + Help/prop_dir/VS_SOLUTION_ITEMS.rst | 19 + Help/release/dev/vs-solution-items.rst | 6 + Source/cmGlobalVisualStudio14Generator.cxx | 53 +++ Source/cmGlobalVisualStudio14Generator.h | 5 + Source/cmGlobalVisualStudio71Generator.cxx | 7 +- Source/cmGlobalVisualStudio7Generator.cxx | 8 +- Source/cmGlobalVisualStudio7Generator.h | 6 + .../VS10Project/SolutionItems-check.cmake | 351 ++++++++++++++++++ .../RunCMake/VS10Project/SolutionItems.cmake | 4 + .../VS10Project/SolutionItems/CMakeLists.txt | 4 + .../VS10Project/SolutionItems/extraneous.txt | 0 .../VS10Project/solution-item-0-1.txt | 0 .../VS10Project/solution-item-1-1.txt | 0 .../VS10Project/solution-item-2-1.txt | 0 .../VS10Project/solution-item-2-2.txt | 0 16 files changed, 460 insertions(+), 4 deletions(-) create mode 100644 Help/prop_dir/VS_SOLUTION_ITEMS.rst create mode 100644 Help/release/dev/vs-solution-items.rst create mode 100644 Tests/RunCMake/VS10Project/SolutionItems-check.cmake create mode 100644 Tests/RunCMake/VS10Project/SolutionItems.cmake create mode 100644 Tests/RunCMake/VS10Project/SolutionItems/CMakeLists.txt create mode 100644 Tests/RunCMake/VS10Project/SolutionItems/extraneous.txt create mode 100644 Tests/RunCMake/VS10Project/solution-item-0-1.txt create mode 100644 Tests/RunCMake/VS10Project/solution-item-1-1.txt create mode 100644 Tests/RunCMake/VS10Project/solution-item-2-1.txt create mode 100644 Tests/RunCMake/VS10Project/solution-item-2-2.txt diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst index cd379849f4..4d20d98c4b 100644 --- a/Help/manual/cmake-properties.7.rst +++ b/Help/manual/cmake-properties.7.rst @@ -97,6 +97,7 @@ Properties on Directories /prop_dir/VARIABLES /prop_dir/VS_GLOBAL_SECTION_POST_section /prop_dir/VS_GLOBAL_SECTION_PRE_section + /prop_dir/VS_SOLUTION_ITEMS /prop_dir/VS_STARTUP_PROJECT .. _`Target Properties`: diff --git a/Help/prop_dir/VS_SOLUTION_ITEMS.rst b/Help/prop_dir/VS_SOLUTION_ITEMS.rst new file mode 100644 index 0000000000..435b9a7dca --- /dev/null +++ b/Help/prop_dir/VS_SOLUTION_ITEMS.rst @@ -0,0 +1,19 @@ +VS_SOLUTION_ITEMS +----------------- + +.. versionadded:: 3.32 + +Specify solution level items included in the generated Visual Studio solution. + +The :ref:`Visual Studio Generators` create a ``.sln`` file for each directory +whose ``CMakeLists.txt`` file calls the :command:`project` command. Append paths +to this property in the same directory as the top-level :command:`project` +command call (e.g. in the top-level ``CMakeLists.txt`` file) to specify files +included in the corresponding solution file. + +If a file specified in ``VS_SOLUTION_ITEMS`` matches a :command:`source_group` +command call, the affected solution level items are placed in a hierarchy of +solution level folders according to the name specified in that command. +Otherwise the items are placed in a default solution level directory named +``Solution Items``. This name matches the default directory name used by Visual +Studio when attempting to add solution level items at the root of the solution. diff --git a/Help/release/dev/vs-solution-items.rst b/Help/release/dev/vs-solution-items.rst new file mode 100644 index 0000000000..35489bcc5c --- /dev/null +++ b/Help/release/dev/vs-solution-items.rst @@ -0,0 +1,6 @@ +vs-solution-items +----------------- + +* The :prop_dir:`VS_SOLUTION_ITEMS` directory property was added + to tell :ref:`Visual Studio Generators` to attach files directly + to the Solution (``.sln``). diff --git a/Source/cmGlobalVisualStudio14Generator.cxx b/Source/cmGlobalVisualStudio14Generator.cxx index 35b69af69d..7fb1ce5ce0 100644 --- a/Source/cmGlobalVisualStudio14Generator.cxx +++ b/Source/cmGlobalVisualStudio14Generator.cxx @@ -541,3 +541,56 @@ std::string cmGlobalVisualStudio14Generator::GetWindows10SDKVersion( // Return an empty string return std::string(); } + +void cmGlobalVisualStudio14Generator::AddSolutionItems(cmLocalGenerator* root) +{ + cmValue n = root->GetMakefile()->GetProperty("VS_SOLUTION_ITEMS"); + if (cmNonempty(n)) { + cmMakefile* makefile = root->GetMakefile(); + + std::vector sourceGroups = makefile->GetSourceGroups(); + + cmVisualStudioFolder* defaultFolder = nullptr; + + std::vector pathComponents = { + makefile->GetCurrentSourceDirectory(), + "", + "", + }; + + for (const std::string& relativePath : cmList(n)) { + pathComponents[2] = relativePath; + std::string fullPath = cmSystemTools::JoinPath(pathComponents); + + cmSourceGroup* sg = makefile->FindSourceGroup(fullPath, sourceGroups); + + cmVisualStudioFolder* folder = nullptr; + if (!sg->GetFullName().empty()) { + std::string folderPath = sg->GetFullName(); + // Source groups use '\' while solution folders use '/'. + cmSystemTools::ReplaceString(folderPath, "\\", "/"); + folder = this->CreateSolutionFolders(folderPath); + } else { + // Lazily initialize the default solution items folder. + if (defaultFolder == nullptr) { + defaultFolder = this->CreateSolutionFolders("Solution Items"); + } + folder = defaultFolder; + } + + folder->SolutionItems.insert(fullPath); + } + } +} + +void cmGlobalVisualStudio14Generator::WriteFolderSolutionItems( + std::ostream& fout, const cmVisualStudioFolder& folder) +{ + fout << "\tProjectSection(SolutionItems) = preProject\n"; + + for (const std::string& item : folder.SolutionItems) { + fout << "\t\t" << item << " = " << item << "\n"; + } + + fout << "\tEndProjectSection\n"; +} diff --git a/Source/cmGlobalVisualStudio14Generator.h b/Source/cmGlobalVisualStudio14Generator.h index e9035780f7..e5b8ff89d3 100644 --- a/Source/cmGlobalVisualStudio14Generator.h +++ b/Source/cmGlobalVisualStudio14Generator.h @@ -67,6 +67,11 @@ protected: std::string GetWindows10SDKVersion(cmMakefile* mf); + void AddSolutionItems(cmLocalGenerator* root) override; + + void WriteFolderSolutionItems(std::ostream& fout, + const cmVisualStudioFolder& folder) override; + private: class Factory; friend class Factory; diff --git a/Source/cmGlobalVisualStudio71Generator.cxx b/Source/cmGlobalVisualStudio71Generator.cxx index 8375b72cab..b59c6c6105 100644 --- a/Source/cmGlobalVisualStudio71Generator.cxx +++ b/Source/cmGlobalVisualStudio71Generator.cxx @@ -48,9 +48,10 @@ void cmGlobalVisualStudio71Generator::WriteSLNFile( std::ostringstream targetsSlnString; this->WriteTargetsToSolution(targetsSlnString, root, orderedProjectTargets); + this->AddSolutionItems(root); + // Generate folder specification. - bool useFolderProperty = this->UseFolderProperty(); - if (useFolderProperty) { + if (!this->VisualStudioFolders.empty()) { this->WriteFolders(fout); } @@ -67,7 +68,7 @@ void cmGlobalVisualStudio71Generator::WriteSLNFile( this->WriteTargetConfigurations(fout, configs, orderedProjectTargets); fout << "\tEndGlobalSection\n"; - if (useFolderProperty) { + if (!this->VisualStudioFolders.empty()) { // Write out project folders fout << "\tGlobalSection(NestedProjects) = preSolution\n"; this->WriteFoldersContent(fout); diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx index febbc99059..55416175f3 100644 --- a/Source/cmGlobalVisualStudio7Generator.cxx +++ b/Source/cmGlobalVisualStudio7Generator.cxx @@ -481,7 +481,13 @@ void cmGlobalVisualStudio7Generator::WriteFolders(std::ostream& fout) std::string nameOnly = cmSystemTools::GetFilenameName(fullName); fout << "Project(\"{" << guidProjectTypeFolder << "}\") = \"" << nameOnly - << "\", \"" << fullName << "\", \"{" << guid << "}\"\nEndProject\n"; + << "\", \"" << fullName << "\", \"{" << guid << "}\"\n"; + + if (!iter.second.SolutionItems.empty()) { + this->WriteFolderSolutionItems(fout, iter.second); + } + + fout << "EndProject\n"; } } diff --git a/Source/cmGlobalVisualStudio7Generator.h b/Source/cmGlobalVisualStudio7Generator.h index 3ac2d2c5d8..d7d7aec5fb 100644 --- a/Source/cmGlobalVisualStudio7Generator.h +++ b/Source/cmGlobalVisualStudio7Generator.h @@ -28,6 +28,7 @@ class BT; struct cmVisualStudioFolder { std::set Projects; + std::set SolutionItems; }; /** \class cmGlobalVisualStudio7Generator @@ -185,6 +186,11 @@ protected: virtual void WriteFolders(std::ostream& fout); virtual void WriteFoldersContent(std::ostream& fout); + + virtual void AddSolutionItems(cmLocalGenerator* root) = 0; + virtual void WriteFolderSolutionItems( + std::ostream& fout, const cmVisualStudioFolder& folder) = 0; + std::map VisualStudioFolders; // Set during OutputSLNFile with the name of the current project. diff --git a/Tests/RunCMake/VS10Project/SolutionItems-check.cmake b/Tests/RunCMake/VS10Project/SolutionItems-check.cmake new file mode 100644 index 0000000000..a917e0b9a1 --- /dev/null +++ b/Tests/RunCMake/VS10Project/SolutionItems-check.cmake @@ -0,0 +1,351 @@ +function(MapAppend map key value) + list(APPEND "${map}_k" "${key}") + list(APPEND "${map}_v" "${value}") + + set("${map}_k" "${${map}_k}" PARENT_SCOPE) + set("${map}_v" "${${map}_v}" PARENT_SCOPE) +endfunction() + +function(MapLength map out_variable) + list(LENGTH "${map}_k" length) + set("${out_variable}" "${length}" PARENT_SCOPE) +endfunction() + +function(MapFind map key out_variable) + list(FIND "${map}_k" "${key}" index) + if("${index}" LESS 0) + unset("${out_variable}" PARENT_SCOPE) + else() + list(GET "${map}_v" "${index}" value) + set("${out_variable}" "${value}" PARENT_SCOPE) + endif() +endfunction() + +macro(MapPropagateToParentScope map) + set("${map}_k" "${${map}_k}" PARENT_SCOPE) + set("${map}_v" "${${map}_v}" PARENT_SCOPE) +endmacro() + + +function(ParseSln vcSlnFile) + if(NOT EXISTS "${vcSlnFile}") + set(RunCMake_TEST_FAILED "Solution file ${vcSlnFile} does not exist." PARENT_SCOPE) + return() + endif() + + set(SCOPE "") + + set(IN_SOLUTION_ITEMS FALSE) + set(IN_NESTED_PROJECTS FALSE) + + set(REGUID "\\{[0-9A-F-]+\\}") + + file(STRINGS "${vcSlnFile}" lines) + foreach(line IN LISTS lines) + string(STRIP "${line}" line) + + # Project(...) + if(line MATCHES "Project\\(\"(${REGUID})\"\\) = \"([^\"]+)\", \"([^\"]+)\", \"(${REGUID})\"") + if(NOT "${SCOPE}" STREQUAL "") + set(RunCMake_TEST_FAILED "Improper nesting of Project" PARENT_SCOPE) + return() + endif() + set(SCOPE "Project") + + if("${CMAKE_MATCH_1}" STREQUAL "{2150E333-8FDC-42A3-9474-1A3956D46DE8}") + set(GROUP_NAME "${CMAKE_MATCH_2}") + + MapFind(GROUP_PATHS "${GROUP_NAME}" existing_path) + if(DEFINED existing_path) + set(RunCMake_TEST_FAILED "Duplicate solution items project '${GROUP_NAME}'" PARENT_SCOPE) + return() + endif() + + MapAppend(GROUP_PATHS "${GROUP_NAME}" "${CMAKE_MATCH_3}") + MapAppend(GROUP_GUIDS "${GROUP_NAME}" "${CMAKE_MATCH_4}") + endif() + + # EndProject + elseif(line STREQUAL "EndProject") + if(NOT "${SCOPE}" STREQUAL "Project") + set(RunCMake_TEST_FAILED "Improper nesting of EndProject" PARENT_SCOPE) + return() + endif() + set(SCOPE "") + + unset(GROUP_NAME) + + # ProjectSection + elseif(line MATCHES "ProjectSection\\(([a-zA-Z]+)\\) = ([a-zA-Z]+)") + if(NOT "${SCOPE}" STREQUAL "Project") + set(RunCMake_TEST_FAILED "Improper nesting of ProjectSection" PARENT_SCOPE) + return() + endif() + set(SCOPE "ProjectSection") + + if("${CMAKE_MATCH_1}" STREQUAL "SolutionItems") + if(NOT "${CMAKE_MATCH_2}" STREQUAL "preProject") + set(RunCMake_TEST_FAILED "SolutionItems must be preProject" PARENT_SCOPE) + return() + endif() + + set(IN_SOLUTION_ITEMS TRUE) + endif() + + # EndProjectSection + elseif(line STREQUAL "EndProjectSection") + if(NOT "${SCOPE}" STREQUAL "ProjectSection") + set(RunCMake_TEST_FAILED "Improper nesting of EndProjectSection" PARENT_SCOPE) + return() + endif() + set(SCOPE "Project") + + set(IN_SOLUTION_ITEMS FALSE) + + # Global + elseif(line STREQUAL "Global") + if(NOT "${SCOPE}" STREQUAL "") + set(RunCMake_TEST_FAILED "Improper nesting of Global" PARENT_SCOPE) + return() + endif() + set(SCOPE "Global") + + # EndGlobal + elseif(line STREQUAL "EndGlobal") + if(NOT "${SCOPE}" STREQUAL "Global") + set(RunCMake_TEST_FAILED "Improper nesting of EndGlobal" PARENT_SCOPE) + return() + endif() + set(SCOPE "") + + # GlobalSection + elseif(line MATCHES "GlobalSection\\(([a-zA-Z]+)\\) = ([a-zA-Z]+)") + if(NOT "${SCOPE}" STREQUAL "Global") + set(RunCMake_TEST_FAILED "Improper nesting of GlobalSection" PARENT_SCOPE) + return() + endif() + set(SCOPE "GlobalSection") + + if("${CMAKE_MATCH_1}" STREQUAL "NestedProjects") + if(NOT "${CMAKE_MATCH_2}" STREQUAL "preSolution") + set(RunCMake_TEST_FAILED "NestedProjects must be preSolution" PARENT_SCOPE) + return() + endif() + + set(IN_NESTED_PROJECTS TRUE) + endif() + + # EndGlobalSection + elseif(line STREQUAL "EndGlobalSection") + if(NOT "${SCOPE}" STREQUAL "GlobalSection") + set(RunCMake_TEST_FAILED "Improper nesting of EndGlobalSection" PARENT_SCOPE) + return() + endif() + set(SCOPE "Global") + + set(IN_NESTED_PROJECTS FALSE) + + # .../solution-item-0-1.txt = .../solution-item-0-1.txt + elseif(${IN_SOLUTION_ITEMS}) + if(NOT line MATCHES "([^=]+)=([^=]+)") + set(RunCMake_TEST_FAILED "Invalid solution item paths 1" PARENT_SCOPE) + return() + endif() + + string(STRIP "${CMAKE_MATCH_1}" CMAKE_MATCH_1) + string(STRIP "${CMAKE_MATCH_2}" CMAKE_MATCH_2) + + if(NOT "${CMAKE_MATCH_1}" STREQUAL "${CMAKE_MATCH_2}") + set(RunCMake_TEST_FAILED "Invalid solution item paths 2" PARENT_SCOPE) + return() + endif() + + cmake_path(GET CMAKE_MATCH_1 FILENAME filename) + MapAppend(SOLUTION_ITEMS "${filename}" "${GROUP_NAME}") + + # {1EB55F5E...} = {A11E84C6...} + elseif(${IN_NESTED_PROJECTS}) + if(NOT line MATCHES "(${REGUID}) = (${REGUID})") + set(RunCMake_TEST_FAILED "Invalid nested project guids" PARENT_SCOPE) + return() + endif() + + MapFind(PROJECT_PARENTS "${CMAKE_MATCH_1}" existing_parent) + if(DEFINED existing_parent) + set(RunCMake_TEST_FAILED "Duplicate nested project: '${CMAKE_MATCH_1}'" PARENT_SCOPE) + return() + endif() + + MapAppend(PROJECT_PARENTS "${CMAKE_MATCH_1}" "${CMAKE_MATCH_2}") + + endif() + + MapPropagateToParentScope(GROUP_PATHS) + MapPropagateToParentScope(GROUP_GUIDS) + MapPropagateToParentScope(PROJECT_PARENTS) + MapPropagateToParentScope(SOLUTION_ITEMS) + endforeach() +endfunction() + + +# Check the root solution: +block() + ParseSln("${RunCMake_TEST_BINARY_DIR}/SolutionItems.sln") + + if(DEFINED RunCMake_TEST_FAILED) + set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE) + return() + endif() + + + # Check group guids and nesting: + + MapFind(GROUP_GUIDS "Solution Items" root_group_guid) + if(NOT DEFINED root_group_guid) + set(RunCMake_TEST_FAILED "Solution Items not found" PARENT_SCOPE) + return() + endif() + MapFind(GROUP_PATHS "Solution Items" root_group_path) + if(NOT "${root_group_path}" STREQUAL "Solution Items") + set(RunCMake_TEST_FAILED "Invalid Solution Items path: '${root_group_path}'" PARENT_SCOPE) + return() + endif() + MapFind(PROJECT_PARENTS "${root_group_guid}" root_group_parent_guid) + if(DEFINED root_group_parent_guid) + set(RunCMake_TEST_FAILED "Solution Items is nested" PARENT_SCOPE) + return() + endif() + + MapFind(GROUP_GUIDS "Outer Group" outer_group_guid) + if(NOT DEFINED outer_group_guid) + set(RunCMake_TEST_FAILED "Outer Group not found" PARENT_SCOPE) + return() + endif() + MapFind(GROUP_PATHS "Outer Group" outer_group_path) + if(NOT "${outer_group_path}" STREQUAL "Outer Group") + set(RunCMake_TEST_FAILED "Invalid Outer Group path: '${outer_group_path}'" PARENT_SCOPE) + return() + endif() + MapFind(PROJECT_PARENTS "${outer_group_guid}" outer_group_parent_guid) + if(DEFINED outer_group_parent_guid) + set(RunCMake_TEST_FAILED "Outer Group is nested" PARENT_SCOPE) + return() + endif() + + MapFind(GROUP_GUIDS "Inner Group" inner_group_guid) + if(NOT DEFINED inner_group_guid) + set(RunCMake_TEST_FAILED "Inner Group not found" PARENT_SCOPE) + return() + endif() + MapFind(GROUP_PATHS "Inner Group" inner_group_path) + if(NOT "${inner_group_path}" STREQUAL "Outer Group\\Inner Group") + set(RunCMake_TEST_FAILED "Invalid Inner Group path: '${inner_group_path}'" PARENT_SCOPE) + return() + endif() + MapFind(PROJECT_PARENTS "${inner_group_guid}" inner_group_parent_guid) + if(NOT DEFINED inner_group_parent_guid) + set(RunCMake_TEST_FAILED "Inner Group is not nested" PARENT_SCOPE) + return() + endif() + if(NOT "${inner_group_parent_guid}" STREQUAL "${outer_group_guid}") + set(RunCMake_TEST_FAILED "Inner Group is not nested within Outer Group" PARENT_SCOPE) + return() + endif() + + + # Check solution items and nesting: + MapLength(SOLUTION_ITEMS solution_item_count) + if(NOT "${solution_item_count}" EQUAL 4) + set(RunCMake_TEST_FAILED "Unexpected number of solution items: ${solution_item_count}") + return() + endif() + + MapFind(SOLUTION_ITEMS "solution-item-0-1.txt" group_name) + if(NOT DEFINED group_name) + set(RunCMake_TEST_FAILED "Solution item not found: solution-item-0-1.txt") + return() + endif() + if(NOT "${group_name}" STREQUAL "Solution Items") + set(RunCMake_TEST_FAILED "Invalid group for solution-item-0-1.txt: '${group_name}'") + return() + endif() + + MapFind(SOLUTION_ITEMS "solution-item-1-1.txt" group_name) + if(NOT DEFINED group_name) + set(RunCMake_TEST_FAILED "Solution item not found: solution-item-1-1.txt") + return() + endif() + if(NOT "${group_name}" STREQUAL "Outer Group") + set(RunCMake_TEST_FAILED "Invalid group for solution-item-1-1.txt: '${group_name}'") + return() + endif() + + MapFind(SOLUTION_ITEMS "solution-item-2-1.txt" group_name) + if(NOT DEFINED group_name) + set(RunCMake_TEST_FAILED "Solution item not found: solution-item-2-1.txt") + return() + endif() + if(NOT "${group_name}" STREQUAL "Inner Group") + set(RunCMake_TEST_FAILED "Invalid group for solution-item-2-1.txt: '${group_name}'") + return() + endif() + + MapFind(SOLUTION_ITEMS "solution-item-2-2.txt" group_name) + if(NOT DEFINED group_name) + set(RunCMake_TEST_FAILED "Solution item not found: solution-item-2-2.txt") + return() + endif() + if(NOT "${group_name}" STREQUAL "Inner Group") + set(RunCMake_TEST_FAILED "Invalid group for solution-item-2-2.txt: '${group_name}'") + return() + endif() +endblock() + + +# Check the nested solution: +block() + ParseSln("${RunCMake_TEST_BINARY_DIR}/SolutionItems/SolutionItemsSubproject.sln") + + if(DEFINED RunCMake_TEST_FAILED) + set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE) + return() + endif() + + + # Check group guids and nesting: + + MapFind(GROUP_GUIDS "Extraneous" root_group_guid) + if(NOT DEFINED root_group_guid) + set(RunCMake_TEST_FAILED "Extraneous not found" PARENT_SCOPE) + return() + endif() + MapFind(GROUP_PATHS "Extraneous" root_group_path) + if(NOT "${root_group_path}" STREQUAL "Extraneous") + set(RunCMake_TEST_FAILED "Invalid Extraneous path: '${root_group_path}'" PARENT_SCOPE) + return() + endif() + MapFind(PROJECT_PARENTS "${root_group_guid}" root_group_parent_guid) + if(DEFINED root_group_parent_guid) + set(RunCMake_TEST_FAILED "Extraneous is nested" PARENT_SCOPE) + return() + endif() + + + # Check solution items and nesting: + + MapLength(SOLUTION_ITEMS solution_item_count) + if(NOT "${solution_item_count}" EQUAL 1) + set(RunCMake_TEST_FAILED "Unexpected number of solution items: ${solution_item_count}" PARENT_SCOPE) + return() + endif() + + MapFind(SOLUTION_ITEMS "extraneous.txt" group_name) + if(NOT DEFINED group_name) + set(RunCMake_TEST_FAILED "Solution item not found: extraneous.txt" PARENT_SCOPE) + return() + endif() + if(NOT "${group_name}" STREQUAL "Extraneous") + set(RunCMake_TEST_FAILED "Invalid group for extraneous.txt: '${group_name}'" PARENT_SCOPE) + return() + endif() +endblock() diff --git a/Tests/RunCMake/VS10Project/SolutionItems.cmake b/Tests/RunCMake/VS10Project/SolutionItems.cmake new file mode 100644 index 0000000000..ae4f5a4d7a --- /dev/null +++ b/Tests/RunCMake/VS10Project/SolutionItems.cmake @@ -0,0 +1,4 @@ +set_property(DIRECTORY APPEND PROPERTY VS_SOLUTION_ITEMS solution-item-0-1.txt solution-item-1-1.txt solution-item-2-1.txt solution-item-2-2.txt) +source_group("Outer Group" FILES solution-item-1-1.txt) +source_group("Outer Group/Inner Group" REGULAR_EXPRESSION "solution-item-2-[0-9]+\\.txt") +add_subdirectory(SolutionItems) diff --git a/Tests/RunCMake/VS10Project/SolutionItems/CMakeLists.txt b/Tests/RunCMake/VS10Project/SolutionItems/CMakeLists.txt new file mode 100644 index 0000000000..320b7a53e3 --- /dev/null +++ b/Tests/RunCMake/VS10Project/SolutionItems/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.10) +project(SolutionItemsSubproject) +set_property(DIRECTORY APPEND PROPERTY VS_SOLUTION_ITEMS extraneous.txt) +source_group("Extraneous" REGULAR_EXPRESSION "[^.]+\\.txt") diff --git a/Tests/RunCMake/VS10Project/SolutionItems/extraneous.txt b/Tests/RunCMake/VS10Project/SolutionItems/extraneous.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/RunCMake/VS10Project/solution-item-0-1.txt b/Tests/RunCMake/VS10Project/solution-item-0-1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/RunCMake/VS10Project/solution-item-1-1.txt b/Tests/RunCMake/VS10Project/solution-item-1-1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/RunCMake/VS10Project/solution-item-2-1.txt b/Tests/RunCMake/VS10Project/solution-item-2-1.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/RunCMake/VS10Project/solution-item-2-2.txt b/Tests/RunCMake/VS10Project/solution-item-2-2.txt new file mode 100644 index 0000000000..e69de29bb2