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 @@ -226,7 +226,20 @@ /// jobs. unsigned CheckInputsExist : 1; + /// The directory to which the driver should write out a compilation database + /// fragment for its invocation. + std::string PathToCDBFragmentDir; + public: + /// Returns the directory to which a compilation database fragment should be + /// written out for its invocation, or \c None if the fragments should not be + /// saved. + Optional getPathToCDBFragmentDir() const { + if (PathToCDBFragmentDir.empty()) + return None; + return StringRef(PathToCDBFragmentDir); + } + /// Force clang to emit reproducer for driver invocation. This is enabled /// indirectly by setting FORCE_CLANG_DIAGNOSTICS_CRASH environment variable /// or when using the -gen-reproducer driver flag. @@ -606,6 +619,13 @@ MutableArrayRef Digits); /// Compute the default -fmodule-cache-path. static void getDefaultModuleCachePath(SmallVectorImpl &Result); + + /// Emits a compilation database fragment to the given directory, that + /// contains an entry for the specified filename and arguments. + void + emitCompilationDatabaseFragment(StringRef DestDir, StringRef Executable, + StringRef Filename, + const llvm::opt::ArgStringList &Args) const; }; /// \return True if the last defined optimization level is -Ofast. diff --git a/clang/include/clang/Driver/Job.h b/clang/include/clang/Driver/Job.h --- a/clang/include/clang/Driver/Job.h +++ b/clang/include/clang/Driver/Job.h @@ -125,6 +125,10 @@ const llvm::opt::ArgStringList &getArguments() const { return Arguments; } + const llvm::opt::ArgStringList &getInputFilenames() const { + return InputFilenames; + } + /// Print a command argument, and optionally quote it. static void printArg(llvm::raw_ostream &OS, StringRef Arg, bool Quote); 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 @@ -280,6 +280,8 @@ Flags<[CC1Option]>; def gen_reproducer: Flag<["-"], "gen-reproducer">, InternalDebugOpt, HelpText<"Auto-generates preprocessed source files and a reproduction script">; +def gen_cdb_fragment_path: Separate<["-"], "gen-cdb-fragment-path">, InternalDebugOpt, + HelpText<"Emit a compilation database fragment to the specified directory">; def _migrate : Flag<["--"], "migrate">, Flags<[DriverOption]>, HelpText<"Run the migrator">; diff --git a/clang/lib/Driver/Compilation.cpp b/clang/lib/Driver/Compilation.cpp --- a/clang/lib/Driver/Compilation.cpp +++ b/clang/lib/Driver/Compilation.cpp @@ -176,6 +176,14 @@ C.Print(*OS, "\n", /*Quote=*/getDriver().CCPrintOptions); } + if (auto CDBOutputDir = getDriver().getPathToCDBFragmentDir()) { + if (isa(C.getSource()) || + isa(C.getSource())) { + for (const auto &F : C.getInputFilenames()) + getDriver().emitCompilationDatabaseFragment( + *CDBOutputDir, C.getExecutable(), F, C.getArguments()); + } + } std::string Error; bool ExecutionFailed; 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 @@ -1040,6 +1040,8 @@ GenReproducer = Args.hasFlag(options::OPT_gen_reproducer, options::OPT_fno_crash_diagnostics, !!::getenv("FORCE_CLANG_DIAGNOSTICS_CRASH")); + if (const Arg *A = Args.getLastArg(options::OPT_gen_cdb_fragment_path)) + PathToCDBFragmentDir = A->getValue(); // FIXME: TargetTriple is used by the target-prefixed calls to as/ld // and getToolChain is const. if (IsCLMode()) { @@ -4862,3 +4864,46 @@ bool clang::driver::isOptimizationLevelFast(const ArgList &Args) { return Args.hasFlag(options::OPT_Ofast, options::OPT_O_Group, false); } + +void Driver::emitCompilationDatabaseFragment(StringRef DestDir, + StringRef Executable, + StringRef Filename, + const ArgStringList &Args) const { + auto CWD = getVFS().getCurrentWorkingDirectory(); + if (!CWD) + return; + + SmallString<256> Path = DestDir; + getVFS().makeAbsolute(Path); + auto Err = llvm::sys::fs::create_directory(Path, /*IgnoreExisting=*/true); + if (Err) + return; + + llvm::sys::path::append(Path, Twine(llvm::sys::path::filename(Filename)) + + ".%%%%.json"); + int FD; + SmallString<256> TempPath; + Err = llvm::sys::fs::createUniqueFile(Path, FD, TempPath); + if (Err) + return; + + llvm::raw_fd_ostream OS(FD, /*ShouldClose=*/true); + OS << "[{\n"; + OS << R"("directory":")" << *CWD << "\",\n"; + OS << R"("command":")"; + auto EmitCommandLineFragment = [&OS](StringRef Arg) { + for (char C : Arg) { + if (C == ' ' || C == '\'' || C == '"' || C == '\\') + OS << '\\'; + OS << C; + } + }; + EmitCommandLineFragment(Executable); + for (const auto &Arg : Args) { + OS << ' '; + EmitCommandLineFragment(Arg); + } + OS << "\",\n"; + OS << R"("file":")" << Filename << "\"\n"; + OS << "}]"; +} diff --git a/clang/test/Driver/gen-cdb-fragment.c b/clang/test/Driver/gen-cdb-fragment.c new file mode 100644 --- /dev/null +++ b/clang/test/Driver/gen-cdb-fragment.c @@ -0,0 +1,18 @@ +// REQUIRES: x86-registered-target +// RUN: rm -rf %t.cdb +// RUN: %clang -target x86_64-apple-macos10.15 -c %s -o - -gen-cdb-fragment-path %t.cdb +// RUN: ls %t.cdb | FileCheck --check-prefix=CHECK-LS %s +// CHECK-LS: gen-cdb-fragment.c.{{.*}}.json + +// RUN: cat %t.cdb/*.json | FileCheck --check-prefix=CHECK %s +// CHECK: [{ +// CHECK-NEXT: "directory":"{{.*}}", +// CHECK-NEXT: "command":"{{.*}} -cc1 -triple x86_64-apple-macosx10.15.0 {{.*}}", +// CHECK-NEXT: "file":"{{.*}}gen-cdb-fragment.c" +// CHECK: }] + +// RUN: rm -rf %t.cdb +// RUN: mkdir %t.cdb +// RUN: ls %t.cdb | not FileCheck --check-prefix=CHECK-LS %s +// RUN: %clang -target x86_64-apple-macos10.15 -S %s -o - -gen-cdb-fragment-path %t.cdb +// RUN: ls %t.cdb | FileCheck --check-prefix=CHECK-LS %s