Просмотр исходного кода

source_group: Add options create groups matching directory tree

Add `TREE` and `PREFIX` arguments to enable this behavior.
Mateusz Janek 9 лет назад
Родитель
Сommit
b42330be21

+ 16 - 2
Help/command/source_group.rst

@@ -2,15 +2,27 @@ source_group
 ------------
 
 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(TREE <root> [PREFIX <prefix>] [FILES <src>...])
 
 Defines a group into which sources will be placed in project files.
 This is intended to set up file tabs in Visual Studio.
 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``
  Any source file specified explicitly will be placed in group
  ``<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
 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
 
   source_group(outer\\inner ...)
+  source_group(TREE <root> PREFIX sources\\inc ...)
 
 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 "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;
 
 // cmSourceGroupCommand
@@ -19,6 +112,17 @@ bool cmSourceGroupCommand::InitialPass(std::vector<std::string> const& args,
     return false;
   }
 
+  if (args[0] == "TREE") {
+    std::string error;
+
+    if (!processTree(args, error)) {
+      this->SetError(error);
+      return false;
+    }
+
+    return true;
+  }
+
   std::string delimiter = "\\";
   if (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;
 }
+
+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.
    */
   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

+ 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
 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 barbar(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()
 {
   printf("foo: %d bar: %d foobar: %d barbar: %d baz: %d\n", foo(), bar(),
          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;
 }

+ 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;
+}