Index: docs/UsersManual.rst =================================================================== --- docs/UsersManual.rst +++ docs/UsersManual.rst @@ -644,6 +644,54 @@ 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. + +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 in the directories: `~/.llvm` and the +directory where clang executable resides. 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/Driver/Driver.h =================================================================== --- include/clang/Driver/Driver.h +++ include/clang/Driver/Driver.h @@ -207,6 +207,9 @@ /// Name to use when invoking gcc/g++. std::string CCCGenericGCCName; + /// Name of configuration file if used. + std::string ConfigFile; + /// Whether to check that input files exist when constructing compilation /// jobs. unsigned CheckInputsExist : 1; @@ -272,6 +275,9 @@ /// Name to use when invoking gcc/g++. const std::string &getCCCGenericGCCName() const { return CCCGenericGCCName; } + const std::string &getConfigFile() const { return ConfigFile; } + void setConfigFile(StringRef x) { ConfigFile = x; } + const llvm::opt::OptTable &getOpts() const { return *Opts; } const DiagnosticsEngine &getDiags() const { return Diags; } Index: lib/Driver/Driver.cpp =================================================================== --- lib/Driver/Driver.cpp +++ lib/Driver/Driver.cpp @@ -1086,6 +1086,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 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,6 @@ +# target +-target x86_64-unknown-linux-gnu + +# nested inclusion +@config-3.cfg +@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/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,34 @@ +//--- 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 -### 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 +// CHECK-HHH2: -isysroot /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: +// CHECK-INEX-NOSEARCH: ~/.llvm +// 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- 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 @@ -305,6 +305,35 @@ return 1; } +// Directories searched for configuration specified by option '--config'. +static const char * const SearchDirs[] = { "~/.llvm" }; + +/// Deduce configuration name if it is encoded in the executable name. +/// +/// \param ConfigFile [out] Is assigned configuration file path. +/// \param ProgramName [in] clang executable path. +/// \return True if configuration file was found. +/// +/// If clang executable is named e.g. 'armv7l-clang' the function tries to +/// find config file 'armv7l.cfg'. If it is found, its path is put into +/// ConfigFile and the function returns true. +/// +static bool findConfigFileFromProgramName( + llvm::SmallVectorImpl &ConfigFile, StringRef ProgramName) { + ConfigFile.clear(); + StringRef PName = llvm::sys::path::stem(ProgramName); + size_t Pos = PName.find("-clang"); + if (Pos != StringRef::npos) { + ConfigFile.append(PName.begin(), PName.begin() + Pos); + const StringRef Ext(".cfg"); + ConfigFile.append(Ext.begin(), Ext.end()); + std::string CName(ConfigFile.begin(), ConfigFile.size()); + return llvm::cl::searchForFile(ConfigFile, ArrayRef(), + ProgramName, CName); + } + return false; +} + int main(int argc_, const char **argv_) { llvm::sys::PrintStackTraceOnErrorSignal(argv_[0]); llvm::PrettyStackTraceProgram X(argc_, argv_); @@ -330,6 +359,31 @@ llvm::BumpPtrAllocator A; llvm::StringSaver Saver(A); + // Try reading options from configuration file. + llvm::SmallString<128> ConfigFile; + llvm::cl::SearchResult SRes; + + // First try config file specified in command line. It has higher priority + // than any other way to specify configuration. + SRes = llvm::cl::findConfigFileFromArgs(ConfigFile, argv, SearchDirs, true); + if (llvm::cl::checkConfigFileSearchResult(SRes, ConfigFile, SearchDirs, true, + ProgName)) + 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'. Second, + // try to find file 'clang.cfg'. + if (SRes == llvm::cl::SearchResult::NotSpecified) { + if (findConfigFileFromProgramName(ConfigFile, ProgName)) + SRes = llvm::cl::SearchResult::Successful; + } + + if (SRes == llvm::cl::SearchResult::Successful) { + unsigned NumOpts; + llvm::cl::readConfigFile(ConfigFile, Saver, argv, NumOpts); + } + // 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 @@ -446,6 +500,8 @@ ProcessWarningOptions(Diags, *DiagOpts, /*ReportDiags=*/false); Driver TheDriver(Path, llvm::sys::getDefaultTargetTriple(), Diags); + if (!ConfigFile.empty()) + TheDriver.setConfigFile(ConfigFile.str()); SetInstallDir(argv, TheDriver, CanonicalPrefixes); insertTargetAndModeArgs(TargetAndMode.first, TargetAndMode.second, argv,