diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -883,13 +883,18 @@ 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_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 --- 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 #include #include +#include namespace llvm { class Triple; @@ -258,8 +260,8 @@ /// 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 ConfigFiles; /// Allocator for string saver. llvm::BumpPtrAllocator Alloc; @@ -353,7 +355,9 @@ /// Name to use when invoking gcc/g++. const std::string &getCCCGenericGCCName() const { return CCCGenericGCCName; } - const std::string &getConfigFile() const { return ConfigFile; } + llvm::ArrayRef getConfigFiles() const { + return ConfigFiles; + } const llvm::opt::OptTable &getOpts() const { return getDriverOptTable(); } @@ -664,10 +668,16 @@ 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 CfgFileSearchDirs); /// Read options from the specified file. /// diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -929,6 +929,22 @@ 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 NewCfgArgs; @@ -940,31 +956,38 @@ // 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( + std::unique_ptr NewOptions = std::make_unique( 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 @@ // 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 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 @@ 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 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 @@ } // 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 @@ // 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 @@ 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 @@ if (!ContainsError) for (auto *Opt : *CLModePassThroughOptions) { - appendOneArg(Opt, nullptr); + appendOneArg(Args, Opt, nullptr); } } } @@ -1880,8 +1883,8 @@ // 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 --- 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 --- 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 --- 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