[clang-tools-extra] r220199 - [clang-tidy] Add support for custom configuration file names/formats.

Alexander Kornienko alexfh at google.com
Mon Oct 20 05:29:15 PDT 2014


Author: alexfh
Date: Mon Oct 20 07:29:15 2014
New Revision: 220199

URL: http://llvm.org/viewvc/llvm-project?rev=220199&view=rev
Log:
[clang-tidy] Add support for custom configuration file names/formats.

Summary: We're using different clang-tidy frontends (command-line, batch analysis jobs, code review integration), some of which are limited in the choice of configuration format. In order to avoid duplication of configuration information, we need to support the same configuration format in the command-line tool. This patch adds an extension point to make this possible without rewriting FileOptionsProvider.

Reviewers: djasper

Reviewed By: djasper

Subscribers: cfe-commits

Differential Revision: http://reviews.llvm.org/D5821

Modified:
    clang-tools-extra/trunk/clang-tidy/ClangTidyOptions.cpp
    clang-tools-extra/trunk/clang-tidy/ClangTidyOptions.h

Modified: clang-tools-extra/trunk/clang-tidy/ClangTidyOptions.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/ClangTidyOptions.cpp?rev=220199&r1=220198&r2=220199&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/ClangTidyOptions.cpp (original)
+++ clang-tools-extra/trunk/clang-tidy/ClangTidyOptions.cpp Mon Oct 20 07:29:15 2014
@@ -139,10 +139,19 @@ FileOptionsProvider::FileOptionsProvider
     const ClangTidyOptions &OverrideOptions)
     : DefaultOptionsProvider(GlobalOptions, DefaultOptions),
       OverrideOptions(OverrideOptions) {
+  ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
   CachedOptions[""] = DefaultOptions.mergeWith(OverrideOptions);
 }
 
-static const char ConfigFileName[] = ".clang-tidy";
+FileOptionsProvider::FileOptionsProvider(
+    const ClangTidyGlobalOptions &GlobalOptions,
+    const ClangTidyOptions &DefaultOptions,
+    const ClangTidyOptions &OverrideOptions,
+    const FileOptionsProvider::ConfigFileHandlers &ConfigHandlers)
+    : DefaultOptionsProvider(GlobalOptions, DefaultOptions),
+      OverrideOptions(OverrideOptions), ConfigHandlers(ConfigHandlers) {
+  CachedOptions[""] = DefaultOptions.mergeWith(OverrideOptions);
+}
 
 // FIXME: This method has some common logic with clang::format::getStyle().
 // Consider pulling out common bits to a findParentFileWithName function or
@@ -164,7 +173,7 @@ const ClangTidyOptions &FileOptionsProvi
   StringRef Path = llvm::sys::path::parent_path(FileName);
   for (StringRef CurrentPath = Path;;
        CurrentPath = llvm::sys::path::parent_path(CurrentPath)) {
-    llvm::ErrorOr<ClangTidyOptions> Result = std::error_code();
+    llvm::Optional<ClangTidyOptions> Result;
 
     auto Iter = CachedOptions.find(CurrentPath);
     if (Iter != CachedOptions.end())
@@ -183,49 +192,57 @@ const ClangTidyOptions &FileOptionsProvi
       }
       return CachedOptions.GetOrCreateValue(Path, *Result).getValue();
     }
-    if (Result.getError() != llvm::errc::no_such_file_or_directory) {
-      llvm::errs() << "Error reading " << ConfigFileName << " from " << Path
-                   << ": " << Result.getError().message() << "\n";
-    }
   }
 }
 
-llvm::ErrorOr<ClangTidyOptions>
+llvm::Optional<ClangTidyOptions>
 FileOptionsProvider::TryReadConfigFile(StringRef Directory) {
   assert(!Directory.empty());
 
-  if (!llvm::sys::fs::is_directory(Directory))
-    return make_error_code(llvm::errc::not_a_directory);
+  if (!llvm::sys::fs::is_directory(Directory)) {
+    llvm::errs() << "Error reading configuration from " << Directory
+                 << ": directory doesn't exist.\n";
+    return llvm::None;
+  }
+
+  for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) {
+    SmallString<128> ConfigFile(Directory);
+    llvm::sys::path::append(ConfigFile, ConfigHandler.first);
+    DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
+
+    bool IsFile = false;
+    // Ignore errors from is_regular_file: we only need to know if we can read
+    // the file or not.
+    llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile);
+    if (!IsFile)
+      continue;
+
+    llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
+        llvm::MemoryBuffer::getFile(ConfigFile.c_str());
+    if (std::error_code EC = Text.getError()) {
+      llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message()
+                   << "\n";
+      continue;
+    }
+
+    // Skip empty files, e.g. files opened for writing via shell output
+    // redirection.
+    if ((*Text)->getBuffer().empty())
+      continue;
+    llvm::ErrorOr<ClangTidyOptions> ParsedOptions =
+        ConfigHandler.second((*Text)->getBuffer());
+    if (!ParsedOptions) {
+      llvm::errs() << "Error parsing " << ConfigFile << ": "
+                   << ParsedOptions.getError().message() << "\n";
+      continue;
+    }
 
-  SmallString<128> ConfigFile(Directory);
-  llvm::sys::path::append(ConfigFile, ".clang-tidy");
-  DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
-
-  bool IsFile = false;
-  // Ignore errors from is_regular_file: we only need to know if we can read
-  // the file or not.
-  llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile);
-
-  if (!IsFile)
-    return make_error_code(llvm::errc::no_such_file_or_directory);
-
-  llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
-      llvm::MemoryBuffer::getFile(ConfigFile.c_str());
-  if (std::error_code EC = Text.getError())
-    return EC;
-  // Skip empty files, e.g. files opened for writing via shell output
-  // redirection.
-  if ((*Text)->getBuffer().empty())
-    return make_error_code(llvm::errc::no_such_file_or_directory);
-  llvm::ErrorOr<ClangTidyOptions> ParsedOptions =
-      parseConfiguration((*Text)->getBuffer());
-  if (ParsedOptions) {
     ClangTidyOptions Defaults = DefaultOptionsProvider::getOptions(Directory);
     // Only use checks from the config file.
     Defaults.Checks = None;
     return Defaults.mergeWith(*ParsedOptions).mergeWith(OverrideOptions);
   }
-  return ParsedOptions.getError();
+  return llvm::None;
 }
 
 /// \brief Parses -line-filter option and stores it to the \c Options.

Modified: clang-tools-extra/trunk/clang-tidy/ClangTidyOptions.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/ClangTidyOptions.h?rev=220199&r1=220198&r2=220199&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/ClangTidyOptions.h (original)
+++ clang-tools-extra/trunk/clang-tidy/ClangTidyOptions.h Mon Oct 20 07:29:15 2014
@@ -14,6 +14,7 @@
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/ErrorOr.h"
+#include <functional>
 #include <map>
 #include <string>
 #include <system_error>
@@ -114,32 +115,85 @@ private:
 };
 
 /// \brief Implementation of the \c ClangTidyOptionsProvider interface, which
-/// tries to find a .clang-tidy file in the closest parent directory of each
-/// file.
+/// tries to find a configuration file in the closest parent directory of each
+/// source file.
+///
+/// By default, files named ".clang-tidy" will be considered, and the
+/// \c clang::tidy::parseConfiguration function will be used for parsing, but a
+/// custom set of configuration file names and parsing functions can be
+/// specified using the appropriate constructor.
 class FileOptionsProvider : public DefaultOptionsProvider {
 public:
+  // \brief A pair of configuration file base name and a function parsing
+  // configuration from text in the corresponding format.
+  typedef std::pair<std::string, std::function<llvm::ErrorOr<ClangTidyOptions>(
+                                     llvm::StringRef)>> ConfigFileHandler;
+
+  /// \brief Configuration file handlers listed in the order of priority.
+  ///
+  /// Custom configuration file formats can be supported by constructing the
+  /// list of handlers and passing it to the appropriate \c FileOptionsProvider
+  /// constructor. E.g. initialization of a \c FileOptionsProvider with support
+  /// of a custom configuration file format for files named ".my-tidy-config"
+  /// could look similar to this:
+  /// \code
+  /// FileOptionsProvider::ConfigFileHandlers ConfigHandlers;
+  /// ConfigHandlers.emplace_back(".my-tidy-config", parseMyConfigFormat);
+  /// ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
+  /// return llvm::make_unique<FileOptionsProvider>(
+  ///     GlobalOptions, DefaultOptions, OverrideOptions, ConfigHandlers);
+  /// \endcode
+  ///
+  /// With the order of handlers shown above, the ".my-tidy-config" file would
+  /// take precedence over ".clang-tidy" if both reside in the same directory.
+  typedef std::vector<ConfigFileHandler> ConfigFileHandlers;
+
   /// \brief Initializes the \c FileOptionsProvider instance.
   ///
   /// \param GlobalOptions are just stored and returned to the caller of
   /// \c getGlobalOptions.
   ///
   /// \param DefaultOptions are used for all settings not specified in a
-  /// .clang-tidy file.
+  /// configuration file.
   ///
   /// If any of the \param OverrideOptions fields are set, they will override
   /// whatever options are read from the configuration file.
   FileOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions,
                       const ClangTidyOptions &DefaultOptions,
                       const ClangTidyOptions &OverrideOptions);
+
+  /// \brief Initializes the \c FileOptionsProvider instance with a custom set
+  /// of configuration file handlers.
+  ///
+  /// \param GlobalOptions are just stored and returned to the caller of
+  /// \c getGlobalOptions.
+  ///
+  /// \param DefaultOptions are used for all settings not specified in a
+  /// configuration file.
+  ///
+  /// If any of the \param OverrideOptions fields are set, they will override
+  /// whatever options are read from the configuration file.
+  ///
+  /// \param ConfigHandlers specifies a custom set of configuration file
+  /// handlers. Each handler is a pair of configuration file name and a function
+  /// that can parse configuration from this file type. The configuration files
+  /// in each directory are searched for in the order of appearance in
+  /// \p ConfigHandlers.
+  FileOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions,
+                      const ClangTidyOptions &DefaultOptions,
+                      const ClangTidyOptions &OverrideOptions,
+                      const ConfigFileHandlers &ConfigHandlers);
+
   const ClangTidyOptions &getOptions(llvm::StringRef FileName) override;
 
 private:
-  /// \brief Try to read configuration file from \p Directory. If \p Directory
-  /// is empty, use the default value.
-  llvm::ErrorOr<ClangTidyOptions> TryReadConfigFile(llvm::StringRef Directory);
+  /// \brief Try to read configuration files from \p Directory using registered
+  /// \c ConfigHandlers.
+  llvm::Optional<ClangTidyOptions> TryReadConfigFile(llvm::StringRef Directory);
 
   llvm::StringMap<ClangTidyOptions> CachedOptions;
   ClangTidyOptions OverrideOptions;
+  ConfigFileHandlers ConfigHandlers;
 };
 
 /// \brief Parses LineFilter from JSON and stores it to the \p Options.





More information about the cfe-commits mailing list