[clang] 8a774c3 - [clang] [Driver] Support multiple configuration files

Michał Górny via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 21 04:14:50 PDT 2022


Author: Michał Górny
Date: 2022-09-21T13:14:36+02:00
New Revision: 8a774c35e9f8c6a200a8f4d00aae909840f5004a

URL: https://github.com/llvm/llvm-project/commit/8a774c35e9f8c6a200a8f4d00aae909840f5004a
DIFF: https://github.com/llvm/llvm-project/commit/8a774c35e9f8c6a200a8f4d00aae909840f5004a.diff

LOG: [clang] [Driver] Support multiple configuration files

Support specifying multiple configuration files via multiple `--config`
options.  When multiple files are specified, the options from subsequent
files are appended to the options from the initial file.

While at it, remove the incorrect assertion about CfgFileName being
non-empty.  It can be empty if `--config ""` is passed, and it makes
sense to report it as non-existing file rather than crash.

Differential Revision: https://reviews.llvm.org/D134270

Added: 
    

Modified: 
    clang/docs/UsersManual.rst
    clang/include/clang/Driver/Driver.h
    clang/lib/Driver/Driver.cpp
    clang/test/Driver/config-file-errs.c
    clang/test/Driver/config-file.c
    clang/test/Driver/config-file3.c

Removed: 
    


################################################################################
diff  --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst
index 0ddbb61f1d540..9c0a916f1a10d 100644
--- a/clang/docs/UsersManual.rst
+++ b/clang/docs/UsersManual.rst
@@ -883,13 +883,18 @@ specified just by referencing the configuration file. They may be used, for
 example, to collect options required to tune compilation for particular
 target, such as ``-L``, ``-I``, ``-l``, ``--sysroot``, codegen options, etc.
 
-The command line option ``--config`` can be used to specify configuration
-file in a Clang invocation. For example:
+Configuration files can be either specified on the command line or loaded
+from default locations. If both variants are present, the default configuration
+files are loaded first.
+
+The command line option ``--config`` can be used to specify explicit
+configuration files in a Clang invocation. If the option is used multiple times,
+all specified files are loaded, in order. For example:
 
 ::
 
     clang --config /home/user/cfgs/testing.txt
-    clang --config debug.cfg
+    clang --config debug.cfg --config runtimes.cfg
 
 If the provided argument contains a directory separator, it is considered as
 a file path, and options are read from that file. Otherwise the argument is
@@ -904,14 +909,15 @@ clang build using CMake parameters, ``CLANG_CONFIG_FILE_USER_DIR`` and
 ``CLANG_CONFIG_FILE_SYSTEM_DIR`` respectively. The first file found is used.
 It is an error if the required file cannot be found.
 
-If no explicit configuration file is specified, Clang searches for a default
-configuration file following the rules described in the next paragraphs.
-To disable this behavior, ``--no-default-config`` flag can be used.
+The default configuration files are searched for in the same directories
+following the rules described in the next paragraphs. Loading default
+configuration files can be disabled entirely via passing
+the ``--no-default-config`` flag.
 
-Another way to specify a configuration file is to encode it in executable name.
-For example, if the Clang executable is named ``armv7l-clang`` (it may be a
-symbolic link to ``clang``), then Clang will search for file ``armv7l.cfg``
-in the directory where Clang resides.
+The name of the default configuration file is deduced from the clang executable
+name.  For example, if the Clang executable is named ``armv7l-clang`` (it may
+be a symbolic link to ``clang``), then Clang will search for file
+``armv7l.cfg`` in the directory where Clang resides.
 
 If a driver mode is specified in invocation, Clang tries to find a file specific
 for the specified mode. For example, if the executable file is named

diff  --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h
index 59c2f1f52cecf..203557a5cfc66 100644
--- a/clang/include/clang/Driver/Driver.h
+++ b/clang/include/clang/Driver/Driver.h
@@ -19,6 +19,7 @@
 #include "clang/Driver/ToolChain.h"
 #include "clang/Driver/Types.h"
 #include "clang/Driver/Util.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Option/Arg.h"
@@ -28,6 +29,7 @@
 #include <list>
 #include <map>
 #include <string>
+#include <vector>
 
 namespace llvm {
 class Triple;
@@ -258,8 +260,8 @@ class Driver {
   /// Name to use when invoking gcc/g++.
   std::string CCCGenericGCCName;
 
-  /// Name of configuration file if used.
-  std::string ConfigFile;
+  /// Paths to configuration files used.
+  std::vector<std::string> ConfigFiles;
 
   /// Allocator for string saver.
   llvm::BumpPtrAllocator Alloc;
@@ -353,7 +355,9 @@ class Driver {
   /// Name to use when invoking gcc/g++.
   const std::string &getCCCGenericGCCName() const { return CCCGenericGCCName; }
 
-  const std::string &getConfigFile() const { return ConfigFile; }
+  llvm::ArrayRef<std::string> getConfigFiles() const {
+    return ConfigFiles;
+  }
 
   const llvm::opt::OptTable &getOpts() const { return getDriverOptTable(); }
 
@@ -664,10 +668,16 @@ class Driver {
 
 private:
 
-  /// Tries to load options from configuration file.
+  /// Tries to load options from configuration files.
+  ///
+  /// \returns true if error occurred.
+  bool loadConfigFiles();
+
+  /// Tries to load options from default configuration files (deduced from
+  /// executable filename).
   ///
   /// \returns true if error occurred.
-  bool loadConfigFile();
+  bool loadDefaultConfigFiles(ArrayRef<StringRef> CfgFileSearchDirs);
 
   /// Read options from the specified file.
   ///

diff  --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 52990f14e7eac..4d3ab9b7096e2 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -929,6 +929,22 @@ static bool searchForFile(SmallVectorImpl<char> &FilePath,
   return false;
 }
 
+static void appendOneArg(InputArgList &Args, const Arg *Opt,
+                         const Arg *BaseArg) {
+  // The args for config files or /clang: flags belong to 
diff erent InputArgList
+  // objects than Args. This copies an Arg from one of those other InputArgLists
+  // to the ownership of Args.
+  unsigned Index = Args.MakeIndex(Opt->getSpelling());
+  Arg *Copy = new llvm::opt::Arg(Opt->getOption(), Args.getArgString(Index),
+                                 Index, BaseArg);
+  Copy->getValues() = Opt->getValues();
+  if (Opt->isClaimed())
+    Copy->claim();
+  Copy->setOwnsValues(Opt->getOwnsValues());
+  Opt->setOwnsValues(false);
+  Args.append(Copy);
+}
+
 bool Driver::readConfigFile(StringRef FileName) {
   // Try reading the given file.
   SmallVector<const char *, 32> NewCfgArgs;
@@ -940,31 +956,38 @@ bool Driver::readConfigFile(StringRef FileName) {
   // Read options from config file.
   llvm::SmallString<128> CfgFileName(FileName);
   llvm::sys::path::native(CfgFileName);
-  ConfigFile = std::string(CfgFileName);
   bool ContainErrors;
-  CfgOptions = std::make_unique<InputArgList>(
+  std::unique_ptr<InputArgList> NewOptions = std::make_unique<InputArgList>(
       ParseArgStrings(NewCfgArgs, IsCLMode(), ContainErrors));
-  if (ContainErrors) {
-    CfgOptions.reset();
+  if (ContainErrors)
     return true;
-  }
 
-  if (CfgOptions->hasArg(options::OPT_config)) {
-    CfgOptions.reset();
+  if (NewOptions->hasArg(options::OPT_config)) {
     Diag(diag::err_drv_nested_config_file);
     return true;
   }
 
   // Claim all arguments that come from a configuration file so that the driver
   // does not warn on any that is unused.
-  for (Arg *A : *CfgOptions)
+  for (Arg *A : *NewOptions)
     A->claim();
+
+  if (!CfgOptions)
+    CfgOptions = std::move(NewOptions);
+  else {
+    // If this is a subsequent config file, append options to the previous one.
+    for (auto *Opt : *NewOptions) {
+      const Arg *BaseArg = &Opt->getBaseArg();
+      if (BaseArg == Opt)
+        BaseArg = nullptr;
+      appendOneArg(*CfgOptions, Opt, BaseArg);
+    }
+  }
+  ConfigFiles.push_back(std::string(CfgFileName));
   return false;
 }
 
-bool Driver::loadConfigFile() {
-  std::string CfgFileName;
-
+bool Driver::loadConfigFiles() {
   // Process options that change search path for config files.
   if (CLOptions) {
     if (CLOptions->hasArg(options::OPT_config_system_dir_EQ)) {
@@ -990,26 +1013,18 @@ bool Driver::loadConfigFile() {
   // Prepare list of directories where config file is searched for.
   StringRef CfgFileSearchDirs[] = {UserConfigDir, SystemConfigDir, Dir};
 
-  // First try to find config file specified in command line.
+  // First try to load configuration from the default files, return on error.
+  if (loadDefaultConfigFiles(CfgFileSearchDirs))
+    return true;
+
+  // Then load configuration files specified explicitly.
   llvm::SmallString<128> CfgFilePath;
   if (CLOptions) {
-    std::vector<std::string> ConfigFiles =
-        CLOptions->getAllArgValues(options::OPT_config);
-    if (ConfigFiles.size() > 1) {
-      if (!llvm::all_equal(ConfigFiles)) {
-        Diag(diag::err_drv_duplicate_config);
-        return true;
-      }
-    }
-
-    if (!ConfigFiles.empty()) {
-      CfgFileName = ConfigFiles.front();
-      assert(!CfgFileName.empty());
-
+    for (auto CfgFileName : CLOptions->getAllArgValues(options::OPT_config)) {
       // If argument contains directory separator, treat it as a path to
       // configuration file.
       if (llvm::sys::path::has_parent_path(CfgFileName)) {
-        SmallString<128> CfgFilePath(CfgFileName);
+        CfgFilePath = CfgFileName;
         if (llvm::sys::path::is_relative(CfgFilePath)) {
           if (getVFS().makeAbsolute(CfgFilePath))
             return true;
@@ -1020,35 +1035,37 @@ bool Driver::loadConfigFile() {
             return true;
           }
         }
-        return readConfigFile(CfgFilePath);
+      } else if (!searchForFile(CfgFilePath, CfgFileSearchDirs, CfgFileName,
+                                getVFS())) {
+        // Report an error that the config file could not be found.
+        Diag(diag::err_drv_config_file_not_found) << CfgFileName;
+        for (const StringRef &SearchDir : CfgFileSearchDirs)
+          if (!SearchDir.empty())
+            Diag(diag::note_drv_config_file_searched_in) << SearchDir;
+        return true;
       }
 
-      // Look for the configuration file in the usual locations.
-      if (searchForFile(CfgFilePath, CfgFileSearchDirs, CfgFileName, getVFS()))
-        return readConfigFile(CfgFilePath);
-
-      // Report error but only if config file was specified explicitly, by
-      // option --config. If it was deduced from executable name, it is not an
-      // error.
-      Diag(diag::err_drv_config_file_not_found) << CfgFileName;
-      for (const StringRef &SearchDir : CfgFileSearchDirs)
-        if (!SearchDir.empty())
-          Diag(diag::note_drv_config_file_searched_in) << SearchDir;
-      return true;
+      // Try to read the config file, return on error.
+      if (readConfigFile(CfgFilePath))
+        return true;
     }
   }
 
-  if (!(CLOptions && CLOptions->hasArg(options::OPT_no_default_config))) {
-    // If config file is not specified explicitly, try to deduce configuration
-    // from executable name. For instance, an executable 'armv7l-clang' will
-    // search for config file 'armv7l-clang.cfg'.
-    if (CfgFileName.empty() && !ClangNameParts.TargetPrefix.empty())
-      CfgFileName =
-          ClangNameParts.TargetPrefix + '-' + ClangNameParts.ModeSuffix;
-  }
+  // No error occurred.
+  return false;
+}
 
-  if (CfgFileName.empty())
+bool Driver::loadDefaultConfigFiles(ArrayRef<StringRef> CfgFileSearchDirs) {
+  if (CLOptions && CLOptions->hasArg(options::OPT_no_default_config))
     return false;
+  if (ClangNameParts.TargetPrefix.empty())
+    return false;
+
+  // If config file is not specified explicitly, try to deduce configuration
+  // from executable name. For instance, an executable 'armv7l-clang' will
+  // search for config file 'armv7l-clang.cfg'.
+  std::string CfgFileName =
+        ClangNameParts.TargetPrefix + '-' + ClangNameParts.ModeSuffix;
 
   // Determine architecture part of the file name, if it is present.
   StringRef CfgFileArch = CfgFileName;
@@ -1086,6 +1103,7 @@ bool Driver::loadConfigFile() {
   }
 
   // Try to find config file. First try file with corrected architecture.
+  llvm::SmallString<128> CfgFilePath;
   if (!FixedConfigFile.empty()) {
     if (searchForFile(CfgFilePath, CfgFileSearchDirs, FixedConfigFile,
                       getVFS()))
@@ -1138,28 +1156,13 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
 
   // Try parsing configuration file.
   if (!ContainsError)
-    ContainsError = loadConfigFile();
+    ContainsError = loadConfigFiles();
   bool HasConfigFile = !ContainsError && (CfgOptions.get() != nullptr);
 
   // All arguments, from both config file and command line.
   InputArgList Args = std::move(HasConfigFile ? std::move(*CfgOptions)
                                               : std::move(*CLOptions));
 
-  // The args for config files or /clang: flags belong to 
diff erent InputArgList
-  // objects than Args. This copies an Arg from one of those other InputArgLists
-  // to the ownership of Args.
-  auto appendOneArg = [&Args](const Arg *Opt, const Arg *BaseArg) {
-    unsigned Index = Args.MakeIndex(Opt->getSpelling());
-    Arg *Copy = new llvm::opt::Arg(Opt->getOption(), Args.getArgString(Index),
-                                   Index, BaseArg);
-    Copy->getValues() = Opt->getValues();
-    if (Opt->isClaimed())
-      Copy->claim();
-    Copy->setOwnsValues(Opt->getOwnsValues());
-    Opt->setOwnsValues(false);
-    Args.append(Copy);
-  };
-
   if (HasConfigFile)
     for (auto *Opt : *CLOptions) {
       if (Opt->getOption().matches(options::OPT_config))
@@ -1167,7 +1170,7 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
       const Arg *BaseArg = &Opt->getBaseArg();
       if (BaseArg == Opt)
         BaseArg = nullptr;
-      appendOneArg(Opt, BaseArg);
+      appendOneArg(Args, Opt, BaseArg);
     }
 
   // In CL mode, look for any pass-through arguments
@@ -1186,7 +1189,7 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
 
       if (!ContainsError)
         for (auto *Opt : *CLModePassThroughOptions) {
-          appendOneArg(Opt, nullptr);
+          appendOneArg(Args, Opt, nullptr);
         }
     }
   }
@@ -1880,8 +1883,8 @@ void Driver::PrintVersion(const Compilation &C, raw_ostream &OS) const {
   // Print out the install directory.
   OS << "InstalledDir: " << InstalledDir << '\n';
 
-  // If configuration file was used, print its path.
-  if (!ConfigFile.empty())
+  // If configuration files were used, print their paths.
+  for (auto ConfigFile : ConfigFiles)
     OS << "Configuration file: " << ConfigFile << '\n';
 }
 

diff  --git a/clang/test/Driver/config-file-errs.c b/clang/test/Driver/config-file-errs.c
index c4ce9df915bf3..497e6cb4a5c2b 100644
--- a/clang/test/Driver/config-file-errs.c
+++ b/clang/test/Driver/config-file-errs.c
@@ -1,9 +1,3 @@
-//--- No more than one '--config' may be specified.
-//
-// RUN: not %clang --config 1.cfg --config 2.cfg 2>&1 | FileCheck %s -check-prefix CHECK-DUPLICATE
-// CHECK-DUPLICATE: no more than one option '--config' is allowed
-
-
 //--- '--config' must be followed by config file name.
 //
 // RUN: not %clang --config 2>&1 | FileCheck %s -check-prefix CHECK-MISSING-FILE
@@ -22,6 +16,11 @@
 // CHECK-NONEXISTENT: configuration file '{{.*}}somewhere/nonexistent-config-file' does not exist
 
 
+//--- All '--config' arguments must be existing files.
+//
+// RUN: not %clang --config %S/Inputs/config-4.cfg --config somewhere/nonexistent-config-file 2>&1 | FileCheck %s -check-prefix CHECK-NONEXISTENT
+
+
 //--- Argument of '--config' must exist somewhere in well-known directories, if it is specified by bare name.
 //
 // RUN: not %clang --config-system-dir= --config-user-dir= --config nonexistent-config-file.cfg 2>&1 | FileCheck %s -check-prefix CHECK-NOTFOUND0

diff  --git a/clang/test/Driver/config-file.c b/clang/test/Driver/config-file.c
index 265fd49f2e664..5cb2e485e1787 100644
--- a/clang/test/Driver/config-file.c
+++ b/clang/test/Driver/config-file.c
@@ -73,6 +73,10 @@
 // CHECK-PRECEDENCE: -Wall
 
 
-//--- Duplicate --config options are allowed if the value is the same
-// RUN: %clang --config-system-dir=%S/Inputs/config --config-user-dir=%S/Inputs/config2 --config config-4.cfg --config config-4.cfg -S %s -o /dev/null -v 2>&1 | FileCheck %s -check-prefix CHECK-SAME-CONFIG
-// CHECK-SAME-CONFIG: Configuration file: {{.*}}Inputs{{.}}config2{{.}}config-4.cfg
+//--- Multiple configuration files can be specified.
+// RUN: %clang --config-system-dir=%S/Inputs/config --config-user-dir= --config config-4.cfg --config %S/Inputs/config2/config-4.cfg -S %s -o /dev/null -v 2>&1 | FileCheck %s -check-prefix CHECK-TWO-CONFIGS
+// CHECK-TWO-CONFIGS: Configuration file: {{.*}}Inputs{{.}}config{{.}}config-4.cfg
+// CHECK-TWO-CONFIGS-NEXT: Configuration file: {{.*}}Inputs{{.}}config2{{.}}config-4.cfg
+// CHECK-TWO-CONFIGS: -isysroot
+// CHECK-TWO-CONFIGS-SAME: /opt/data
+// CHECK-TWO-CONFIGS-SAME: -Wall

diff  --git a/clang/test/Driver/config-file3.c b/clang/test/Driver/config-file3.c
index 086355b382a1d..3158ac0d7f3b7 100644
--- a/clang/test/Driver/config-file3.c
+++ b/clang/test/Driver/config-file3.c
@@ -34,12 +34,13 @@
 // RUN: %t/testdmode/qqq-clang-g++ --config-system-dir= --config-user-dir=%t/testdmode -c -### %s 2>&1 | FileCheck %s -check-prefix SYMLINK
 //
 // SYMLINK: Configuration file: {{.*}}/testdmode/qqq-clang-g++.cfg
-//
-//--- File specified by --config overrides config inferred from clang executable.
+
+//--- File specified by --config is loaded after the one inferred from the executable.
 //
 // RUN: %t/testdmode/qqq-clang-g++ --config-system-dir=%S/Inputs/config --config-user-dir= --config i386-qqq.cfg -c -no-canonical-prefixes -### %s 2>&1 | FileCheck %s -check-prefix CHECK-EXPLICIT
 //
-// CHECK-EXPLICIT: Configuration file: {{.*}}/Inputs/config/i386-qqq.cfg
+// CHECK-EXPLICIT: Configuration file: {{.*}}/testdmode/qqq-clang-g++.cfg
+// CHECK-EXPLICIT-NEXT: Configuration file: {{.*}}/Inputs/config/i386-qqq.cfg
 
 //--- --no-default-config disables config search.
 //
@@ -47,6 +48,13 @@
 //
 // NO-DEFAULT-CONFIG-NOT: Configuration file:
 
+//--- Explicit --config works with --no-default-config.
+//
+// RUN: %t/testdmode/qqq-clang-g++ --config-system-dir=%S/Inputs/config --config-user-dir= --no-default-config --config i386-qqq.cfg -c -no-canonical-prefixes -### %s 2>&1 | FileCheck %s -check-prefix CHECK-EXPLICIT-NO-DEFAULT
+//
+// CHECK-EXPLICIT-NO-DEFAULT-NOT: Configuration file: {{.*}}/testdmode/qqq-clang-g++.cfg
+// CHECK-EXPLICIT-NO-DEFAULT: Configuration file: {{.*}}/Inputs/config/i386-qqq.cfg
+
 //--- Invocation qqq-clang-g++ tries to find config file qqq.cfg if qqq-clang-g++.cfg is not found.
 //
 // RUN: rm %t/testdmode/qqq-clang-g++.cfg


        


More information about the cfe-commits mailing list