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