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/ToolChains/Clang.h b/clang/lib/Driver/ToolChains/Clang.h --- a/clang/lib/Driver/ToolChains/Clang.h +++ b/clang/lib/Driver/ToolChains/Clang.h @@ -95,6 +95,10 @@ const InputInfo &Output, const InputInfo &Input, const llvm::opt::ArgList &Args) const; + void DumpCompilationDatabaseFragmentToDir( + StringRef Dir, Compilation &C, StringRef Target, const InputInfo &Output, + const InputInfo &Input, const llvm::opt::ArgList &Args) const; + public: Clang(const ToolChain &TC); ~Clang() override; diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -2013,13 +2013,14 @@ CompilationDatabase = std::move(File); } auto &CDB = *CompilationDatabase; - SmallString<128> Buf; - if (llvm::sys::fs::current_path(Buf)) - Buf = "."; - CDB << "{ \"directory\": \"" << escape(Buf) << "\""; + auto CWD = D.getVFS().getCurrentWorkingDirectory(); + if (!CWD) + CWD = "."; + CDB << "{ \"directory\": \"" << escape(*CWD) << "\""; CDB << ", \"file\": \"" << escape(Input.getFilename()) << "\""; CDB << ", \"output\": \"" << escape(Output.getFilename()) << "\""; CDB << ", \"arguments\": [\"" << escape(D.ClangExecutable) << "\""; + SmallString<128> Buf; Buf = "-x"; Buf += types::getTypeName(Input.getType()); CDB << ", \"" << escape(Buf) << "\""; @@ -2037,6 +2038,8 @@ // Skip writing dependency output and the compilation database itself. if (O.getGroup().isValid() && O.getGroup().getID() == options::OPT_M_Group) continue; + if (O.getID() == options::OPT_gen_cdb_fragment_path) + continue; // Skip inputs. if (O.getKind() == Option::InputClass) continue; @@ -2051,6 +2054,40 @@ CDB << ", \"" << escape(Buf) << "\"]},\n"; } +void Clang::DumpCompilationDatabaseFragmentToDir( + StringRef Dir, Compilation &C, StringRef Target, const InputInfo &Output, + const InputInfo &Input, const llvm::opt::ArgList &Args) const { + // If this is a dry run, do not create the compilation database file. + if (C.getArgs().hasArg(options::OPT__HASH_HASH_HASH)) + return; + + if (CompilationDatabase) + DumpCompilationDatabase(C, "", Target, Output, Input, Args); + + SmallString<256> Path = Dir; + const auto &Driver = C.getDriver(); + Driver.getVFS().makeAbsolute(Path); + auto Err = llvm::sys::fs::create_directory(Path, /*IgnoreExisting=*/true); + if (Err) { + Driver.Diag(diag::err_drv_compilationdatabase) << Dir << Err.message(); + return; + } + + llvm::sys::path::append( + Path, + Twine(llvm::sys::path::filename(Input.getFilename())) + ".%%%%.json"); + int FD; + SmallString<256> TempPath; + Err = llvm::sys::fs::createUniqueFile(Path, FD, TempPath); + if (Err) { + Driver.Diag(diag::err_drv_compilationdatabase) << Path << Err.message(); + return; + } + CompilationDatabase = + std::make_unique(FD, /*shouldClose=*/true); + DumpCompilationDatabase(C, "", Target, Output, Input, Args); +} + static void CollectArgsForIntegratedAssembler(Compilation &C, const ArgList &Args, ArgStringList &CmdArgs, @@ -3495,6 +3532,11 @@ if (const Arg *MJ = Args.getLastArg(options::OPT_MJ)) { DumpCompilationDatabase(C, MJ->getValue(), TripleStr, Output, Input, Args); Args.ClaimAllArgs(options::OPT_MJ); + } else if (const Arg *GenCDBFragment = + Args.getLastArg(options::OPT_gen_cdb_fragment_path)) { + DumpCompilationDatabaseFragmentToDir(GenCDBFragment->getValue(), C, + TripleStr, Output, Input, Args); + Args.ClaimAllArgs(options::OPT_gen_cdb_fragment_path); } if (IsCuda || IsHIP) { 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,37 @@ +// 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: { "directory": "{{.*}}", "file": "{{.*}}gen-cdb-fragment.c", "output": "-", "arguments": [{{.*}}, "--target=x86_64-apple-macos10.15"{{.*}}]}, +// RUN: cat %t.cdb/*.json | FileCheck --check-prefix=CHECK-FLAG %s +// CHECK-FLAG-NOT: -gen-cdb-fragment-path + +// 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 + +// Empty path is equivalent to '.' +// RUN: rm -rf %t.cdb +// RUN: mkdir %t.cdb +// RUN: %clang -target x86_64-apple-macos10.15 -working-directory %t.cdb -c %s -o - -gen-cdb-fragment-path "" +// RUN: ls %t.cdb | FileCheck --check-prefix=CHECK-LS %s +// RUN: cat %t.cdb/*.json | FileCheck --check-prefix=CHECK-CWD %s +// CHECK-CWD: "directory": "{{.*}}.cdb" + +// -### does not emit the CDB fragment +// RUN: rm -rf %t.cdb +// RUN: mkdir %t.cdb +// RUN: %clang -target x86_64-apple-macos10.15 -S %s -o - -gen-cdb-fragment-path %t.cdb -### +// RUN: ls %t.cdb | not FileCheck --check-prefix=CHECK-LS %s + +// -MJ is preferred over -gen-cdb-fragment-path +// RUN: rm -rf %t.cdb +// RUN: mkdir %t.cdb +// RUN: %clang -target x86_64-apple-macos10.15 -S %s -o - -gen-cdb-fragment-path %t.cdb -MJ %t.out +// RUN: ls %t.cdb | not FileCheck --check-prefix=CHECK-LS %s +// RUN: FileCheck %s < %t.out