Index: docs/UsersManual.rst =================================================================== --- docs/UsersManual.rst +++ docs/UsersManual.rst @@ -655,6 +655,75 @@ 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. + +If driver mode is specified in invocation, Clang tries to find file specific for +the specified mode. For instance, if executable file is `x86_64-clang-cl`, Clang +first looks for `x86_64-cl.cfg` and if it is not found, looks for `x86_64.cfg'. + +If command line contains options that effectively changes target architecture +(these are -m32, -EL and some other) and configuration file starts with architecture +name, Clang tries to load config file for effective architecture. For instance, +invocation: + +:: + + x86_64-clang -m32 abc.c + +makes Clang to search file `i368.cfg` first, and if it is not found, Clang looks +for the file `x86_64.cfg`. + +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 @@ -103,6 +103,16 @@ "invalid argument '%0' to -fdebug-prefix-map">; def err_drv_malformed_sanitizer_blacklist : Error< "malformed sanitizer blacklist: '%0'">; +def err_drv_duplicate_config : Error< + "no more than one option '--config' is allowed">; +def err_drv_config_file_not_exist : Error< + "configuration file '%0' does not exist">; +def err_drv_config_file_not_found : Error< + "configuration file '%0' cannot be found">; +def note_drv_config_file_searched_in : Note< + "was searched for in the directory: %0">; +def err_drv_cannot_read_config_file : Error< + "cannot read configuration file '%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 @@ -19,6 +19,8 @@ #include "clang/Driver/Util.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/StringSaver.h" #include #include @@ -26,14 +28,6 @@ namespace llvm { class Triple; - -namespace opt { - class Arg; - class ArgList; - class DerivedArgList; - class InputArgList; - class OptTable; -} } namespace clang { @@ -129,6 +123,9 @@ /// The original path to the clang executable. std::string ClangExecutable; + /// Target and driver mode components extracted from clang executable name. + ParsedClangName ClangNameParts; + /// The path to the installed clang directory, if any. std::string InstalledDir; @@ -208,6 +205,21 @@ /// Name to use when invoking gcc/g++. std::string CCCGenericGCCName; + /// Name of configuration file if used. + std::string ConfigFile; + + /// Allocator for string saver. + llvm::BumpPtrAllocator Alloc; + + /// Object that stores strings read from configuration file. + llvm::StringSaver Saver; + + /// Arguments originated from configuration file. + std::unique_ptr CfgOptions; + + /// Arguments originated from command line. + std::unique_ptr CLOptions; + /// Whether to check that input files exist when constructing compilation /// jobs. unsigned CheckInputsExist : 1; @@ -277,6 +289,8 @@ /// Name to use when invoking gcc/g++. const std::string &getCCCGenericGCCName() const { return CCCGenericGCCName; } + const std::string &getConfigFile() const { return ConfigFile; } + const llvm::opt::OptTable &getOpts() const { return *Opts; } const DiagnosticsEngine &getDiags() const { return Diags; } @@ -287,6 +301,8 @@ void setCheckInputsExist(bool Value) { CheckInputsExist = Value; } + void setTargetAndMode(const ParsedClangName &TM) { ClangNameParts = TM; } + const std::string &getTitle() { return DriverTitle; } void setTitle(std::string Value) { DriverTitle = std::move(Value); } @@ -486,6 +502,18 @@ LTOKind getLTOMode() const { return LTOMode; } private: + + /// Tries to load options from configuration file. + /// + /// \returns true if error occurred. + bool loadConfigFile(); + + /// Read options from the specified file. + /// + /// \param [in] FileName File to read. + /// \returns true, if error occurred while reading. + bool readConfigFile(StringRef FileName); + /// Set the driver mode (cl, gcc, etc) from an option string of the form /// --driver-mode=. void setDriverModeFromOption(StringRef Opt); Index: include/clang/Driver/Options.td =================================================================== --- include/clang/Driver/Options.td +++ include/clang/Driver/Options.td @@ -500,6 +500,7 @@ def client__name : JoinedOrSeparate<["-"], "client_name">; def combine : Flag<["-", "--"], "combine">, Flags<[DriverOption, Unsupported]>; def compatibility__version : JoinedOrSeparate<["-"], "compatibility_version">; +def config : JoinedOrSeparate<["--"], "config">, HelpText<"Specifies configuration file">; def coverage : Flag<["-", "--"], "coverage">; def cpp_precomp : Flag<["-"], "cpp-precomp">, Group; def current__version : JoinedOrSeparate<["-"], "current_version">; Index: include/clang/Driver/ToolChain.h =================================================================== --- include/clang/Driver/ToolChain.h +++ include/clang/Driver/ToolChain.h @@ -46,6 +46,23 @@ class Tool; class XRayArgs; +/// Helper structure used to pass information extracted from clang executable +/// name such as `i686-linux-android-g++`. +/// +struct ParsedClangName { + /// 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; + + ParsedClangName() : TargetIsValid(false) {} + ParsedClangName(std::string Mode) : ModeSuffix(Mode), TargetIsValid(false) {} + ParsedClangName(std::string Target, std::string Mode, bool IsRegistered) + : TargetPrefix(Target), ModeSuffix(Mode), TargetIsValid(IsRegistered) {} +}; + /// ToolChain - Access to tools for a single platform. class ToolChain { public: @@ -193,13 +210,16 @@ /// 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 ParsedClangName that contains the executable + /// name parts. + /// + static ParsedClangName getTargetAndModeFromProgramName(StringRef ProgName); // Tool access. Index: lib/Driver/Driver.cpp =================================================================== --- lib/Driver/Driver.cpp +++ lib/Driver/Driver.cpp @@ -61,6 +61,7 @@ #include "llvm/Option/OptSpecifier.h" #include "llvm/Option/OptTable.h" #include "llvm/Option/Option.h" +#include "llvm/Support/CommandLine.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" @@ -68,6 +69,7 @@ #include "llvm/Support/Process.h" #include "llvm/Support/Program.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/StringSaver.h" #include #include #include @@ -90,7 +92,8 @@ CCPrintHeadersFilename(nullptr), CCLogDiagnosticsFilename(nullptr), CCCPrintBindings(false), CCPrintHeaders(false), CCLogDiagnostics(false), CCGenDiagnostics(false), DefaultTargetTriple(DefaultTargetTriple), - CCCGenericGCCName(""), CheckInputsExist(true), CCCUsePCH(true), + CCCGenericGCCName(""), Saver(Alloc), + CheckInputsExist(true), CCCUsePCH(true), GenReproducer(false), SuppressMissingInputWarning(false) { // Provide a sane fallback if no VFS is specified. @@ -117,9 +120,8 @@ void Driver::ParseDriverMode(StringRef ProgramName, ArrayRef Args) { - auto Default = ToolChain::getTargetAndModeFromProgramName(ProgramName); - StringRef DefaultMode(Default.second); - setDriverModeFromOption(DefaultMode); + ClangNameParts = ToolChain::getTargetAndModeFromProgramName(ProgramName); + setDriverModeFromOption(ClangNameParts.ModeSuffix); for (const char *ArgPtr : Args) { // Ingore nullptrs, they are response file's EOL markers @@ -571,6 +573,213 @@ return; } +/// Directories searched for configuration files. +/// +static const ArrayRef CfgFileSearchDirs = { +#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 +}; + +/// Search the given directories for the specified file. +/// +/// \param FilePath [out] Full file path, if it was found. +/// \param FileName [in] The file name to search for. +/// \param Directories [in] List of directories to search. +/// \return True if the file was found. +/// +static bool searchDirectoriesForFile(SmallVectorImpl &FilePath, + std::string FileName, + ArrayRef Directories) { + for (const char *Dir : Directories) { + assert(Dir); + FilePath.clear(); + llvm::sys::path::append(FilePath, Dir, FileName); + llvm::sys::path::native(FilePath); + if (llvm::sys::fs::is_regular_file(FilePath)) + return true; + } + return false; +} + +/// Looks for the specified file in well-known directories. +/// +/// \param FilePath [out] File path, if the file was found. +/// \param Dirs [in] Directories used for the search. +/// \param BinDirectory [in] Path to the directory where executable +/// resides or empty string. +/// \param FileName [in] Name of the file to search for. +/// \return True if file was found. +/// +/// Looks for file specified by FileName sequentially in directories specified +/// by Dirs and BinDirectory. +/// +static bool searchForFile(SmallVectorImpl &FilePath, + ArrayRef Dirs, + StringRef BinDirectory, + StringRef FileName) { + FilePath.clear(); + if (searchDirectoriesForFile(FilePath, FileName, Dirs)) + return true; + + // If not found, try searching the directory where executable resides. + FilePath.clear(); + if (!BinDirectory.empty()) { + llvm::sys::path::append(FilePath, BinDirectory, FileName); + if (llvm::sys::fs::is_regular_file(FilePath)) + return true; + } + FilePath.clear(); + return false; +} + +bool Driver::readConfigFile(StringRef FileName) { + // Try reading the given file. + SmallVector NewCfgArgs; + if (!llvm::cl::readConfigFile(FileName, Saver, NewCfgArgs)) { + Diag(diag::err_drv_cannot_read_config_file) << FileName; + return true; + } + + // Update Driver state. + llvm::SmallString<128> CfgFileName(FileName); + llvm::sys::path::native(CfgFileName); + ConfigFile = CfgFileName.str(); + CfgOptions = llvm::make_unique(ParseArgStrings(NewCfgArgs)); + + // Claim all arguments that come from a configuration file so that the driver + // does not warn on any that is unused. + for (auto *A : *CfgOptions) + A->claim(); + return false; +} + +bool Driver::loadConfigFile() { + std::string CfgFileName; + bool FileSpecifiedExplicitly = false; + + // First try to find config file specified in command line. + if (CLOptions) { + std::vector ConfigFiles = + CLOptions->getAllArgValues(options::OPT_config); + if (ConfigFiles.size() > 1) { + Diag(diag::err_drv_duplicate_config); + return true; + } + + if (!ConfigFiles.empty()) { + CfgFileName = ConfigFiles.front(); + assert(!CfgFileName.empty()); + + // If argument contains directory separator, treat it as a path to + // configuration file. + if (llvm::sys::path::has_parent_path(CfgFileName)) { + SmallString<128> CfgFilePath; + if (llvm::sys::path::is_relative(CfgFileName)) + llvm::sys::fs::current_path(CfgFilePath); + llvm::sys::path::append(CfgFilePath, CfgFileName); + if (!llvm::sys::fs::is_regular_file(CfgFilePath)) { + Diag(diag::err_drv_config_file_not_exist) << CfgFilePath; + return true; + } + return readConfigFile(CfgFilePath); + } + + FileSpecifiedExplicitly = true; + } + } + + // If config file is not specified explicitly, try to deduce configuration + // from executable name. For instance, a file 'armv7l-clang' will search for + // config file 'armv7l.cfg'. + if (CfgFileName.empty() && !ClangNameParts.TargetPrefix.empty()) + CfgFileName = ClangNameParts.TargetPrefix; + + if (CfgFileName.empty()) + return false; + + // If driver mode is specified, explicitly or encoded in executable name, try + // first to find config file for this driver mode. For instance, if executable + // file is 'armv7l-clang-g++', try first to find 'armv7l-g++.cfg', if it is + // not found, look for 'armv7l.cfg'. + SmallString<32> ModeSuffix; + if (Arg *ModeArg = CLOptions->getLastArg(options::OPT_driver_mode)) { + ModeSuffix = "-"; + ModeSuffix += ModeArg->getValue(); + if (!FileSpecifiedExplicitly) + ModeSuffix += ".cfg"; + } + + + // If config file starts with architecture name and command line options + // redefine the architecture (with options like -m32 -LE etc), try finding + // new config file with that architecture. + SmallString<128> FixedConfigFile; + { + StringRef FileName(llvm::sys::path::filename(CfgFileName)); + StringRef CfgFileArch = FileName; + size_t ArchLen = CfgFileArch.find_first_of('-'); + if (ArchLen != StringRef::npos) { + CfgFileArch.take_front(ArchLen); + } else { + ArchLen = CfgFileArch.find_last_of('.'); + if (ArchLen == StringRef::npos) + ArchLen = CfgFileArch.size(); + } + llvm::Triple CfgTriple(llvm::Triple::normalize(CfgFileArch)); + if (CfgTriple.getArch() != llvm::Triple::ArchType::UnknownArch) { + llvm::Triple EffectiveTriple = computeTargetTriple(*this, + CfgTriple.getTriple(), *CLOptions); + if (CfgTriple.getArch() != EffectiveTriple.getArch()) { + FixedConfigFile = EffectiveTriple.getArchName(); + if (ArchLen < FileName.size()) + FixedConfigFile += FileName.substr(ArchLen); + } + } + } + + // Try to find config file. For invocation 'x86_64-clang-cl -m32 abc.cpp' we + // have variants: 'x86_64-cl.cfg', 'x86_64.cfg', 'i386-cl.cfg' and 'i386.cfg'. + llvm::SmallString<128> CfgFilePath; + SmallString<128> FName(FixedConfigFile); + if (!FixedConfigFile.empty()) { + if (!ModeSuffix.empty()) { + FName.append(ModeSuffix); + if (searchForFile(CfgFilePath, CfgFileSearchDirs, Dir, FName)) + return readConfigFile(CfgFilePath); + } + FName.set_size(FixedConfigFile.size()); + if (!FileSpecifiedExplicitly) + FName.append(".cfg"); + if (searchForFile(CfgFilePath, CfgFileSearchDirs, Dir, FName)) + return readConfigFile(CfgFilePath); + } + FName = CfgFileName; + if (!ModeSuffix.empty()) { + FName += ModeSuffix; + if (searchForFile(CfgFilePath, CfgFileSearchDirs, Dir, FName)) + return readConfigFile(CfgFilePath); + } + FName.set_size(CfgFileName.size()); + if (!FileSpecifiedExplicitly) + FName.append(".cfg"); + if (searchForFile(CfgFilePath, CfgFileSearchDirs, Dir, FName)) + return readConfigFile(CfgFilePath); + + if (FileSpecifiedExplicitly) { + Diag(diag::err_drv_config_file_not_found) << CfgFileName; + for (auto SearchDir : CfgFileSearchDirs) + Diag(diag::note_drv_config_file_searched_in) << SearchDir; + Diag(diag::note_drv_config_file_searched_in) << Dir; + return true; + } + + return false; +} + Compilation *Driver::BuildCompilation(ArrayRef ArgList) { llvm::PrettyStackTraceString CrashInfo("Compilation construction"); @@ -592,15 +801,37 @@ // how other options are parsed. ParseDriverMode(ClangExecutable, ArgList.slice(1)); + // Arguments specified in command line. + CLOptions = llvm::make_unique(ParseArgStrings(ArgList.slice(1))); + if (Diags.hasErrorOccurred()) + return nullptr; + + // Try parsing configuration file. + if (loadConfigFile()) + return nullptr; + bool HasConfigFile = CfgOptions.get() != nullptr; + // FIXME: What are we going to do with -V and -b? + // All arguments, from both config file and command line. + InputArgList Args = std::move(HasConfigFile ? std::move(*CfgOptions) + : std::move(*CLOptions)); + if (HasConfigFile) + for (auto *Opt : *CLOptions) { + const Arg *BaseArg = &Opt->getBaseArg(); + if (BaseArg == Opt) + BaseArg = nullptr; + Arg *Copy = new llvm::opt::Arg(Opt->getOption(), Opt->getSpelling(), + Args.size(), BaseArg); + Copy->getValues() = Opt->getValues(); + if (Opt->isClaimed()) + Copy->claim(); + Args.append(Copy); + } + // FIXME: This stuff needs to go into the Compilation, not the driver. bool CCCPrintPhases; - InputArgList Args = ParseArgStrings(ArgList.slice(1)); - if (Diags.hasErrorOccurred()) - return nullptr; - // Silence driver warnings if requested Diags.setIgnoreAllWarnings(Args.hasArg(options::OPT_w)); @@ -1115,6 +1346,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: lib/Driver/ToolChain.cpp =================================================================== --- lib/Driver/ToolChain.cpp +++ lib/Driver/ToolChain.cpp @@ -176,28 +176,25 @@ } } // anonymous namespace -std::pair +ParsedClangName ToolChain::getTargetAndModeFromProgramName(StringRef PN) { std::string ProgName = normalizeProgramName(PN); const DriverSuffix *DS = parseDriverSuffix(ProgName); if (!DS) - return std::make_pair("", ""); + return ParsedClangName(); 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 ParsedClangName(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 ParsedClangName{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-errs.c =================================================================== --- /dev/null +++ test/Driver/config-file-errs.c @@ -0,0 +1,29 @@ +//--- 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 +// CHECK-MISSING-FILE: argument to '--config' is missing (expected 1 value) + + +//--- Argument of '--config' must be existing file, if it is specified by path. +// +// RUN: not %clang --config somewhere/nonexistent-config-file 2>&1 | FileCheck %s -check-prefix CHECK-NONEXISTENT +// CHECK-NONEXISTENT: configuration file '{{.*}}somewhere/nonexistent-config-file' does not exist + + +//--- Argument of '--config' must exist somewhere is well-known directories, it is is specified by bare name. +// +// RUN: not %clang --config nonexistent-config-file 2>&1 | FileCheck %s -check-prefix CHECK-NOTFOUND +// CHECK-NOTFOUND: configuration file 'nonexistent-config-file' cannot be found +// CHECK-NOTFOUND: was searched for in the directory: + + +//--- 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: error: argument to '-target' is missing Index: test/Driver/config-file.c =================================================================== --- /dev/null +++ test/Driver/config-file.c @@ -0,0 +1,37 @@ +//--- 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 + + +//--- 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: + Index: test/Driver/config-file2.c =================================================================== --- /dev/null +++ test/Driver/config-file2.c @@ -0,0 +1,107 @@ +// 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 -no-canonical-prefixes %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 -no-canonical-prefixes %s -### 2>&1 | FileCheck %s -check-prefix CHECK-BIN +// +// CHECK-BIN: Configuration file: {{.*}}/testbin/aaa.cfg +// CHECK-BIN: -Werror + + +//--- If config file is specified by relative path (workdir/cfg-s2), it is searched for by +// that path. +// +// 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 + + +//--- Invocation qqq-g++ tries to find config file qqq-g++.cfg first ... +// +// RUN: mkdir -p %T/testdmode +// RUN: [ ! -s %T/testdmode/qqq-clang-g++ ] || rm %T/testdmode/qqq-clang-g++ +// RUN: ln -s %clang %T/testdmode/qqq-clang-g++ +// RUN: echo "-Wundefined-func-template" > %T/testdmode/qqq-g++.cfg +// RUN: echo "-Werror" > %T/testdmode/qqq.cfg +// +// RUN: %T/testdmode/qqq-clang-g++ -c -no-canonical-prefixes %s -### 2>&1 | FileCheck %s -check-prefix CHECK-DMODE +// +// CHECK-DMODE: Configuration file: {{.*}}/testdmode/qqq-g++.cfg +// CHECK-DMODE: -Wundefined-func-template +// CHECK-DMODE-NOT: -Werror +// +//--- ... and qqq.cfg if qqq-g++.cfg is not found. +// +// RUN: rm %T/testdmode/qqq-g++.cfg +// +// RUN: %T/testdmode/qqq-clang-g++ -c -no-canonical-prefixes %s -### 2>&1 | FileCheck %s -check-prefix CHECK-DMODE2 +// +// CHECK-DMODE2: Configuration file: {{.*}}/testdmode/qqq.cfg +// CHECK-DMODE2: -Werror +// CHECK-DMODE2-NOT: -Wundefined-func-template + + +//--- If command line contains options that change triple (-m32), clang tries +// reloading config file. +// +// RUN: mkdir -p %T/testreload +// RUN: [ ! -s %T/testreload/x86_64-clang ] || rm %T/testreload/x86_64-clang +// RUN: ln -s %clang %T/testreload/x86_64-clang +// RUN: echo "-Wundefined-func-template" > %T/testreload/x86_64.cfg +// RUN: echo "-Werror" > %T/testreload/i386.cfg +// +// RUN: %T/testreload/x86_64-clang -c -m32 -no-canonical-prefixes %s -### 2>&1 | FileCheck %s -check-prefix CHECK-RELOAD +// +// CHECK-RELOAD: Target: i386 +// CHECK-RELOAD: Configuration file: {{.*}}/testreload/i386.cfg +// CHECK-RELOAD-NOT: -Wundefined-func-template +// CHECK-RELOAD: -Werror + + +//--- When reloading config file, target-clang-g++ tries to find config target32-g++.cfg first ... +// +// RUN: mkdir -p %T/testreload2 +// RUN: [ ! -s %T/testreload2/x86_64-clang-g++ ] || rm %T/testreload2/x86_64-clang-g++ +// RUN: ln -s %clang %T/testreload2/x86_64-clang-g++ +// RUN: echo "-Wundefined-func-template" > %T/testreload2/i386-g++.cfg +// RUN: echo "-Werror" > %T/testreload2/i386.cfg +// +// RUN: %T/testreload2/x86_64-clang-g++ -c -m32 -no-canonical-prefixes %s -### 2>&1 | FileCheck %s -check-prefix CHECK-RELOAD2 +// +// CHECK-RELOAD2: Configuration file: {{.*}}/testreload2/i386-g++.cfg +// CHECK-RELOAD2: -Wundefined-func-template +// CHECK-RELOAD2-NOT: -Werror +// +//--- and target32.cfg if target32-g++.cfg is not found. +// +// RUN: rm %T/testreload2/i386-g++.cfg +// +// RUN: %T/testreload2/x86_64-clang-g++ -c -m32 -no-canonical-prefixes %s -### 2>&1 | FileCheck %s -check-prefix CHECK-RELOAD3 +// +// CHECK-RELOAD3: Configuration file: {{.*}}/testreload2/i386.cfg +// CHECK-RELOAD3: -Werror +// CHECK-RELOAD3-NOT: -Wundefined-func-template + Index: tools/driver/driver.cpp =================================================================== --- tools/driver/driver.cpp +++ tools/driver/driver.cpp @@ -199,23 +199,19 @@ extern int cc1as_main(ArrayRef Argv, const char *Argv0, void *MainAddr); -static void insertTargetAndModeArgs(StringRef Target, StringRef Mode, +static void insertTargetAndModeArgs(const ParsedClangName &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(ArgVector.end(), + GetStableCStr(SavedStrings, NameParts.ModeSuffix)); } - if (!Target.empty()) { - auto it = ArgVector.begin(); - if (it != ArgVector.end()) - ++it; - const char *arr[] = {"-target", GetStableCStr(SavedStrings, Target)}; - ArgVector.insert(it, std::begin(arr), std::end(arr)); + if (NameParts.TargetIsValid) { + const char *arr[] = {"-target", GetStableCStr(SavedStrings, + NameParts.TargetPrefix)}; + ArgVector.insert(ArgVector.end(), std::begin(arr), std::end(arr)); } } @@ -323,9 +319,7 @@ } llvm::InitializeAllTargets(); - std::string ProgName = argv[0]; - std::pair TargetAndMode = - ToolChain::getTargetAndModeFromProgramName(ProgName); + auto TargetAndMode = ToolChain::getTargetAndModeFromProgramName(argv[0]); llvm::BumpPtrAllocator A; llvm::StringSaver Saver(A); @@ -338,7 +332,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()) { @@ -447,9 +441,9 @@ Driver TheDriver(Path, llvm::sys::getDefaultTargetTriple(), Diags); SetInstallDir(argv, TheDriver, CanonicalPrefixes); + TheDriver.setTargetAndMode(TargetAndMode); - insertTargetAndModeArgs(TargetAndMode.first, TargetAndMode.second, argv, - SavedStrings); + insertTargetAndModeArgs(TargetAndMode, argv, SavedStrings); SetBackdoorDriverOutputsFromEnvVars(TheDriver); @@ -491,7 +485,6 @@ } } } - Diags.getClient()->finish(); // If any timers were active but haven't been destroyed yet, print their