From 591fdcde921334b2d502779006d7c168307a2106 Mon Sep 17 00:00:00 2001 From: Chuanqi Xu Date: Fri, 10 Feb 2023 10:40:17 +0800 Subject: [PATCH] [C++20] [Modules] [ClangScanDeps] Allow clang-scan-deps to without specified compilation database in P1689 (3/4) In a private chat with @ben.boeckel , we get in consensus it would be great for cmake if the invocation of clang-scan-deps can get rid of compilation database. Due to the compilation database can't do very well for the files which are not existed yet. @ben.boeckel may have more context to add here. This patch should be innocent for others usages. Reviewed By: jansvoboda11 Differential Revision: https://reviews.llvm.org/D137534 --- clang/test/ClangScanDeps/P1689.cppm | 121 ++++++++++++++++++++++++++ clang/tools/clang-scan-deps/ClangScanDeps.cpp | 113 ++++++++++++++++++++++-- 2 files changed, 226 insertions(+), 8 deletions(-) diff --git a/clang/test/ClangScanDeps/P1689.cppm b/clang/test/ClangScanDeps/P1689.cppm index c539f8f..22d7721 100644 --- a/clang/test/ClangScanDeps/P1689.cppm +++ b/clang/test/ClangScanDeps/P1689.cppm @@ -5,6 +5,26 @@ // RUN: sed "s|DIR|%/t|g" %t/P1689.json.in > %t/P1689.json // RUN: clang-scan-deps -compilation-database %t/P1689.json -format=p1689 | FileCheck %t/Checks.cpp -DPREFIX=%/t // RUN: clang-scan-deps --mode=preprocess-dependency-directives -compilation-database %t/P1689.json -format=p1689 | FileCheck %t/Checks.cpp -DPREFIX=%/t +// +// Check the separated dependency format. This is required by CMake for the case +// that we have non-exist files in a fresh build and potentially out-of-date after that. +// So the build system need to wrtie a compilation database just for scanning purposes, +// which is not so good. So here is the per file mode for P1689. +// RUN: clang-scan-deps -format=p1689 \ +// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/M.cppm -o %t/M.o \ +// RUN: | FileCheck %t/M.cppm -DPREFIX=%/t +// RUN: clang-scan-deps -format=p1689 \ +// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/Impl.cpp -o %t/Impl.o \ +// RUN: | FileCheck %t/Impl.cpp -DPREFIX=%/t +// RUN: clang-scan-deps -format=p1689 \ +// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/impl_part.cppm -o %t/impl_part.o \ +// RUN: | FileCheck %t/impl_part.cppm -DPREFIX=%/t +// RUN: clang-scan-deps -format=p1689 \ +// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/interface_part.cppm -o %t/interface_part.o \ +// RUN: | FileCheck %t/interface_part.cppm -DPREFIX=%/t +// RUN: clang-scan-deps -format=p1689 \ +// RUN: -- %clang++ -std=c++20 -c -fprebuilt-module-path=%t %t/User.cpp -o %t/User.o \ +// RUN: | FileCheck %t/User.cpp -DPREFIX=%/t //--- P1689.json.in [ @@ -47,6 +67,31 @@ export import :interface_part; import :impl_part; export void Hello(); +// CHECK: { +// CHECK-NEXT: "revision": 0, +// CHECK-NEXT: "rules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "primary-output": "[[PREFIX]]/M.o", +// CHECK-NEXT: "provides": [ +// CHECK-NEXT: { +// CHECK-NEXT: "is-interface": true, +// CHECK-NEXT: "logical-name": "M", +// CHECK-NEXT: "source-path": "[[PREFIX]]/M.cppm" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "requires": [ +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "M:interface_part" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "M:impl_part" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "version": 1 +// CHECK-NEXT: } + //--- Impl.cpp module; #include "header.mock" @@ -55,6 +100,21 @@ void Hello() { std::cout << "Hello "; } +// CHECK: { +// CHECK-NEXT: "revision": 0, +// CHECK-NEXT: "rules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "primary-output": "[[PREFIX]]/Impl.o", +// CHECK-NEXT: "requires": [ +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "M" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "version": 1 +// CHECK-NEXT: } + //--- impl_part.cppm module; #include "header.mock" @@ -66,10 +126,53 @@ void World() { std::cout << W << std::endl; } +// CHECK: { +// CHECK-NEXT: "revision": 0, +// CHECK-NEXT: "rules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "primary-output": "[[PREFIX]]/impl_part.o", +// CHECK-NEXT: "provides": [ +// CHECK-NEXT: { +// CHECK-NEXT: "is-interface": false, +// CHECK-NEXT: "logical-name": "M:impl_part", +// CHECK-NEXT: "source-path": "[[PREFIX]]/impl_part.cppm" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "requires": [ +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "M:interface_part" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "version": 1 +// CHECK-NEXT: } + +// CHECK-MAKE: [[PREFIX]]/impl_part.o.ddi: +// CHECK-MAKE: [[PREFIX]]/impl_part.cppm +// CHECK-MAKE: [[PREFIX]]/header.mock + //--- interface_part.cppm export module M:interface_part; export void World(); +// CHECK: { +// CHECK-NEXT: "revision": 0, +// CHECK-NEXT: "rules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "primary-output": "[[PREFIX]]/interface_part.o", +// CHECK-NEXT: "provides": [ +// CHECK-NEXT: { +// CHECK-NEXT: "is-interface": true, +// CHECK-NEXT: "logical-name": "M:interface_part", +// CHECK-NEXT: "source-path": "[[PREFIX]]/interface_part.cppm" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "version": 1 +// CHECK-NEXT: } + //--- User.cpp import M; import third_party_module; @@ -79,6 +182,24 @@ int main() { return 0; } +// CHECK: { +// CHECK-NEXT: "revision": 0, +// CHECK-NEXT: "rules": [ +// CHECK-NEXT: { +// CHECK-NEXT: "primary-output": "[[PREFIX]]/User.o", +// CHECK-NEXT: "requires": [ +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "M" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "logical-name": "third_party_module" +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "version": 1 +// CHECK-NEXT: } + //--- Checks.cpp // CHECK: { // CHECK-NEXT: "revision": 0, diff --git a/clang/tools/clang-scan-deps/ClangScanDeps.cpp b/clang/tools/clang-scan-deps/ClangScanDeps.cpp index 49d9648..b220465 100644 --- a/clang/tools/clang-scan-deps/ClangScanDeps.cpp +++ b/clang/tools/clang-scan-deps/ClangScanDeps.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Driver/Compilation.h" #include "clang/Driver/Driver.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/TextDiagnosticPrinter.h" @@ -18,6 +19,7 @@ #include "llvm/ADT/Twine.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileUtilities.h" +#include "llvm/Support/Host.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Program.h" @@ -169,9 +171,14 @@ llvm::cl::opt llvm::cl::opt CompilationDB("compilation-database", - llvm::cl::desc("Compilation database"), llvm::cl::Required, + llvm::cl::desc("Compilation database"), llvm::cl::Optional, llvm::cl::cat(DependencyScannerCategory)); +llvm::cl::opt P1689TargettedCommand( + llvm::cl::Positional, llvm::cl::ZeroOrMore, + llvm::cl::desc("The command line flags for the target of which " + "the dependencies are to be computed.")); + llvm::cl::opt ModuleName( "module-name", llvm::cl::Optional, llvm::cl::desc("the module of which the dependencies are to be computed"), @@ -583,19 +590,109 @@ static std::string getModuleCachePath(ArrayRef Args) { return std::string(Path); } -int main(int argc, const char **argv) { +// getCompilationDataBase - If -compilation-database is set, load the +// compilation database from the specified file. Otherwise if the we're +// generating P1689 format, trying to generate the compilation database +// form specified command line after the positional parameter "--". +static std::unique_ptr +getCompilationDataBase(int argc, const char **argv, std::string &ErrorMessage) { llvm::InitLLVM X(argc, argv); llvm::cl::HideUnrelatedOptions(DependencyScannerCategory); if (!llvm::cl::ParseCommandLineOptions(argc, argv)) - return 1; + return nullptr; + + if (!CompilationDB.empty()) + return tooling::JSONCompilationDatabase::loadFromFile( + CompilationDB, ErrorMessage, + tooling::JSONCommandLineSyntax::AutoDetect); + + if (Format != ScanningOutputFormat::P1689) { + llvm::errs() << "the --compilation-database option: must be specified at " + "least once!"; + return nullptr; + } + + // Trying to get the input file, the output file and the command line options + // from the positional parameter "--". + const char **DoubleDash = std::find(argv, argv + argc, StringRef("--")); + if (DoubleDash == argv + argc) { + llvm::errs() << "The command line arguments is required after '--' in " + "P1689 per file mode."; + return nullptr; + } + std::vector CommandLine(DoubleDash + 1, argv + argc); + + llvm::IntrusiveRefCntPtr Diags = + CompilerInstance::createDiagnostics(new DiagnosticOptions); + driver::Driver TheDriver(CommandLine[0], llvm::sys::getDefaultTargetTriple(), + *Diags); + std::unique_ptr C( + TheDriver.BuildCompilation(CommandLine)); + if (!C) + return nullptr; + + auto Cmd = C->getJobs().begin(); + auto CI = std::make_unique(); + CompilerInvocation::CreateFromArgs(*CI, Cmd->getArguments(), *Diags, + CommandLine[0]); + if (!CI) + return nullptr; + + FrontendOptions &FEOpts = CI->getFrontendOpts(); + if (FEOpts.Inputs.size() != 1) { + llvm::errs() << "Only one input file is allowed in P1689 per file mode."; + return nullptr; + } + + // There might be multiple jobs for a compilation. Extract the specified + // output filename from the last job. + auto LastCmd = C->getJobs().end(); + LastCmd--; + if (LastCmd->getOutputFilenames().size() != 1) { + llvm::errs() << "The command line should provide exactly one output file " + "in P1689 per file mode.\n"; + } + StringRef OutputFile = LastCmd->getOutputFilenames().front(); + + class InplaceCompilationDatabase : public tooling::CompilationDatabase { + public: + InplaceCompilationDatabase(StringRef InputFile, StringRef OutputFile, + ArrayRef CommandLine) + : Command(".", InputFile, {}, OutputFile) { + for (auto *C : CommandLine) + Command.CommandLine.push_back(C); + } + std::vector + getCompileCommands(StringRef FilePath) const override { + if (FilePath != Command.Filename) + return {}; + return {Command}; + } + + std::vector getAllFiles() const override { + return {Command.Filename}; + } + + std::vector + getAllCompileCommands() const override { + return {Command}; + } + + private: + tooling::CompileCommand Command; + }; + + return std::make_unique( + FEOpts.Inputs[0].getFile(), OutputFile, CommandLine); +} + +int main(int argc, const char **argv) { std::string ErrorMessage; - std::unique_ptr Compilations = - tooling::JSONCompilationDatabase::loadFromFile( - CompilationDB, ErrorMessage, - tooling::JSONCommandLineSyntax::AutoDetect); + std::unique_ptr Compilations = + getCompilationDataBase(argc, argv, ErrorMessage); if (!Compilations) { - llvm::errs() << "error: " << ErrorMessage << "\n"; + llvm::errs() << ErrorMessage << "\n"; return 1; } -- 2.7.4