diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -940,10 +940,14 @@ @linux.options Files included by `@file` directives in configuration files are resolved -relative to the including file. For example, if a configuration file +relative to the including file by default. For example, if a configuration file `~/.llvm/target.cfg` contains the directive `@os/linux.opts`, the file `linux.opts` is searched for in the directory `~/.llvm/os`. +Option `--search-config-dirs` changes the search algorith if the included +file is specified without a directory separator. In this case the file is looked +for in the same directories as configuration files. + To generate paths relative to the configuration file, the `` token may be used. This will expand to the absolute path of the directory containing the configuration file. 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 @@ -34,6 +34,9 @@ namespace vfs { class FileSystem; } +namespace cl { +class ExpansionContext; +} } // namespace llvm namespace clang { @@ -673,7 +676,7 @@ /// /// \param [in] FileName File to read. /// \returns true, if error occurred while reading. - bool readConfigFile(StringRef FileName); + bool readConfigFile(StringRef FileName, llvm::cl::ExpansionContext &ECtx); /// Set the driver mode (cl, gcc, etc) from the value of the `--driver-mode` /// option. diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -905,6 +905,8 @@ HelpText<"System directory for configuration files">; def config_user_dir_EQ : Joined<["--"], "config-user-dir=">, Flags<[NoXarchOption, HelpHidden]>, HelpText<"User directory for configuration files">; +def search_config_dirs : Flag<["--"], "search-config-dirs">, Flags<[NoXarchOption]>, + HelpText<"Search for file included by '@file' in configuration file in search directories">; def coverage : Flag<["-", "--"], "coverage">, Group, Flags<[CoreOption]>; def cpp_precomp : Flag<["-"], "cpp-precomp">, Group; def current__version : JoinedOrSeparate<["-"], "current_version">; 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 @@ -900,40 +900,11 @@ // } -/// Looks the given directories for the specified file. -/// -/// \param[out] FilePath File path, if the file was found. -/// \param[in] Dirs Directories used for the search. -/// \param[in] FileName 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. -/// -static bool searchForFile(SmallVectorImpl &FilePath, - ArrayRef Dirs, StringRef FileName, - llvm::vfs::FileSystem &FS) { - SmallString<128> WPath; - for (const StringRef &Dir : Dirs) { - if (Dir.empty()) - continue; - WPath.clear(); - llvm::sys::path::append(WPath, Dir, FileName); - llvm::sys::path::native(WPath); - auto Status = FS.status(WPath); - if (Status && Status->getType() == llvm::sys::fs::file_type::regular_file) { - FilePath = std::move(WPath); - return true; - } - } - return false; -} - -bool Driver::readConfigFile(StringRef FileName) { +bool Driver::readConfigFile(StringRef FileName, + llvm::cl::ExpansionContext &ECtx) { // Try reading the given file. SmallVector NewCfgArgs; - llvm::cl::ExpansionContext ExpCtx(Saver, llvm::cl::tokenizeConfigFile, &getVFS()); - if (!ExpCtx.readConfigFile(FileName, NewCfgArgs)) { + if (!ECtx.readConfigFile(FileName, NewCfgArgs)) { Diag(diag::err_drv_cannot_read_config_file) << FileName; return true; } @@ -966,6 +937,8 @@ bool Driver::loadConfigFile() { std::string CfgFileName; bool FileSpecifiedExplicitly = false; + llvm::cl::ExpansionContext ExpCtx(Saver, llvm::cl::tokenizeConfigFile, + &getVFS()); // Process options that change search path for config files. if (CLOptions) { @@ -987,6 +960,8 @@ else UserConfigDir = static_cast(CfgDir); } + if (CLOptions->hasArg(options::OPT_search_config_dirs)) + ExpCtx.setSearchAsConfig(true); } // First try to find config file specified in command line. @@ -1018,7 +993,7 @@ return true; } } - return readConfigFile(CfgFilePath); + return readConfigFile(CfgFilePath, ExpCtx); } FileSpecifiedExplicitly = true; @@ -1071,30 +1046,31 @@ // Prepare list of directories where config file is searched for. StringRef CfgFileSearchDirs[] = {UserConfigDir, SystemConfigDir, Dir}; + ExpCtx.setSearchDirs(CfgFileSearchDirs); // Try to find config file. First try file with corrected architecture. - llvm::SmallString<128> CfgFilePath; + std::string CfgFilePath; if (!FixedConfigFile.empty()) { - if (searchForFile(CfgFilePath, CfgFileSearchDirs, FixedConfigFile, getVFS())) - return readConfigFile(CfgFilePath); + if (ExpCtx.findConfigFile(FixedConfigFile, CfgFilePath)) + return readConfigFile(CfgFilePath, ExpCtx); // If 'x86_64-clang.cfg' was not found, try 'x86_64.cfg'. FixedConfigFile.resize(FixedArchPrefixLen); FixedConfigFile.append(".cfg"); - if (searchForFile(CfgFilePath, CfgFileSearchDirs, FixedConfigFile, getVFS())) - return readConfigFile(CfgFilePath); + if (ExpCtx.findConfigFile(FixedConfigFile, CfgFilePath)) + return readConfigFile(CfgFilePath, ExpCtx); } // Then try original file name. - if (searchForFile(CfgFilePath, CfgFileSearchDirs, CfgFileName, getVFS())) - return readConfigFile(CfgFilePath); + if (ExpCtx.findConfigFile(CfgFileName, CfgFilePath)) + return readConfigFile(CfgFilePath, ExpCtx); // Finally try removing driver mode part: 'x86_64-clang.cfg' -> 'x86_64.cfg'. if (!ClangNameParts.ModeSuffix.empty() && !ClangNameParts.TargetPrefix.empty()) { CfgFileName.assign(ClangNameParts.TargetPrefix); CfgFileName.append(".cfg"); - if (searchForFile(CfgFilePath, CfgFileSearchDirs, CfgFileName, getVFS())) - return readConfigFile(CfgFilePath); + if (ExpCtx.findConfigFile(CfgFileName, CfgFilePath)) + return readConfigFile(CfgFilePath, ExpCtx); } // Report error but only if config file was specified explicitly, by option @@ -1156,7 +1132,8 @@ if (HasConfigFile) for (auto *Opt : *CLOptions) { - if (Opt->getOption().matches(options::OPT_config)) + if (Opt->getOption().matches(options::OPT_config) || + Opt->getOption().matches(options::OPT_search_config_dirs)) continue; const Arg *BaseArg = &Opt->getBaseArg(); if (BaseArg == Opt) diff --git a/clang/test/Driver/Inputs/config/config-3.cfg b/clang/test/Driver/Inputs/config/config-3.cfg new file mode 100644 --- /dev/null +++ b/clang/test/Driver/Inputs/config/config-3.cfg @@ -0,0 +1 @@ +-isysroot /opt/sdk/user 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 @@ -39,14 +39,14 @@ //--- Nested config files // -// RUN: %clang --config-system-dir=%S/Inputs --config-user-dir= --config config-2.cfg -S %s -### 2>&1 | FileCheck %s -check-prefix CHECK-NESTED +// RUN: %clang --config-system-dir=%S/Inputs --config-user-dir=%S/Inputs/config --config config-2.cfg -S %s -### 2>&1 | FileCheck %s -check-prefix CHECK-NESTED // CHECK-NESTED: Configuration file: {{.*}}Inputs{{.}}config-2.cfg // CHECK-NESTED: -Wundefined-func-template -// RUN: %clang --config-system-dir=%S/Inputs --config-user-dir= --config config-2.cfg -S %s -### 2>&1 | FileCheck %s -check-prefix CHECK-NESTED2 +// RUN: %clang --config-system-dir=%S/Inputs --config-user-dir=%S/Inputs/config --config config-2.cfg -S --search-config-dirs %s -### 2>&1 | FileCheck %s -check-prefix CHECK-NESTED2 // CHECK-NESTED2: Configuration file: {{.*}}Inputs{{.}}config-2.cfg -// CHECK-NESTED2: -Wundefined-func-template - +// CHECK-NESTED2: -isysroot +// CHECK-NESTED2-SAME: /opt/sdk/user // RUN: %clang --config %S/Inputs/config-2a.cfg -S %s -### 2>&1 | FileCheck %s -check-prefix CHECK-NESTEDa // CHECK-NESTEDa: Configuration file: {{.*}}Inputs{{.}}config-2a.cfg diff --git a/clang/unittests/Driver/ToolChainTest.cpp b/clang/unittests/Driver/ToolChainTest.cpp --- a/clang/unittests/Driver/ToolChainTest.cpp +++ b/clang/unittests/Driver/ToolChainTest.cpp @@ -506,9 +506,12 @@ FS->addFile("/home/test/bin/root.cfg", 0, llvm::MemoryBuffer::getMemBuffer("--sysroot=/opt/sdk/platform2\n")); + SmallString<128> ClangExecutable("/home/test/bin/clang"); + FS->makeAbsolute(ClangExecutable); + { - Driver TheDriver("/home/test/bin/clang", "arm-linux-gnueabi", Diags, - "clang LLVM compiler", FS); + Driver TheDriver(ClangExecutable, "arm-linux-gnueabi", Diags, + "clang LLVM compiler", FS); std::unique_ptr C( TheDriver.BuildCompilation({"/home/test/bin/clang", "--config", "root.cfg", @@ -519,7 +522,7 @@ EXPECT_EQ("/opt/sdk/platform1", TheDriver.SysRoot); } { - Driver TheDriver("/home/test/bin/clang", "arm-linux-gnueabi", Diags, + Driver TheDriver(ClangExecutable, "arm-linux-gnueabi", Diags, "clang LLVM compiler", FS); std::unique_ptr C( TheDriver.BuildCompilation({ "/home/test/bin/clang", @@ -531,7 +534,7 @@ EXPECT_EQ("/opt/sdk/platform0", TheDriver.SysRoot); } { - Driver TheDriver("/home/test/bin/clang", "arm-linux-gnueabi", Diags, + Driver TheDriver(ClangExecutable, "arm-linux-gnueabi", Diags, "clang LLVM compiler", FS); std::unique_ptr C( TheDriver.BuildCompilation({ "/home/test/bin/clang", @@ -544,4 +547,129 @@ } } +TEST(ToolChainTest, ConfigFileSearch2) { + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + struct TestDiagnosticConsumer : public DiagnosticConsumer {}; + DiagnosticsEngine Diags(DiagID, &*DiagOpts, new TestDiagnosticConsumer); + IntrusiveRefCntPtr FS( + new llvm::vfs::InMemoryFileSystem); + +#ifdef _WIN32 + const char *TestRoot = "C:\\"; +#else + const char *TestRoot = "/"; +#endif + FS->setCurrentWorkingDirectory(TestRoot); + + FS->addFile("/opt/sdk/root.cfg", 0, + llvm::MemoryBuffer::getMemBuffer("@platform.cfg\n")); + FS->addFile("/opt/sdk/root2.cfg", 0, + llvm::MemoryBuffer::getMemBuffer("@platform.cfg\n" + "--search-config-dirs\n")); + FS->addFile("/opt/sdk/platform.cfg", 0, + llvm::MemoryBuffer::getMemBuffer("--sysroot=/platform-sys\n")); + FS->addFile("/home/test/sdk/platform.cfg", 0, + llvm::MemoryBuffer::getMemBuffer("--sysroot=/platform-user\n")); + FS->addFile("/home/test/bin/platform.cfg", 0, + llvm::MemoryBuffer::getMemBuffer("--sysroot=/platform-bin\n")); + + SmallString<128> ClangExecutable("/home/test/bin/clang"); + FS->makeAbsolute(ClangExecutable); + + { + // Without '--search-config-dirs' included file is searched for in the same + // directory where including file is resided. + Driver TheDriver(ClangExecutable, "arm-linux-gnueabi", Diags, + "clang LLVM compiler", FS); + std::unique_ptr C(TheDriver.BuildCompilation( + {"/home/test/bin/clang", "--config", "root.cfg", + "--config-system-dir=/opt/sdk", "--config-user-dir=/home/test/sdk"})); + ASSERT_TRUE(C); + ASSERT_FALSE(C->containsError()); + EXPECT_EQ("/platform-sys", TheDriver.SysRoot); + } + + { + // With '--search-config-dirs' included file is searched for in the same + // directories as config files, so the file in user directory is found. + Driver TheDriver(ClangExecutable, "arm-linux-gnueabi", Diags, + "clang LLVM compiler", FS); + std::unique_ptr C(TheDriver.BuildCompilation( + {"/home/test/bin/clang", "--config", "root.cfg", + "--config-system-dir=/opt/sdk", "--config-user-dir=/home/test/sdk", + "--search-config-dirs"})); + ASSERT_TRUE(C); + ASSERT_FALSE(C->containsError()); + EXPECT_EQ("/platform-user", TheDriver.SysRoot); + } + + { + // The option '--search-config-dirs' can be specified inside configuration + // file. + Driver TheDriver(ClangExecutable, "arm-linux-gnueabi", Diags, + "clang LLVM compiler", FS); + std::unique_ptr C(TheDriver.BuildCompilation( + {"/home/test/bin/clang", "--config", "root2.cfg", + "--config-system-dir=/opt/sdk", "--config-user-dir=/home/test/sdk"})); + ASSERT_TRUE(C); + ASSERT_FALSE(C->containsError()); + EXPECT_EQ("/platform-user", TheDriver.SysRoot); + } +} + +TEST(ToolChainTest, ConfigFileSearch3) { + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + struct TestDiagnosticConsumer : public DiagnosticConsumer {}; + DiagnosticsEngine Diags(DiagID, &*DiagOpts, new TestDiagnosticConsumer); + IntrusiveRefCntPtr FS( + new llvm::vfs::InMemoryFileSystem); + +#ifdef _WIN32 + const char *TestRoot = "C:\\"; +#else + const char *TestRoot = "/"; +#endif + FS->setCurrentWorkingDirectory(TestRoot); + + SmallString<128> ClangExecutable("/opt/sdk/bin/clang"); + FS->makeAbsolute(ClangExecutable); + + // Configuration file is loaded from system directory, it includes file + // from binary one. + FS->addFile("/opt/sdk/root.cfg", 0, + llvm::MemoryBuffer::getMemBuffer("--search-config-dirs\n" + "@workspace.cfg\n")); + FS->addFile("/opt/sdk/bin/workspace.cfg", 0, + llvm::MemoryBuffer::getMemBuffer("--sysroot=/settings-bin\n")); + + { + Driver TheDriver(ClangExecutable, "arm-linux-gnueabi", Diags, + "clang LLVM compiler", FS); + std::unique_ptr C(TheDriver.BuildCompilation( + {ClangExecutable.c_str(), "--config", "root.cfg", + "--config-system-dir=/opt/sdk", "--config-user-dir=/home/test/sdk"})); + ASSERT_TRUE(C); + ASSERT_FALSE(C->containsError()); + EXPECT_EQ("/settings-bin", TheDriver.SysRoot); + } + + // User can override included file by providing file with the same name in + // the user directory. + FS->addFile("/home/test/sdk/workspace.cfg", 0, + llvm::MemoryBuffer::getMemBuffer("--sysroot=/settings-user\n")); + + { + Driver TheDriver(ClangExecutable, "arm-linux-gnueabi", Diags, + "clang LLVM compiler", FS); + std::unique_ptr C(TheDriver.BuildCompilation( + {ClangExecutable.c_str(), "--config", "root.cfg", + "--config-system-dir=/opt/sdk", "--config-user-dir=/home/test/sdk"})); + ASSERT_TRUE(C); + ASSERT_FALSE(C->containsError()); + EXPECT_EQ("/settings-user", TheDriver.SysRoot); + } +} + } // end anonymous namespace. diff --git a/llvm/include/llvm/Support/CommandLine.h b/llvm/include/llvm/Support/CommandLine.h --- a/llvm/include/llvm/Support/CommandLine.h +++ b/llvm/include/llvm/Support/CommandLine.h @@ -2080,6 +2080,9 @@ /// used instead. StringRef CurrentDir; + /// Directories used for search of config files. + ArrayRef SearchDirs; + /// True if names of nested response files must be resolved relative to /// including file. bool RelativeNames = false; @@ -2091,6 +2094,10 @@ /// If true, body of config file is expanded. bool InConfigFile = false; + /// If true, the file included by '@file' is searched for as a config file, in + /// the config search directories. + bool SearchAsConfig = false; + llvm::Error expandResponseFile(StringRef FName, SmallVectorImpl &NewArgv); @@ -2116,6 +2123,29 @@ return *this; } + ArrayRef getSearchDirs() const { return SearchDirs; } + ExpansionContext &setSearchDirs(ArrayRef X) { + SearchDirs = X; + return *this; + } + + bool getSearchAsConfig() const { return SearchAsConfig; } + ExpansionContext &setSearchAsConfig(bool X) { + SearchAsConfig = X; + return *this; + } + + /// Looks for the specified configuration file. + /// + /// \param[in] FileName Name of the file to search for. + /// \param[out] FilePath File absolute path, if it was found. + /// \return True if file was found. + /// + /// If the specified file name contains a directory separator, it is searched + /// for by its absolute path. Otherwise looks for file sequentially in + /// directories specified by SearchDirs field. + bool findConfigFile(StringRef FileName, std::string &FilePath); + /// Reads command line options from the given configuration file. /// /// \param [in] CfgFile Path to configuration file. diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp --- a/llvm/lib/Support/CommandLine.cpp +++ b/llvm/lib/Support/CommandLine.cpp @@ -1184,6 +1184,15 @@ if (!RelativeNames) return Error::success(); + + if (InConfigFile) { + if (std::find_if(NewArgv.begin(), NewArgv.end(), + [](const char *Arg) -> bool { + return StringRef("--search-config-dirs") == Arg; + }) != NewArgv.end()) + setSearchAsConfig(true); + } + llvm::StringRef BasePath = llvm::sys::path::parent_path(FName); // If names of nested response files should be resolved relative to including // file, replace the included response file names with their full paths @@ -1207,8 +1216,17 @@ SmallString<128> ResponseFile; ResponseFile.push_back('@'); - ResponseFile.append(BasePath); - llvm::sys::path::append(ResponseFile, FileName); + if (SearchAsConfig) { + std::string FilePath; + if (!findConfigFile(FileName, FilePath)) + return llvm::createStringError( + std::make_error_code(std::errc::no_such_file_or_directory), + "Could not find config file: " + FileName); + ResponseFile.append(FilePath); + } else { + ResponseFile.append(BasePath); + llvm::sys::path::append(ResponseFile, FileName); + } Arg = Saver.save(ResponseFile.str()).data(); } return Error::success(); @@ -1350,15 +1368,48 @@ llvm::vfs::FileSystem *FS) : Saver(S), Tokenizer(T), FS(FS ? FS : vfs::getRealFileSystem().get()) {} -bool ExpansionContext::readConfigFile(StringRef CfgFile, - SmallVectorImpl &Argv) { - SmallString<128> AbsPath; - if (sys::path::is_relative(CfgFile)) { - AbsPath.assign(CfgFile); - if (std::error_code EC = FS->makeAbsolute(AbsPath)) +bool ExpansionContext::findConfigFile(StringRef FileName, + std::string &FilePath) { + SmallString<128> CfgFilePath; + const auto FileExists = [this](SmallString<128> Path) -> bool { + auto Status = FS->status(Path); + return Status && + Status->getType() == llvm::sys::fs::file_type::regular_file; + }; + + // If file name contains directory separator, treat it as a path to + // configuration file. + if (llvm::sys::path::has_parent_path(FileName)) { + CfgFilePath = FileName; + if (llvm::sys::path::is_relative(FileName)) { + if (FS->makeAbsolute(CfgFilePath)) + return false; + } + if (!FileExists(CfgFilePath)) return false; - CfgFile = AbsPath.str(); + FilePath = CfgFilePath.str(); + return true; + } + + // Look for the file in search directories. + for (const StringRef &Dir : SearchDirs) { + if (Dir.empty()) + continue; + CfgFilePath.assign(Dir); + llvm::sys::path::append(CfgFilePath, FileName); + llvm::sys::path::native(CfgFilePath); + if (FileExists(CfgFilePath)) { + FilePath = CfgFilePath.str(); + return true; + } } + + return false; +} + +bool ExpansionContext::readConfigFile(StringRef CfgFile, + SmallVectorImpl &Argv) { + assert(llvm::sys::path::is_absolute(CfgFile)); InConfigFile = true; RelativeNames = true; if (llvm::Error Err = expandResponseFile(CfgFile, Argv)) {