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. /// 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: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -1026,8 +1026,92 @@ return NoStyle; } +static llvm::SmallVector 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 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 SearchPaths = getStyleSearchPaths(); + llvm::SmallVector Paths; + for (const std::string &Path: SearchPaths) + Paths.append({Twine(Path.c_str(), Name)}); + Paths.append({Name}); + + for (Twine Path: Paths) { + llvm::SmallVector RealPath; + Twine ConfigFile{!FS->getRealPath(Path, RealPath) ? RealPath : Path}; + if (FS->exists(ConfigFile)) { + llvm::ErrorOr> 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: 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 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.