Przeglądaj źródła

source_group: Add options create groups matching directory tree

Add `TREE` and `PREFIX` arguments to enable this behavior.
Mateusz Janek 9 lat temu
rodzic
commit
b42330be21

+ 16 - 2
Help/command/source_group.rst

@@ -2,15 +2,27 @@ source_group
 ------------
 ------------
 
 
 Define a grouping for source files in IDE project generation.
 Define a grouping for source files in IDE project generation.
+There are two different signatures to create source groups.
 
 
-.. code-block:: cmake
+::
 
 
   source_group(<name> [FILES <src>...] [REGULAR_EXPRESSION <regex>])
   source_group(<name> [FILES <src>...] [REGULAR_EXPRESSION <regex>])
+  source_group(TREE <root> [PREFIX <prefix>] [FILES <src>...])
 
 
 Defines a group into which sources will be placed in project files.
 Defines a group into which sources will be placed in project files.
 This is intended to set up file tabs in Visual Studio.
 This is intended to set up file tabs in Visual Studio.
 The options are:
 The options are:
 
 
+``TREE``
+ CMake will automatically detect, from ``<src>`` files paths, source groups
+ it needs to create, to keep structure of source groups analogically to the
+ actual files and directories structure in the project. Paths of ``<src>``
+ files will be cut to be relative to ``<root>``.
+
+``PREFIX``
+ Source group and files located directly in ``<root>`` path, will be placed
+ in ``<prefix>`` source groups.
+
 ``FILES``
 ``FILES``
  Any source file specified explicitly will be placed in group
  Any source file specified explicitly will be placed in group
  ``<name>``.  Relative paths are interpreted with respect to the
  ``<name>``.  Relative paths are interpreted with respect to the
@@ -25,11 +37,13 @@ explicitly lists the file with ``FILES`` will be favored, if any.
 If no group explicitly lists the file, the *last* group whose
 If no group explicitly lists the file, the *last* group whose
 regular expression matches the file will be favored.
 regular expression matches the file will be favored.
 
 
-The ``<name>`` of the group may contain backslashes to specify subgroups:
+The ``<name>`` of the group and ``<prefix>`` argument may contain backslashes
+to specify subgroups:
 
 
 .. code-block:: cmake
 .. code-block:: cmake
 
 
   source_group(outer\\inner ...)
   source_group(outer\\inner ...)
+  source_group(TREE <root> PREFIX sources\\inc ...)
 
 
 For backwards compatibility, the short-hand signature
 For backwards compatibility, the short-hand signature
 
 

+ 5 - 0
Help/release/dev/source_group-tree.rst

@@ -0,0 +1,5 @@
+source_group-tree
+-----------------
+
+* The :command:`source_group` command gained ``TREE`` and ``PREFIX``
+  options to add groups following source tree directory structure.

+ 165 - 0
Source/cmSourceGroupCommand.cxx

@@ -8,6 +8,99 @@
 #include "cmSourceGroup.h"
 #include "cmSourceGroup.h"
 #include "cmSystemTools.h"
 #include "cmSystemTools.h"
 
 
+namespace {
+const size_t RootIndex = 1;
+const size_t FilesWithoutPrefixKeywordIndex = 2;
+const size_t FilesWithPrefixKeywordIndex = 4;
+const size_t PrefixKeywordIdex = 2;
+
+std::vector<std::string> tokenizePath(const std::string& path)
+{
+  return cmSystemTools::tokenize(path, "\\/");
+}
+
+std::string getFullFilePath(const std::string& currentPath,
+                            const std::string& path)
+{
+  std::string fullPath = path;
+
+  if (!cmSystemTools::FileIsFullPath(path.c_str())) {
+    fullPath = currentPath;
+    fullPath += "/";
+    fullPath += path;
+  }
+
+  return cmSystemTools::CollapseFullPath(fullPath);
+}
+
+std::set<std::string> getSourceGroupFilesPaths(
+  const std::string& currentPath, const std::string& root,
+  const std::vector<std::string>& files)
+{
+  std::set<std::string> ret;
+  const std::string::size_type rootLength = root.length();
+
+  for (size_t i = 0; i < files.size(); ++i) {
+    const std::string fullPath = getFullFilePath(currentPath, files[i]);
+
+    ret.insert(fullPath.substr(rootLength + 1)); // +1 to also omnit last '/'
+  }
+
+  return ret;
+}
+
+cmSourceGroup* addSourceGroup(const std::vector<std::string>& tokenizedPath,
+                              cmMakefile& makefile)
+{
+  cmSourceGroup* sg;
+
+  sg = makefile.GetSourceGroup(tokenizedPath);
+  if (!sg) {
+    makefile.AddSourceGroup(tokenizedPath);
+    sg = makefile.GetSourceGroup(tokenizedPath);
+    if (!sg) {
+      return CM_NULLPTR;
+    }
+  }
+
+  return sg;
+}
+
+bool addFilesToItsSourceGroups(const std::set<std::string>& sgFilesPaths,
+                               const std::string& prefix, cmMakefile& makefile,
+                               std::string& errorMsg)
+{
+  cmSourceGroup* sg;
+
+  for (std::set<std::string>::const_iterator it = sgFilesPaths.begin();
+       it != sgFilesPaths.end(); ++it) {
+
+    std::vector<std::string> tokenizedPath;
+    if (!prefix.empty()) {
+      tokenizedPath = tokenizePath(prefix + '/' + *it);
+    } else {
+      tokenizedPath = tokenizePath(*it);
+    }
+
+    if (tokenizedPath.size() > 1) {
+      tokenizedPath.pop_back();
+
+      sg = addSourceGroup(tokenizedPath, makefile);
+
+      if (!sg) {
+        errorMsg = "Could not create source group for file: " + *it;
+        return false;
+      }
+      const std::string fullPath =
+        getFullFilePath(makefile.GetCurrentSourceDirectory(), *it);
+      sg->AddGroupFile(fullPath);
+    }
+  }
+
+  return true;
+}
+}
+
 class cmExecutionStatus;
 class cmExecutionStatus;
 
 
 // cmSourceGroupCommand
 // cmSourceGroupCommand
@@ -19,6 +112,17 @@ bool cmSourceGroupCommand::InitialPass(std::vector<std::string> const& args,
     return false;
     return false;
   }
   }
 
 
+  if (args[0] == "TREE") {
+    std::string error;
+
+    if (!processTree(args, error)) {
+      this->SetError(error);
+      return false;
+    }
+
+    return true;
+  }
+
   std::string delimiter = "\\";
   std::string delimiter = "\\";
   if (this->Makefile->GetDefinition("SOURCE_GROUP_DELIMITER")) {
   if (this->Makefile->GetDefinition("SOURCE_GROUP_DELIMITER")) {
     delimiter = this->Makefile->GetDefinition("SOURCE_GROUP_DELIMITER");
     delimiter = this->Makefile->GetDefinition("SOURCE_GROUP_DELIMITER");
@@ -82,3 +186,64 @@ bool cmSourceGroupCommand::InitialPass(std::vector<std::string> const& args,
 
 
   return true;
   return true;
 }
 }
+
+bool cmSourceGroupCommand::checkTreeArgumentsPreconditions(
+  const std::vector<std::string>& args, std::string& errorMsg) const
+{
+  if (args.size() == 1) {
+    errorMsg = "TREE argument given without a root.";
+    return false;
+  }
+
+  if (args.size() < 3) {
+    errorMsg = "Missing FILES arguments.";
+    return false;
+  }
+
+  if (args[FilesWithoutPrefixKeywordIndex] != "FILES" &&
+      args[PrefixKeywordIdex] != "PREFIX") {
+    errorMsg = "Unknown argument \"" + args[2] +
+      "\". Perhaps the FILES keyword is missing.\n";
+    return false;
+  }
+
+  if (args[PrefixKeywordIdex] == "PREFIX" &&
+      (args.size() < 5 || args[FilesWithPrefixKeywordIndex] != "FILES")) {
+    errorMsg = "Missing FILES arguments.";
+    return false;
+  }
+
+  return true;
+}
+
+bool cmSourceGroupCommand::processTree(const std::vector<std::string>& args,
+                                       std::string& errorMsg)
+{
+  if (!checkTreeArgumentsPreconditions(args, errorMsg)) {
+    return false;
+  }
+
+  const std::string root = cmSystemTools::CollapseFullPath(args[RootIndex]);
+  std::string prefix;
+  size_t filesBegin = FilesWithoutPrefixKeywordIndex + 1;
+  if (args[PrefixKeywordIdex] == "PREFIX") {
+    prefix = args[PrefixKeywordIdex + 1];
+    filesBegin = FilesWithPrefixKeywordIndex + 1;
+  }
+
+  const std::vector<std::string> filesVector(args.begin() + filesBegin,
+                                             args.end());
+
+  std::set<std::string> sourceGroupPaths = getSourceGroupFilesPaths(
+    this->Makefile->GetCurrentSourceDirectory(), root, filesVector);
+
+  addFilesToItsSourceGroups(sourceGroupPaths, prefix, *(this->Makefile),
+                            errorMsg);
+
+  if (!errorMsg.empty()) {
+    this->SetError(errorMsg);
+    return false;
+  }
+
+  return true;
+}

+ 6 - 0
Source/cmSourceGroupCommand.h

@@ -36,6 +36,12 @@ public:
    * The name of the command as specified in CMakeList.txt.
    * The name of the command as specified in CMakeList.txt.
    */
    */
   std::string GetName() const CM_OVERRIDE { return "source_group"; }
   std::string GetName() const CM_OVERRIDE { return "source_group"; }
+
+private:
+  bool processTree(const std::vector<std::string>& args,
+                   std::string& errorMsg);
+  bool checkTreeArgumentsPreconditions(const std::vector<std::string>& args,
+                                       std::string& errorMsg) const;
 };
 };
 
 
 #endif
 #endif

+ 12 - 1
Tests/SourceGroups/CMakeLists.txt

@@ -30,6 +30,17 @@ source_group(Base\\Sub1\\Base FILES bar.c)
 # a group without files, is currently not created
 # a group without files, is currently not created
 source_group(EmptyGroup)
 source_group(EmptyGroup)
 
 
+set(root ${CMAKE_CURRENT_SOURCE_DIR})
 
 
-add_executable(SourceGroups main.c bar.c foo.c sub1/foo.c sub1/foobar.c baz.c README.txt)
+set(tree_files_without_prefix ${root}/sub1/tree_bar.c
+                              ${root}/sub1/tree_baz.c
+                              ${root}/sub1/tree_subdir/tree_foobar.c)
 
 
+set(tree_files_with_prefix ${root}/tree_foo.c)
+
+source_group(TREE ${root} FILES ${tree_files_without_prefix})
+
+source_group(TREE ${root} PREFIX tree_root/subgroup FILES ${tree_files_with_prefix})
+
+add_executable(SourceGroups main.c bar.c foo.c sub1/foo.c sub1/foobar.c baz.c
+                            ${tree_files_with_prefix} ${tree_files_without_prefix} README.txt)

+ 7 - 0
Tests/SourceGroups/main.c

@@ -5,10 +5,17 @@ extern int bar(void);
 extern int foobar(void);
 extern int foobar(void);
 extern int barbar(void);
 extern int barbar(void);
 extern int baz(void);
 extern int baz(void);
+extern int tree_foo(void);
+extern int tree_bar(void);
+extern int tree_foobar(void);
+extern int tree_baz(void);
 
 
 int main()
 int main()
 {
 {
   printf("foo: %d bar: %d foobar: %d barbar: %d baz: %d\n", foo(), bar(),
   printf("foo: %d bar: %d foobar: %d barbar: %d baz: %d\n", foo(), bar(),
          foobar(), barbar(), baz());
          foobar(), barbar(), baz());
+
+  printf("tree_foo: %d tree_bar: %d tree_foobar: %d tree_baz: %d\n",
+         tree_foo(), tree_bar(), tree_foobar(), tree_baz());
   return 0;
   return 0;
 }
 }

+ 4 - 0
Tests/SourceGroups/sub1/tree_bar.c

@@ -0,0 +1,4 @@
+int tree_bar(void)
+{
+  return 8;
+}

+ 4 - 0
Tests/SourceGroups/sub1/tree_baz.c

@@ -0,0 +1,4 @@
+int tree_baz(void)
+{
+  return 9;
+}

+ 4 - 0
Tests/SourceGroups/sub1/tree_subdir/tree_foobar.c

@@ -0,0 +1,4 @@
+int tree_foobar(void)
+{
+  return 7;
+}

+ 4 - 0
Tests/SourceGroups/tree_foo.c

@@ -0,0 +1,4 @@
+int tree_foo(void)
+{
+  return 6;
+}