Index: docs/UsersManual.rst =================================================================== --- docs/UsersManual.rst +++ docs/UsersManual.rst @@ -645,6 +645,59 @@ option tells Clang to put double-quotes around the entire filename, which is the convention used by NMake and Jom. +Configuration files +------------------- + +Configuration files group command line options and allow to specify all of +them just by referencing the configuration file. They may be used, for +instance, 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 instance: + +:: + + clang --config /home/user/cfgs/testing.txt + clang --config debug.cfg + +If the provided argument contains a directory separator, it is considered as +a file path, options are read from that file. Otherwise the argument is treated +as a file name and is searched for sequentially in the directories: + - user directory, + - system directory, + - the directory where clang executable resides. +Both user and system directory for configuration files are specified during +clang build using cmake parameters, CLANG_CONFIG_FILE_USER_DIR and +CLANG_CONFIG_FILE_SYSTEM_DIR respectively. The first found file is used. It is +an error if the required file cannot be found. + +Another way to specify configuration file is to encode it in executable name. For +instance, if clang executable is named `armv7l-clang` (it may be a symbolic link +to `clang`), then clang will search file `armv7l.cfg` in the directory where clang +resides. + +The configuration file consists of command line options specified on one or several +lines. Lines composed of whitespace characters only are ignored as well as lines in +which the first non-blank character is `#`. Long options may be split between several +lines by trailing backslash. Here is an example of config file: + +:: + + # Several options on line + -c --target=x86_64-unknown-linux-gnu + + # Long option split between lines + -I/usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../\ + include/c++/5.4.0 + + # other config files may be included + @linux.options + +Files included by directives `@file` in configuration files are resolved relative to +the including file. For instance if a config file `~/.llvm/target.cfg` contains +directive `@os/linux.opts`, the file `linux.opts` is searched for in the directory +`~/.llvm/os`. Language and Target-Independent Features ======================================== Index: include/clang/Basic/DiagnosticDriverKinds.td =================================================================== --- include/clang/Basic/DiagnosticDriverKinds.td +++ include/clang/Basic/DiagnosticDriverKinds.td @@ -101,6 +101,8 @@ "invalid argument '%0' to -fdebug-prefix-map">; def err_drv_malformed_sanitizer_blacklist : Error< "malformed sanitizer blacklist: '%0'">; +def err_drv_incomplete_config : Error< + "config file does not contain argument for the option '%0'">; def err_target_unsupported_arch : Error<"the target architecture '%0' is not supported by the target '%1'">; Index: include/clang/Config/config.h.cmake =================================================================== --- include/clang/Config/config.h.cmake +++ include/clang/Config/config.h.cmake @@ -29,6 +29,10 @@ /* Directories clang will search for headers */ #define C_INCLUDE_DIRS "${C_INCLUDE_DIRS}" +/* Directories clang will search for configuration files */ +#cmakedefine CLANG_CONFIG_FILE_SYSTEM_DIR ${CLANG_CONFIG_FILE_SYSTEM_DIR} +#cmakedefine CLANG_CONFIG_FILE_USER_DIR ${CLANG_CONFIG_FILE_USER_DIR} + /* Default to all compiler invocations for --sysroot=. */ #define DEFAULT_SYSROOT "${DEFAULT_SYSROOT}" Index: include/clang/Driver/Driver.h =================================================================== --- include/clang/Driver/Driver.h +++ include/clang/Driver/Driver.h @@ -208,6 +208,17 @@ /// Name to use when invoking gcc/g++. std::string CCCGenericGCCName; + /// Name of configuration file if used. + std::string ConfigFile; + + /// Number of items in command line that came from configuration file. + unsigned NumConfigOptions; + + /// Number of arguments originated from configuration file. + /// This number may be smaller that \c NumConfigOptions if some options + /// require separate arguments. + unsigned NumConfigArgs; + /// Whether to check that input files exist when constructing compilation /// jobs. unsigned CheckInputsExist : 1; @@ -272,6 +283,13 @@ /// Name to use when invoking gcc/g++. const std::string &getCCCGenericGCCName() const { return CCCGenericGCCName; } + const std::string &getConfigFile() const { return ConfigFile; } + void setConfigFile(StringRef FileName, unsigned N) { + ConfigFile = FileName; + NumConfigOptions = N; + NumConfigArgs = 0; + } + const llvm::opt::OptTable &getOpts() const { return *Opts; } const DiagnosticsEngine &getDiags() const { return Diags; } Index: include/clang/Driver/ToolChain.h =================================================================== --- include/clang/Driver/ToolChain.h +++ include/clang/Driver/ToolChain.h @@ -181,19 +181,40 @@ // Returns the RTTIMode for the toolchain with the current arguments. RTTIMode getRTTIMode() const { return CachedRTTIMode; } + /// Helper structure used to pass information extracted from clang executable + /// name such as `i686-linux-android-g++`. + /// + struct DriverNameParts { + /// Target part of the executable name, as `i686-linux-android`. + std::string TargetPrefix; + /// Driver mode part of the executable name, as `g++`. + std::string ModeSuffix; + /// True if TargetPrefix is recognized as a registered target name. + bool TargetIsValid; + + DriverNameParts() : TargetIsValid(false) {} + DriverNameParts(std::string Mode) + : ModeSuffix(Mode), TargetIsValid(false) {} + DriverNameParts(std::string Target, std::string Mode, bool IsRegistered) + : TargetPrefix(Target), ModeSuffix(Mode), TargetIsValid(IsRegistered) {} + }; + /// \brief Return any implicit target and/or mode flag for an invocation of /// the compiler driver as `ProgName`. /// /// For example, when called with i686-linux-android-g++, the first element /// of the return value will be set to `"i686-linux-android"` and the second /// will be set to "--driver-mode=g++"`. + /// It is OK if the target name is not registered. In this case the return + /// value contains false in the field TargetIsValid. /// /// \pre `llvm::InitializeAllTargets()` has been called. /// \param ProgName The name the Clang driver was invoked with (from, - /// e.g., argv[0]) - /// \return A pair of (`target`, `mode-flag`), where one or both may be empty. - static std::pair - getTargetAndModeFromProgramName(StringRef ProgName); + /// e.g., argv[0]). + /// \return A structure of type DriverNameParts that contatins the executable + /// name parts. + /// + static DriverNameParts getTargetAndModeFromProgramName(StringRef ProgName); // Tool access. Index: lib/Driver/Driver.cpp =================================================================== --- lib/Driver/Driver.cpp +++ lib/Driver/Driver.cpp @@ -61,7 +61,8 @@ CCPrintHeadersFilename(nullptr), CCLogDiagnosticsFilename(nullptr), CCCPrintBindings(false), CCPrintHeaders(false), CCLogDiagnostics(false), CCGenDiagnostics(false), DefaultTargetTriple(DefaultTargetTriple), - CCCGenericGCCName(""), CheckInputsExist(true), CCCUsePCH(true), + CCCGenericGCCName(""), NumConfigOptions(0), NumConfigArgs(0), + CheckInputsExist(true), CCCUsePCH(true), SuppressMissingInputWarning(false) { // Provide a sane fallback if no VFS is specified. @@ -88,7 +89,7 @@ void Driver::ParseDriverMode(StringRef ProgramName, ArrayRef Args) { auto Default = ToolChain::getTargetAndModeFromProgramName(ProgramName); - StringRef DefaultMode(Default.second); + StringRef DefaultMode(Default.ModeSuffix); setDriverModeFromOption(DefaultMode); for (const char *ArgPtr : Args) { @@ -129,9 +130,11 @@ getIncludeExcludeOptionFlagMasks(); unsigned MissingArgIndex, MissingArgCount; + int NumCfgArgs = static_cast(NumConfigOptions); InputArgList Args = getOpts().ParseArgs(ArgStrings, MissingArgIndex, MissingArgCount, - IncludedFlagsBitmask, ExcludedFlagsBitmask); + IncludedFlagsBitmask, ExcludedFlagsBitmask, + &NumCfgArgs); // Check for missing argument error. if (MissingArgCount) @@ -151,6 +154,17 @@ } } + + // Check if the last option specified in config file takes a separate + // argument, the argument comes from the config file also. + // from outside of the file. + if (NumCfgArgs < 0) { + NumConfigArgs = static_cast(-NumCfgArgs); + Diag(diag::err_drv_incomplete_config) << ArgStrings[NumConfigOptions - 1]; + } else { + NumConfigArgs = static_cast(NumCfgArgs); + } + for (const Arg *A : Args.filtered(options::OPT_UNKNOWN)) Diags.Report(IsCLMode() ? diag::warn_drv_unknown_argument_clang_cl : diag::err_drv_unknown_argument) @@ -1080,6 +1094,10 @@ // Print out the install directory. OS << "InstalledDir: " << InstalledDir << '\n'; + + // If configuration file was used, print its path. + if (!ConfigFile.empty()) + OS << "Configuration file: " << ConfigFile << '\n'; } /// PrintDiagnosticCategories - Implement the --print-diagnostic-categories @@ -2676,6 +2694,11 @@ void Driver::BuildJobs(Compilation &C) const { llvm::PrettyStackTraceString CrashInfo("Building compilation jobs"); + // Claim all arguments that come from a configuration file so that the driver + // does not warn on any that is unused. + for (unsigned I = 0; I < NumConfigArgs; ++I) + C.getArgs().getArgs()[I]->claim(); + Arg *FinalOutput = C.getArgs().getLastArg(options::OPT_o); // It is an error to provide a -o option if we are making multiple output Index: lib/Driver/ToolChain.cpp =================================================================== --- lib/Driver/ToolChain.cpp +++ lib/Driver/ToolChain.cpp @@ -162,28 +162,25 @@ } } // anonymous namespace -std::pair +ToolChain::DriverNameParts ToolChain::getTargetAndModeFromProgramName(StringRef PN) { std::string ProgName = normalizeProgramName(PN); const DriverSuffix *DS = parseDriverSuffix(ProgName); if (!DS) - return std::make_pair("", ""); + return DriverNameParts(); std::string ModeFlag = DS->ModeFlag == nullptr ? "" : DS->ModeFlag; std::string::size_type LastComponent = ProgName.rfind('-', ProgName.size() - strlen(DS->Suffix)); if (LastComponent == std::string::npos) - return std::make_pair("", ModeFlag); + return DriverNameParts(ModeFlag); // Infer target from the prefix. StringRef Prefix(ProgName); Prefix = Prefix.slice(0, LastComponent); std::string IgnoredError; - std::string Target; - if (llvm::TargetRegistry::lookupTarget(Prefix, IgnoredError)) { - Target = Prefix; - } - return std::make_pair(Target, ModeFlag); + bool IsRegistered = llvm::TargetRegistry::lookupTarget(Prefix, IgnoredError); + return DriverNameParts{Prefix, ModeFlag, IsRegistered}; } StringRef ToolChain::getDefaultUniversalArchName() const { Index: lib/Tooling/Tooling.cpp =================================================================== --- lib/Tooling/Tooling.cpp +++ lib/Tooling/Tooling.cpp @@ -189,11 +189,12 @@ } auto TargetMode = clang::driver::ToolChain::getTargetAndModeFromProgramName(InvokedAs); - if (!AlreadyHasMode && !TargetMode.second.empty()) { - CommandLine.insert(++CommandLine.begin(), TargetMode.second); + if (!AlreadyHasMode && !TargetMode.ModeSuffix.empty()) { + CommandLine.insert(++CommandLine.begin(), TargetMode.ModeSuffix); } - if (!AlreadyHasTarget && !TargetMode.first.empty()) { - CommandLine.insert(++CommandLine.begin(), {"-target", TargetMode.first}); + if (!AlreadyHasTarget && TargetMode.TargetIsValid) { + CommandLine.insert(++CommandLine.begin(), {"-target", + TargetMode.TargetPrefix}); } } } Index: test/Driver/Inputs/config-1.cfg =================================================================== --- /dev/null +++ test/Driver/Inputs/config-1.cfg @@ -0,0 +1,7 @@ + + +# Empty lines and line started with # are ignored +-Werror -std=\ +c99 + # Target + -target x86_64-apple-darwin \ No newline at end of file Index: test/Driver/Inputs/config-2.cfg =================================================================== --- /dev/null +++ test/Driver/Inputs/config-2.cfg @@ -0,0 +1,5 @@ +# target +-target x86_64-unknown-linux-gnu + +# nested inclusion +@config-3.cfg Index: test/Driver/Inputs/config-2a.cfg =================================================================== --- /dev/null +++ test/Driver/Inputs/config-2a.cfg @@ -0,0 +1,5 @@ +# target +-target x86_64-unknown-linux-gnu + +# nested inclusion +@config/config-4.cfg Index: test/Driver/Inputs/config-3.cfg =================================================================== --- /dev/null +++ test/Driver/Inputs/config-3.cfg @@ -0,0 +1 @@ +-Wundefined-func-template Index: test/Driver/Inputs/config-4.cfg =================================================================== --- /dev/null +++ test/Driver/Inputs/config-4.cfg @@ -0,0 +1,2 @@ +-L/usr/local/lib +-stdlib=libc++ \ No newline at end of file Index: test/Driver/Inputs/config-5.cfg =================================================================== --- /dev/null +++ test/Driver/Inputs/config-5.cfg @@ -0,0 +1,2 @@ +--serialize-diagnostics diag.ser +-target Index: test/Driver/Inputs/config/config-4.cfg =================================================================== --- /dev/null +++ test/Driver/Inputs/config/config-4.cfg @@ -0,0 +1 @@ +-isysroot /opt/data Index: test/Driver/config-file.c =================================================================== --- /dev/null +++ test/Driver/config-file.c @@ -0,0 +1,50 @@ +//--- Config file (full path) in output of -### +// +// RUN: %clang --config %S/Inputs/config-1.cfg -c %s -### 2>&1 | FileCheck %s -check-prefix CHECK-HHH +// CHECK-HHH: Target: x86_64-apple-darwin +// CHECK-HHH: Configuration file: {{.*}}/Inputs/config-1.cfg +// CHECK-HHH: -Werror +// CHECK-HHH: -std=c99 + +//--- Nested config files +// +// RUN: %clang --config %S/Inputs/config-2.cfg -c %s -### 2>&1 | FileCheck %s -check-prefix CHECK-HHH2 +// CHECK-HHH2: Target: x86_64-unknown-linux +// CHECK-HHH2: Configuration file: {{.*}}/Inputs/config-2.cfg +// CHECK-HHH2: -Wundefined-func-template +// +// RUN: %clang --config %S/Inputs/config-2a.cfg -c %s -### 2>&1 | FileCheck %s -check-prefix CHECK-HHH2a +// CHECK-HHH2a: Target: x86_64-unknown-linux +// CHECK-HHH2a: Configuration file: {{.*}}/Inputs/config-2a.cfg +// CHECK-HHH2a: -isysroot +// CHECK-HHH2a-SAME: /opt/data + +//--- Unexistent config file (full path) in output of -### +// +// RUN: not %clang --config %S/Inputs/inexistent.cfg -c %s -### 2>&1 | FileCheck %s -check-prefix CHECK-INEX +// CHECK-INEX: Configuration file {{.*}}/Inputs/inexistent.cfg' specified by option '--config' cannot be found + +//--- Unexistent config file (file name) in output of -### +// +// RUN: not %clang --config inexistent-config-file-for-tests.cfg -c %s -### 2>&1 | FileCheck %s -check-prefix CHECK-INEX-NOSEARCH +// CHECK-INEX-NOSEARCH: Configuration {{.*}}inexistent-config-file-for-tests.cfg' specified by option '--config' cannot be found in directories: +// +// RUN: not %clang --config inexistent-config-file-for-tests.cfg -c %s -### 2>&1 | grep '%bindir' + +//--- Config file (full path) in output of -v +// +// RUN: %clang --config %S/Inputs/config-1.cfg -c %s -v 2>&1 | FileCheck %s -check-prefix CHECK-V +// CHECK-V: Target: x86_64-apple-darwin +// CHECK-V: Configuration file: {{.*}}/Inputs/config-1.cfg +// CHECK-V: -triple{{.*}}x86_64-apple- + +//--- Unused options in config file do not produce warnings +// +// RUN: %clang --config %S/Inputs/config-4.cfg -c %s -v 2>&1 | FileCheck %s -check-prefix CHECK-UNUSED +// CHECK-UNUSED-NOT: argument unused during compilation: + +//--- Argument in config file cannot cross the file boundary +// +// RUN: not %clang --config %S/Inputs/config-5.cfg x86_64-unknown-linux-gnu -c %s 2>&1 | FileCheck %s -check-prefix CHECK-CROSS +// CHECK-CROSS: config file does not contain argument for the option '-target' + Index: test/Driver/config-file2.c =================================================================== --- /dev/null +++ test/Driver/config-file2.c @@ -0,0 +1,29 @@ +// REQUIRES: shell + +//--- Invocation qqq-clang tries to find config file qqq.cfg +// +// RUN: mkdir -p %T/testbin +// RUN: [ ! -s %T/testbin/qqq-clang ] || rm %T/testbin/qqq-clang +// RUN: ln -s %clang %T/testbin/qqq-clang +// RUN: echo "-Wundefined-func-template" > %T/testbin/qqq.cfg +// RUN: %T/testbin/qqq-clang -c %s -### 2>&1 | FileCheck %s +// CHECK: Configuration file: {{.*}}/testbin/qqq.cfg +// CHECK: -Wundefined-func-template + +//--- Config files are searched for in binary directory as well. +// +// RUN: [ ! -s %T/testbin/clang ] || rm %T/testbin/clang +// RUN: ln -s %clang %T/testbin/clang +// RUN: echo "-Werror" > %T/testbin/aaa.cfg +// RUN: %T/testbin/clang --config aaa.cfg -c %s -### 2>&1 | FileCheck %s -check-prefix CHECK-BIN +// CHECK-BIN: Configuration file: {{.*}}/testbin/aaa.cfg +// CHECK-BIN: -Werror + +// RUN: mkdir -p %T/workdir +// RUN: echo "@subdir/cfg-s2" > %T/workdir/cfg-1 +// RUN: mkdir -p %T/workdir/subdir +// RUN: echo "-Wundefined-var-template" > %T/workdir/subdir/cfg-s2 +// RUN: ( cd %T && %clang --config workdir/cfg-1 -c %s -### 2>&1 | FileCheck %s -check-prefix CHECK-REL ) +// CHECK-REL: Configuration file: {{.*}}/workdir/cfg-1 +// CHECK-REL: -Wundefined-var-template + Index: test/Driver/lit.local.cfg =================================================================== --- test/Driver/lit.local.cfg +++ test/Driver/lit.local.cfg @@ -4,6 +4,8 @@ config.substitutions.insert(0, ('%clang_cc1', """*** Do not use 'clang -cc1' in Driver tests. ***""") ) +config.substitutions.append( ('%bindir', + os.path.dirname(config.clang)) ) # Remove harmful environmental variables for clang Driver tests. # Some might be useful for other tests so they are only removed here. Index: tools/driver/driver.cpp =================================================================== --- tools/driver/driver.cpp +++ tools/driver/driver.cpp @@ -199,22 +199,23 @@ extern int cc1as_main(ArrayRef Argv, const char *Argv0, void *MainAddr); -static void insertTargetAndModeArgs(StringRef Target, StringRef Mode, +static void insertTargetAndModeArgs(const ToolChain::DriverNameParts &NameParts, SmallVectorImpl &ArgVector, std::set &SavedStrings) { - if (!Mode.empty()) { + if (!NameParts.ModeSuffix.empty()) { // Add the mode flag to the arguments. auto it = ArgVector.begin(); if (it != ArgVector.end()) ++it; - ArgVector.insert(it, GetStableCStr(SavedStrings, Mode)); + ArgVector.insert(it, GetStableCStr(SavedStrings, NameParts.ModeSuffix)); } - if (!Target.empty()) { + if (NameParts.TargetIsValid) { auto it = ArgVector.begin(); if (it != ArgVector.end()) ++it; - const char *arr[] = {"-target", GetStableCStr(SavedStrings, Target)}; + const char *arr[] = {"-target", GetStableCStr(SavedStrings, + NameParts.TargetPrefix)}; ArgVector.insert(it, std::begin(arr), std::end(arr)); } } @@ -305,6 +306,16 @@ return 1; } +// Directories searched for configuration specified by option '--config'. +static const ArrayRef SearchDirs = { +#if defined(CLANG_CONFIG_FILE_USER_DIR) + CLANG_CONFIG_FILE_USER_DIR, +#endif +#if defined(CLANG_CONFIG_FILE_SYSTEM_DIR) + CLANG_CONFIG_FILE_SYSTEM_DIR +#endif +}; + int main(int argc_, const char **argv_) { llvm::sys::PrintStackTraceOnErrorSignal(argv_[0]); llvm::PrettyStackTraceProgram X(argc_, argv_); @@ -324,12 +335,43 @@ llvm::InitializeAllTargets(); std::string ProgName = argv[0]; - std::pair TargetAndMode = - ToolChain::getTargetAndModeFromProgramName(ProgName); + auto TargetAndMode = ToolChain::getTargetAndModeFromProgramName(ProgName); llvm::BumpPtrAllocator A; llvm::StringSaver Saver(A); + // Try reading options from configuration file. + llvm::SmallString<128> ConfigFile; + bool CfgFound; + std::string ErrText; + + // First try config file specified in command line. It has higher priority + // than any other way to specify configuration. + CfgFound = llvm::cl::findConfigFile(ConfigFile, argv, SearchDirs, true, + ErrText); + if (!CfgFound && !ErrText.empty()) { + llvm::errs() << ProgName << ": CommandLine Error :" << ErrText << '\n'; + return 1; + } + + // If config file is not specified explicitly, try to determine configuration + // implicitly. First try to deduce configuration from executable name. For + // instance, a file 'armv7l-clang' applies config file 'armv7l.cfg'. + if (!CfgFound && !TargetAndMode.TargetPrefix.empty()) + CfgFound = llvm::cl::searchForFile(ConfigFile, SearchDirs, ProgName, + TargetAndMode.TargetPrefix + ".cfg"); + + // If config file is found, read options from it. + unsigned NumConfigOptions = 0; + if (CfgFound) { + if (!llvm::cl::readConfigFile(ConfigFile, Saver, argv, NumConfigOptions)) { + llvm::errs() << ProgName << + ": CommandLine Error : Cannot read configuration file '" << + ConfigFile.c_str() << "'\n"; + return 1; + } + } + // Parse response files using the GNU syntax, unless we're in CL mode. There // are two ways to put clang in CL compatibility mode: argv[0] is either // clang-cl or cl, or --driver-mode=cl is on the command line. The normal @@ -338,7 +380,7 @@ // Finally, our -cc1 tools don't care which tokenization mode we use because // response files written by clang will tokenize the same way in either mode. bool ClangCLMode = false; - if (TargetAndMode.second == "--driver-mode=cl" || + if (TargetAndMode.ModeSuffix == "--driver-mode=cl" || std::find_if(argv.begin(), argv.end(), [](const char *F) { return F && strcmp(F, "--driver-mode=cl") == 0; }) != argv.end()) { @@ -400,8 +442,12 @@ SmallVector PrependedOpts; getCLEnvVarOptions(OptCL.getValue(), Saver, PrependedOpts); - // Insert right after the program name to prepend to the argument list. - argv.insert(argv.begin() + 1, PrependedOpts.begin(), PrependedOpts.end()); + // Insert right after the program name to prepend to the argument list. If + // there are options read from config file, put the options from "CL" + // after them because the config file is considered as a "patch" to + // compiler defaults. + argv.insert(argv.begin() + 1 + NumConfigOptions, + PrependedOpts.begin(), PrependedOpts.end()); } // Arguments in "_CL_" are appended. llvm::Optional Opt_CL_ = llvm::sys::Process::GetEnv("_CL_"); @@ -446,10 +492,11 @@ ProcessWarningOptions(Diags, *DiagOpts, /*ReportDiags=*/false); Driver TheDriver(Path, llvm::sys::getDefaultTargetTriple(), Diags); + if (!ConfigFile.empty()) + TheDriver.setConfigFile(ConfigFile.str(), NumConfigOptions); SetInstallDir(argv, TheDriver, CanonicalPrefixes); - insertTargetAndModeArgs(TargetAndMode.first, TargetAndMode.second, argv, - SavedStrings); + insertTargetAndModeArgs(TargetAndMode, argv, SavedStrings); SetBackdoorDriverOutputsFromEnvVars(TheDriver);