[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
This commit is contained in:
Michał Górny 2022-09-20 11:04:06 +02:00
parent 94747094c7
commit 8a774c35e9
6 changed files with 125 additions and 95 deletions

View file

@ -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

View file

@ -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 @@ private:
/// 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 @@ public:
/// 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 @@ public:
private:
/// Tries to load options from configuration file.
/// Tries to load options from configuration files.
///
/// \returns true if error occurred.
bool loadConfigFile();
bool loadConfigFiles();
/// Tries to load options from default configuration files (deduced from
/// executable filename).
///
/// \returns true if error occurred.
bool loadDefaultConfigFiles(ArrayRef<StringRef> CfgFileSearchDirs);
/// Read options from the specified file.
///

View file

@ -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 different 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 different 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';
}

View file

@ -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

View file

@ -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

View file

@ -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