Typz updated this revision to Diff 201693.
Typz marked 3 inline comments as done.
Typz added a comment.
Herald added subscribers: ormris, mgorny.

- Rebased
- Adapt styles search path to platform conventions
- Allow customizing search path at compile time
- Allow overriding search path at runtime through CLANG_FORMAT_STYLES_PATH env 
variable


Repository:
  rC Clang

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D50147/new/

https://reviews.llvm.org/D50147

Files:
  include/clang/Format/Format.h
  lib/Format/CMakeLists.txt
  lib/Format/Format.cpp
  unittests/Format/FormatTest.cpp

Index: unittests/Format/FormatTest.cpp
===================================================================
--- unittests/Format/FormatTest.cpp
+++ unittests/Format/FormatTest.cpp
@@ -13185,6 +13185,124 @@
   ASSERT_EQ(*Style1, getGoogleStyle());
 }
 
+namespace {
+class EnvSetter {
+    StringRef m_key;
+    Optional<std::string> m_oldValue;
+public:
+    EnvSetter(StringRef key, StringRef value = StringRef()) : m_key(key) {
+      if (const char *oldValue = ::getenv(m_key.data()))
+        m_oldValue.emplace(oldValue);
+      if (value.empty())
+        ::unsetenv(m_key.data());
+      else
+        ::setenv(m_key.data(), value.data(), 1);
+    }
+    ~EnvSetter() {
+      if (m_oldValue)
+        ::setenv(m_key.data(), m_oldValue.getValue().data(), 1);
+    }
+    EnvSetter(const EnvSetter &) = delete;
+    EnvSetter &operator=(const EnvSetter &) = delete;
+};
+}
+
+TEST(FormatStyle, GetExternalStyle) {
+  // Override HOME environment variable to make tests independant of actual user
+  EnvSetter homeEnv("HOME", "/home");
+
+  // Clear CLANG_FORMAT_STYLES_PATH environment variable
+  EnvSetter stylesPathEnv("CLANG_FORMAT_STYLES_PATH");
+
+  llvm::vfs::InMemoryFileSystem FS;
+
+  // Test 1: format file in /usr/local/share/clang-format/
+  ASSERT_TRUE(
+      FS.addFile("/usr/local/share/clang-format/style1", 0,
+                 llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google")));
+  auto Style1 = getStyle("style1", "", "LLVM", "", &FS);
+  ASSERT_TRUE((bool)Style1);
+  ASSERT_EQ(*Style1, getGoogleStyle());
+
+  // Test 2: format file in /usr/share/clang-format/
+  ASSERT_TRUE(
+      FS.addFile("/usr/share/clang-format/style2", 0,
+                 llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google")));
+  auto Style2 = getStyle("style2", "", "LLVM", "", &FS);
+  ASSERT_TRUE((bool)Style2);
+  ASSERT_EQ(*Style2, getGoogleStyle());
+
+  // Test 3: format file in ~/.local/share/clang-format/
+  ASSERT_TRUE(
+      FS.addFile("/home/.local/share/clang-format/style3", 0,
+                 llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google")));
+  auto Style3 = getStyle("style3", "", "LLVM", "", &FS);
+  ASSERT_TRUE((bool)Style3);
+  ASSERT_EQ(*Style3, getGoogleStyle());
+
+  // Test 4: format file in absolute path
+  ASSERT_TRUE(
+      FS.addFile("/clang-format-styles/style4", 0,
+                 llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google")));
+  auto Style4 = getStyle("/clang-format-styles/style4", "", "LLVM", "", &FS);
+  ASSERT_TRUE((bool)Style4);
+  ASSERT_EQ(*Style4, getGoogleStyle());
+
+  // Test 5: format file in relative path
+  ASSERT_TRUE(
+      FS.addFile("/clang-format-styles/style5", 0,
+                 llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google")));
+  FS.setCurrentWorkingDirectory("/clang-format-styles");
+  auto Style5 = getStyle("style5", "", "LLVM", "", &FS);
+  ASSERT_TRUE((bool)Style5);
+  ASSERT_EQ(*Style5, getGoogleStyle());
+  FS.setCurrentWorkingDirectory("/");
+
+  // Test 6: file does not exist
+  auto Style6 = getStyle("style6", "", "LLVM", "", &FS);
+  ASSERT_FALSE((bool)Style6);
+  llvm::consumeError(Style6.takeError());
+
+  // Test 7: absolute file does not exist
+  auto Style7 = getStyle("/style7", "", "LLVM", "", &FS);
+  ASSERT_FALSE((bool)Style7);
+  llvm::consumeError(Style7.takeError());
+
+  // Test 8: file is not a format style
+  ASSERT_TRUE(
+      FS.addFile("/usr/local/share/clang-format/nostyle", 0,
+                 llvm::MemoryBuffer::getMemBuffer("This is not a style...")));
+  FS.setCurrentWorkingDirectory("/home/clang-format-styles");
+  auto Style8 = getStyle("nostyle", "", "LLVM", "", &FS);
+  ASSERT_FALSE((bool)Style8);
+  llvm::consumeError(Style8.takeError());
+
+  {
+    // Override styles path
+    EnvSetter customStylesPathEnv("CLANG_FORMAT_STYLES_PATH", "/customPath1:/customPath2");
+
+    // Test 8: file in custom styles path
+    ASSERT_TRUE(
+        FS.addFile("/customPath1/style8_1", 0,
+                   llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Google")));
+    auto Style8_1 = getStyle("style8_1", "", "LLVM", "", &FS);
+    ASSERT_TRUE((bool)Style8_1);
+    ASSERT_EQ(*Style8_1, getGoogleStyle());
+
+    ASSERT_TRUE(
+        FS.addFile("/customPath2/style8_2", 0,
+                   llvm::MemoryBuffer::getMemBuffer("BasedOnStyle: Mozilla")));
+    auto Style8_2 = getStyle("style8_2", "", "LLVM", "", &FS);
+    ASSERT_TRUE((bool)Style8_2);
+    ASSERT_EQ(*Style8_2, getMozillaStyle());
+
+    // Test 9: cannot find file in standard styles path (/usr/local/share/clang-format/style1)
+    auto Style9 = getStyle("style1", "", "LLVM", "", &FS);
+    ASSERT_FALSE((bool)Style9);
+    llvm::consumeError(Style9.takeError());
+  }
+}
+
 TEST(FormatStyle, GetStyleOfFile) {
   llvm::vfs::InMemoryFileSystem FS;
   // Test 1: format file in the same directory.
Index: lib/Format/Format.cpp
===================================================================
--- lib/Format/Format.cpp
+++ lib/Format/Format.cpp
@@ -1026,8 +1026,92 @@
   return NoStyle;
 }
 
+static llvm::SmallVector<std::string, 3> getStyleSearchPaths() {
+#ifdef WIN32
+  static const char Separator = ';';
+#else
+  static const char Separator = ':';
+#endif
+
+  StringRef SearchPath = CLANG_FORMAT_STYLES_PATH;
+  if (const char *SearchPathEnv = std::getenv("CLANG_FORMAT_STYLES_PATH"))
+    SearchPath = SearchPathEnv;
+
+  llvm::SmallVector<std::string, 3> Dest;
+  while (!SearchPath.empty()) {
+    auto Parts = SearchPath.split(Separator);
+    SearchPath = Parts.second;
+    if (Parts.first.empty())
+      continue;
+
+    SmallString<100> Path;
+    if (Parts.first.startswith("~")) {
+      llvm::sys::fs::expand_tilde({Parts.first}, Path);
+#ifdef WIN32
+    } else if (Parts.first.startswith("%PROGRAMDATA%"))
+      const char *ProgramData = std::getenv("PROGRAMDATA");
+      if (!ProgramData)
+        ProgramData = std::getenv("ALLUSERSPROFILE"); // Fallback for Windows XP
+
+      if (ProgramData) {
+        Path.append(ProgramData);
+      } else if (!llvm::sys::path::getKnownFolderPath(FOLDERID_ProgramData,
+                                                      Path)) {
+        continue;
+      }
+      StringRef Remainder = Parts.first.substr(std::strlen("%PROGRAMDATA%"));
+      Path.append(Remainder.begin(), Remainder.end());
+#endif
+    } else {
+      Path.append(Parts.first.begin(), Parts.first.end());
+    }
+    if (!llvm::sys::path::is_separator(Path.back())) {
+      StringRef separator = llvm::sys::path::get_separator();
+      Path.append(separator.begin(), separator.end());
+    }
+    Dest.append({Path.str().str()});
+  }
+  return std::move(Dest);
+}
+
+// Try loading config file from path, in argument to -style and BasedOnStyle.
+bool loadSystemStyle(StringRef Name, FormatStyle *Style,
+                     llvm::vfs::FileSystem *FS = nullptr) {
+  if (!FS) {
+    FS = llvm::vfs::getRealFileSystem().get();
+  }
+
+  const llvm::SmallVector<std::string, 3> SearchPaths = getStyleSearchPaths();
+  llvm::SmallVector<Twine, 4> Paths;
+  for (const std::string &Path: SearchPaths)
+    Paths.append({Twine(Path.c_str(), Name)});
+  Paths.append({Name});
+
+  for (Twine Path: Paths) {
+    llvm::SmallVector<char, 100> RealPath;
+    Twine ConfigFile{!FS->getRealPath(Path, RealPath) ? RealPath : Path};
+    if (FS->exists(ConfigFile)) {
+      llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
+          FS->getBufferForFile(ConfigFile);
+      if (std::error_code EC = Text.getError()) {
+        LLVM_DEBUG(llvm::dbgs() << "Error reading " << ConfigFile << ": "
+                   << EC.message() << "\n");
+        return false;
+      }
+      if (std::error_code EC =
+              parseConfiguration(Text.get()->getBuffer(), Style)) {
+        LLVM_DEBUG(llvm::dbgs() << "Error parsing " << ConfigFile << ": "
+                   << EC.message() << "\n");
+        return false;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
 bool getPredefinedStyle(StringRef Name, FormatStyle::LanguageKind Language,
-                        FormatStyle *Style) {
+                        FormatStyle *Style, llvm::vfs::FileSystem *FS) {
   if (Name.equals_lower("llvm")) {
     *Style = getLLVMStyle(Language);
   } else if (Name.equals_lower("chromium")) {
@@ -1044,7 +1128,7 @@
     *Style = getMicrosoftStyle(Language);
   } else if (Name.equals_lower("none")) {
     *Style = getNoStyle();
-  } else {
+  } else if (!loadSystemStyle(Name, Style, FS)) {
     return false;
   }
 
@@ -2378,13 +2462,19 @@
 const char *StyleOptionHelpDescription =
     "Coding style, currently supports:\n"
     "  LLVM, Google, Chromium, Mozilla, WebKit.\n"
+    "Additional styles can be installed in any of the folders\n"
+    "specified through the CLANG_FORMAT_STYLES_PATH environment\n"
+    "variable. If not specified, the default search path is:\n"
+    "  " CLANG_FORMAT_STYLES_PATH "\n"
     "Use -style=file to load style configuration from\n"
     ".clang-format file located in one of the parent\n"
     "directories of the source file (or current\n"
     "directory for stdin).\n"
     "Use -style=\"{key: value, ...}\" to set specific\n"
     "parameters, e.g.:\n"
-    "  -style=\"{BasedOnStyle: llvm, IndentWidth: 8}\"";
+    "  -style=\"{BasedOnStyle: llvm, IndentWidth: 8}\"\n"
+    "Other value of the parameter must the path of the config\n"
+    "file to load.";
 
 static FormatStyle::LanguageKind getLanguageByFileName(StringRef FileName) {
   if (FileName.endswith(".java"))
@@ -2451,7 +2541,7 @@
   }
 
   if (!StyleName.equals_lower("file")) {
-    if (!getPredefinedStyle(StyleName, Style.Language, &Style))
+    if (!getPredefinedStyle(StyleName, Style.Language, &Style, FS))
       return make_string_error("Invalid value for -style");
     return Style;
   }
Index: lib/Format/CMakeLists.txt
===================================================================
--- lib/Format/CMakeLists.txt
+++ lib/Format/CMakeLists.txt
@@ -1,5 +1,21 @@
 set(LLVM_LINK_COMPONENTS support)
 
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+    set(CLANG_FORMAT_STYLES_DEFAULT_PATH
+        "~/Library/Application Support/clang-format:/Library/Application Support/clang-format")
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    set(CLANG_FORMAT_STYLES_DEFAULT_PATH
+        "~/AppData/Roaming/clang-format;~/AppData/Local/clang-format;%PROGRAMDATA%/clang-format")
+else (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+    set(CLANG_FORMAT_STYLES_DEFAULT_PATH
+        "~/.local/share/clang-format:/usr/local/share/clang-format:/usr/share/clang-format")
+endif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+
+set(CLANG_FORMAT_STYLES_PATH ${CLANG_FORMAT_STYLES_DEFAULT_PATH} CACHE STRING
+    "Default search path for clang-format styles")
+
+add_definitions(-DCLANG_FORMAT_STYLES_PATH="${CLANG_FORMAT_STYLES_PATH}")
+
 add_clang_library(clangFormat
   AffectedRangeManager.cpp
   BreakableToken.cpp
Index: include/clang/Format/Format.h
===================================================================
--- include/clang/Format/Format.h
+++ include/clang/Format/Format.h
@@ -2030,7 +2030,7 @@
 ///
 /// Returns ``true`` if the Style has been set.
 bool getPredefinedStyle(StringRef Name, FormatStyle::LanguageKind Language,
-                        FormatStyle *Style);
+                        FormatStyle *Style, llvm::vfs::FileSystem *FS = nullptr);
 
 /// Parse configuration from YAML-formatted text.
 ///
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to