Skip to content

Commit

Permalink
Recommit [C++20] [Modules] [ClangScanDeps] Allow clang-scan-deps to w…
Browse files Browse the repository at this point in the history
…ithout 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
  • Loading branch information
ChuanqiXu9 committed Feb 13, 2023
1 parent eb70b38 commit 212d905
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 8 deletions.
121 changes: 121 additions & 0 deletions clang/test/ClangScanDeps/P1689.cppm
Expand Up @@ -8,6 +8,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
[
Expand Down Expand Up @@ -50,6 +70,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"
Expand All @@ -58,6 +103,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"
Expand All @@ -69,10 +129,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;
Expand All @@ -82,6 +185,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,
Expand Down
112 changes: 104 additions & 8 deletions clang/tools/clang-scan-deps/ClangScanDeps.cpp
Expand Up @@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//

#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
Expand Down Expand Up @@ -170,9 +171,14 @@ llvm::cl::opt<unsigned>

llvm::cl::opt<std::string>
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<std::string> 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<std::string> ModuleName(
"module-name", llvm::cl::Optional,
llvm::cl::desc("the module of which the dependencies are to be computed"),
Expand Down Expand Up @@ -588,19 +594,109 @@ static std::string getModuleCachePath(ArrayRef<std::string> 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<tooling::CompilationDatabase>
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<const char *> CommandLine(DoubleDash + 1, argv + argc);

llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
CompilerInstance::createDiagnostics(new DiagnosticOptions);
driver::Driver TheDriver(CommandLine[0], llvm::sys::getDefaultTargetTriple(),
*Diags);
std::unique_ptr<driver::Compilation> C(
TheDriver.BuildCompilation(CommandLine));
if (!C)
return nullptr;

auto Cmd = C->getJobs().begin();
auto CI = std::make_unique<CompilerInvocation>();
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<const char *> CommandLine)
: Command(".", InputFile, {}, OutputFile) {
for (auto *C : CommandLine)
Command.CommandLine.push_back(C);
}

std::vector<tooling::CompileCommand>
getCompileCommands(StringRef FilePath) const override {
if (FilePath != Command.Filename)
return {};
return {Command};
}

std::vector<std::string> getAllFiles() const override {
return {Command.Filename};
}

std::vector<tooling::CompileCommand>
getAllCompileCommands() const override {
return {Command};
}

private:
tooling::CompileCommand Command;
};

return std::make_unique<InplaceCompilationDatabase>(
FEOpts.Inputs[0].getFile(), OutputFile, CommandLine);
}

int main(int argc, const char **argv) {
std::string ErrorMessage;
std::unique_ptr<tooling::JSONCompilationDatabase> Compilations =
tooling::JSONCompilationDatabase::loadFromFile(
CompilationDB, ErrorMessage,
tooling::JSONCommandLineSyntax::AutoDetect);
std::unique_ptr<tooling::CompilationDatabase> Compilations =
getCompilationDataBase(argc, argv, ErrorMessage);
if (!Compilations) {
llvm::errs() << "error: " << ErrorMessage << "\n";
llvm::errs() << ErrorMessage << "\n";
return 1;
}

Expand Down

0 comments on commit 212d905

Please sign in to comment.