Add flag to include additional files in the generated Xcode project

The chromium developer have found it useful to add `*.md` files to
Xcode project so that they can be edited from Xcode. Add an option
to gn Xcode project generator to include them (to remove the need
to use a post-processing of the project in Chromium).

Bug: none
Change-Id: I0ac0eb6529735292b592cff4654259a2b1a61ff9
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/14160
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
diff --git a/docs/reference.md b/docs/reference.md
index 084398d..ef9a324 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -845,6 +845,17 @@
       One useful value is to use Xcode variables such as '${CONFIGURATION}'
       or '${EFFECTIVE_PLATFORM}'.
 
+  --xcode-additional-files-patterns=<pattern_list>
+      If present, must be a list of semicolon-separated file patterns. It
+      will be used to add all files matching the pattern located in the
+      source tree to the project. It can be used to add, e.g. documentation
+      files to the project to allow easily edit them.
+
+  --xcode-additional-files-roots=<path_list>
+      If present, must be a list of semicolon-separated paths. It will be used
+      as roots when looking for additional files to add. If ommitted, defaults
+      to "//".
+
   --ninja-executable=<string>
       Can be used to specify the ninja executable to use when building.
 
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index fb68cf2..5c6cc14 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -58,6 +58,9 @@
 const char kSwitchXcodeBuildsystemValueNew[] = "new";
 const char kSwitchXcodeConfigurations[] = "xcode-configs";
 const char kSwitchXcodeConfigurationBuildPath[] = "xcode-config-build-dir";
+const char kSwitchXcodeAdditionalFilesPatterns[] =
+    "xcode-additional-files-patterns";
+const char kSwitchXcodeAdditionalFilesRoots[] = "xcode-additional-files-roots";
 const char kSwitchJsonFileName[] = "json-file-name";
 const char kSwitchJsonIdeScript[] = "json-ide-script";
 const char kSwitchJsonIdeScriptArgs[] = "json-ide-script-args";
@@ -261,6 +264,8 @@
         command_line->GetSwitchValueASCII(kSwitchFilters),
         command_line->GetSwitchValueASCII(kSwitchXcodeConfigurations),
         command_line->GetSwitchValuePath(kSwitchXcodeConfigurationBuildPath),
+        command_line->GetSwitchValueNative(kSwitchXcodeAdditionalFilesPatterns),
+        command_line->GetSwitchValueNative(kSwitchXcodeAdditionalFilesRoots),
         XcodeBuildSystem::kLegacy,
     };
 
@@ -537,6 +542,17 @@
       One useful value is to use Xcode variables such as '${CONFIGURATION}'
       or '${EFFECTIVE_PLATFORM}'.
 
+  --xcode-additional-files-patterns=<pattern_list>
+      If present, must be a list of semicolon-separated file patterns. It
+      will be used to add all files matching the pattern located in the
+      source tree to the project. It can be used to add, e.g. documentation
+      files to the project to allow easily edit them.
+
+  --xcode-additional-files-roots=<path_list>
+      If present, must be a list of semicolon-separated paths. It will be used
+      as roots when looking for additional files to add. If ommitted, defaults
+      to "//".
+
   --ninja-executable=<string>
       Can be used to specify the ninja executable to use when building.
 
diff --git a/src/gn/xcode_object.cc b/src/gn/xcode_object.cc
index ab357bb..4399c55 100644
--- a/src/gn/xcode_object.cc
+++ b/src/gn/xcode_object.cc
@@ -116,6 +116,7 @@
     {"js", "sourcecode.javascript"},
     {"kext", "wrapper.kext"},
     {"m", "sourcecode.c.objc"},
+    {"md", "net.daringfireball.markdown"},
     {"mm", "sourcecode.cpp.objcpp"},
     {"nib", "wrapper.nib"},
     {"o", "compiled.mach-o.objfile"},
diff --git a/src/gn/xcode_writer.cc b/src/gn/xcode_writer.cc
index 0ec1fa8..8216aa4 100644
--- a/src/gn/xcode_writer.cc
+++ b/src/gn/xcode_writer.cc
@@ -15,6 +15,7 @@
 #include <utility>
 
 #include "base/environment.h"
+#include "base/files/file_enumerator.h"
 #include "base/logging.h"
 #include "base/sha1.h"
 #include "base/stl_util.h"
@@ -195,6 +196,49 @@
   dependent_pbxtarget->AddDependency(std::move(dependency));
 }
 
+// Returns a SourceFile for absolute path `file_path` below `//`.
+SourceFile FilePathToSourceFile(const BuildSettings* build_settings,
+                                const base::FilePath& file_path) {
+  const std::string file_path_utf8 = FilePathToUTF8(file_path);
+  return SourceFile("//" + file_path_utf8.substr(
+                               build_settings->root_path_utf8().size() + 1));
+}
+
+// Returns the list of patterns to use when looking for additional files
+// from `options`.
+std::vector<base::FilePath::StringType> GetAdditionalFilesPatterns(
+    const XcodeWriter::Options& options) {
+  return base::SplitString(options.additional_files_patterns,
+                           FILE_PATH_LITERAL(";"), base::TRIM_WHITESPACE,
+                           base::SPLIT_WANT_ALL);
+}
+
+// Returns the list of roots to use when looking for additional files
+// from `options`.
+std::vector<base::FilePath> GetAdditionalFilesRoots(
+    const BuildSettings* build_settings,
+    const XcodeWriter::Options& options) {
+  if (options.additional_files_roots.empty()) {
+    return {build_settings->root_path()};
+  }
+
+  const std::vector<base::FilePath::StringType> roots =
+      base::SplitString(options.additional_files_roots, FILE_PATH_LITERAL(";"),
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+  std::vector<base::FilePath> root_paths;
+  for (const base::FilePath::StringType& root : roots) {
+    const std::string rebased_root =
+        RebasePath(FilePathToUTF8(root), SourceDir("//"),
+                   build_settings->root_path_utf8());
+
+    root_paths.push_back(
+        build_settings->root_path().Append(UTF8ToFilePath(rebased_root)));
+  }
+
+  return root_paths;
+}
+
 // Helper class to resolve list of XCTest files per target.
 //
 // Uses a cache of file found per intermediate targets to reduce the need
@@ -639,14 +683,34 @@
     if (!build_settings_->root_path().IsParent(path))
       continue;
 
-    const std::string as8bit = path.As8Bit();
-    const SourceFile source(
-        "//" + as8bit.substr(build_settings_->root_path().value().size() + 1));
-
+    const SourceFile source = FilePathToSourceFile(build_settings_, path);
     if (ShouldIncludeFileInProject(source))
       sources.insert(source);
   }
 
+  // Add any files from --xcode-additional-files-patterns, using the root
+  // listed in --xcode-additional-files-roots.
+  if (!options_.additional_files_patterns.empty()) {
+    const std::vector<base::FilePath::StringType> patterns =
+        GetAdditionalFilesPatterns(options_);
+    const std::vector<base::FilePath> roots =
+        GetAdditionalFilesRoots(build_settings_, options_);
+
+    for (const base::FilePath& root : roots) {
+      for (const base::FilePath::StringType& pattern : patterns) {
+        base::FileEnumerator it(root, /*recursive*/ true,
+                                base::FileEnumerator::FILES, pattern,
+                                base::FileEnumerator::FolderSearchPolicy::ALL);
+
+        for (base::FilePath path = it.Next(); !path.empty(); path = it.Next()) {
+          const SourceFile source = FilePathToSourceFile(build_settings_, path);
+          if (ShouldIncludeFileInProject(source))
+            sources.insert(source);
+        }
+      }
+    }
+  }
+
   // Sort files to ensure deterministic generation of the project file (and
   // nicely sorted file list in Xcode).
   std::vector<SourceFile> sorted_sources(sources.begin(), sources.end());
diff --git a/src/gn/xcode_writer.h b/src/gn/xcode_writer.h
index 7edd674..9c960e5 100644
--- a/src/gn/xcode_writer.h
+++ b/src/gn/xcode_writer.h
@@ -56,6 +56,18 @@
     // as the project directory.
     base::FilePath configuration_build_dir;
 
+    // If specified, should be a semicolon-separated list of file patterns.
+    // It will be used to add files to the project that are not referenced
+    // from the BUILD.gn files. This is usually used to add documentation
+    // files.
+    base::FilePath::StringType additional_files_patterns;
+
+    // If specified, should be a semicolon-separated list of file roots.
+    // It will be used to add files to the project that are not referenced
+    // from the BUILD.gn files. This is usually used to add documentation
+    // files.
+    base::FilePath::StringType additional_files_roots;
+
     // Control which version of the build system should be used for the
     // generated Xcode project.
     XcodeBuildSystem build_system = XcodeBuildSystem::kLegacy;