diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index b8c7c6e8d6909..596fd4a37d83f 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -587,6 +587,22 @@ def remark_found_cxx20_module_usage : Remark< def remark_performing_driver_managed_module_build : Remark< "performing driver managed module build">, InGroup; +def remark_std_module_manifest_path : Remark< + "using std modules manifest: '%0'">, InGroup; +def err_failed_parse_modules_manifest_json: Error< + "failed to parse the std modules manifest">; +def err_failed_depdendency_scan : Error< + "failed to perform dependency scan">; +def remark_failed_dependency_scan_for_input : Remark< + "dependency scan failed for source input '%0'">, + InGroup; +def err_mod_graph_named_module_redefinition : Error< + "duplicate definitions of C++20 named module '%0' in '%1' and '%2'">; +def err_building_depdendency_graph : Error< + "failed to construct the module dependency graph">; +def remark_printing_module_graph : Remark< + "printing module dependency graph">, + InGroup; def warn_drv_delayed_template_parsing_after_cxx20 : Warning< "-fdelayed-template-parsing is deprecated after C++20">, diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h index b9b187ada8add..ba0168385c69b 100644 --- a/clang/include/clang/Driver/Driver.h +++ b/clang/include/clang/Driver/Driver.h @@ -135,6 +135,10 @@ class Driver { /// interpretation. bool ModulesModeCXX20; + /// Set if the dirver should plan the compilation after scanning module + /// dependencies, using the scan results (set by -f(no-)modules-driver.) + bool DriverManagedModulesBuild; + /// LTO mode selected via -f(no-)?lto(=.*)? options. LTOKind LTOMode; @@ -512,9 +516,6 @@ class Driver { /// BuildActions - Construct the list of actions to perform for the /// given arguments, which are only done for a single architecture. - /// If the compilation is an explicit module build, delegates to - /// BuildDriverManagedModuleBuildActions. Otherwise, BuildDefaultActions is - /// used. /// /// \param C - The compilation that is being built. /// \param Args - The input arguments. @@ -799,35 +800,6 @@ class Driver { /// compilation based on which -f(no-)?lto(=.*)? option occurs last. void setLTOMode(const llvm::opt::ArgList &Args); - /// BuildDefaultActions - Constructs the list of actions to perform - /// for the provided arguments, which are only done for a single architecture. - /// - /// \param C - The compilation that is being built. - /// \param Args - The input arguments. - /// \param Actions - The list to store the resulting actions onto. - void BuildDefaultActions(Compilation &C, llvm::opt::DerivedArgList &Args, - const InputList &Inputs, ActionList &Actions) const; - - /// BuildDriverManagedModuleBuildActions - Performs a dependency - /// scan and constructs the list of actions to perform for dependency order - /// and the provided arguments. This is only done for a single a architecture. - /// - /// \param C - The compilation that is being built. - /// \param Args - The input arguments. - /// \param Actions - The list to store the resulting actions onto. - void BuildDriverManagedModuleBuildActions(Compilation &C, - llvm::opt::DerivedArgList &Args, - const InputList &Inputs, - ActionList &Actions) const; - - /// Scans the leading lines of the C++ source inputs to detect C++20 module - /// usage. - /// - /// \returns True if module usage is detected, false otherwise, or an error on - /// read failure. - llvm::ErrorOr - ScanInputsForCXX20ModulesUsage(const InputList &Inputs) const; - /// Retrieves a ToolChain for a particular \p Target triple. /// /// Will cache ToolChains for the life of the driver object, and create them diff --git a/clang/include/clang/Driver/Job.h b/clang/include/clang/Driver/Job.h index 561866197b780..c71ad8538e6c2 100644 --- a/clang/include/clang/Driver/Job.h +++ b/clang/include/clang/Driver/Job.h @@ -221,6 +221,8 @@ class Command { const char *getExecutable() const { return Executable; } + llvm::opt::ArgStringList &getArguments() { return Arguments; } + const llvm::opt::ArgStringList &getArguments() const { return Arguments; } const std::vector &getInputInfos() const { return InputInfoList; } @@ -277,6 +279,7 @@ class JobList { /// Clear the job list. void clear(); + list_type &getJobs() { return Jobs; } const list_type &getJobs() const { return Jobs; } bool empty() const { return Jobs.empty(); } diff --git a/clang/include/clang/Driver/ModulesDriver.h b/clang/include/clang/Driver/ModulesDriver.h new file mode 100644 index 0000000000000..0093b6c140bbf --- /dev/null +++ b/clang/include/clang/Driver/ModulesDriver.h @@ -0,0 +1,60 @@ +//===- DependencyScanner.h - Module dependency discovery --------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file defines the module dependency graph and dependency-scanning +/// functionality. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_DRIVER_DEPENDENCYSCANNER_H +#define LLVM_CLANG_DRIVER_DEPENDENCYSCANNER_H + +#include "clang/Driver/Types.h" + +namespace llvm { +namespace vfs { +class FileSystem; +} // namespace vfs +} // namespace llvm + +namespace clang { +class DiagnosticsEngine; +namespace driver { +class Compilation; +} // namespace driver +} // namespace clang + +namespace clang::driver::modules { + +using InputTy = std::pair; + +using InputList = llvm::SmallVector; + +/// Checks whether the -fmodules-driver feature should be implicitly enabled. +/// +/// When -fmodules-driver is no longer experimental, it should be enabled by +/// default iff both conditions are met: +/// (1) there are two or more C++ source inputs; and +/// (2) at least one input uses C++20 named modules. +bool shouldEnableModulesDriver(const InputList &Inputs, + llvm::vfs::FileSystem &VFS, + DiagnosticsEngine &Diags); + +/// Appends the std and std.compat module inputs. +bool ensureNamedModuleStdLibraryInputs(clang::driver::Compilation &C, + InputList &Inputs); + +/// Modifies the compilations JobList to support Clang and C++20 named module +/// imports between source files, importing Standard library modules +bool performDriverModuleBuild(clang::driver::Compilation &C, + clang::DiagnosticsEngine &Diags); + +} // namespace clang::driver::modules + +#endif diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 82e8212bee12d..ed097130ac54f 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -3302,6 +3302,12 @@ def fmodules_driver : Flag<["-"], "fmodules-driver">, def fno_modules_driver : Flag<["-"], "fno-modules-driver">, Group, Visibility<[ClangOption]>, HelpText<"Disable support for driver managed module builds (experimental)">; +def fimplicit_import_std : Flag<["-"], "fimplicit-import-std">, + Group, Visibility<[ClangOption]>, + HelpText<"Implicitly add the std module when discovered in driver managed module builds">; +def fno_implicit_import_std : Flag<["-"], "fno-implicit-import-std">, + Group, Visibility<[ClangOption]>, + HelpText<"Don't implicitly add the std module when discovered in driver managed module builds">; def experimental_modules_reduced_bmi : Flag<["-"], "fexperimental-modules-reduced-bmi">, Group, Visibility<[ClangOption, CC1Option]>, Alias; diff --git a/clang/lib/Driver/CMakeLists.txt b/clang/lib/Driver/CMakeLists.txt index 7c4f70b966c48..43f11fe623893 100644 --- a/clang/lib/Driver/CMakeLists.txt +++ b/clang/lib/Driver/CMakeLists.txt @@ -21,6 +21,7 @@ add_clang_library(clangDriver Driver.cpp DriverOptions.cpp Job.cpp + ModulesDriver.cpp Multilib.cpp MultilibBuilder.cpp OffloadBundler.cpp @@ -98,6 +99,7 @@ add_clang_library(clangDriver LINK_LIBS clangBasic + clangDependencyScanning clangLex ${system_libs} ) diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index f110dbab3e5a5..89ee1884393fa 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -60,6 +60,7 @@ #include "clang/Driver/Compilation.h" #include "clang/Driver/InputInfo.h" #include "clang/Driver/Job.h" +#include "clang/Driver/ModulesDriver.h" #include "clang/Driver/Options.h" #include "clang/Driver/Phases.h" #include "clang/Driver/SanitizerArgs.h" @@ -1826,6 +1827,18 @@ Compilation *Driver::BuildCompilation(ArrayRef ArgList) { } } + if (C->getArgs().hasFlag(options::OPT_fmodules_driver, + options::OPT_fno_modules_driver, false)) { + // The detection logic for this is kept here only for diagnostics until + // is enabled by default. + modules::shouldEnableModulesDriver(Inputs, getVFS(), Diags); + Diags.Report(diag::remark_performing_driver_managed_module_build); + if (C->getArgs().hasFlag(options::OPT_fimplicit_import_std, + options::OPT_fno_implicit_import_std, true)) { + modules::ensureNamedModuleStdLibraryInputs(*C, Inputs); + } + } + // Populate the tool chains for the offloading devices, if any. CreateOffloadingDeviceToolChains(*C, Inputs); @@ -4189,10 +4202,20 @@ void Driver::handleArguments(Compilation &C, DerivedArgList &Args, YcArg = nullptr; } - if (Args.hasArgNoClaim(options::OPT_fmodules_driver)) - // TODO: Check against all incompatible -fmodules-driver arguments - if (!ModulesModeCXX20 && !Args.hasArgNoClaim(options::OPT_fmodules)) - Args.eraseArg(options::OPT_fmodules_driver); + if (Args.hasArgNoClaim(options::OPT_fmodules_driver)) { + // HACK: This should be only added for the Standard library jobs, explicitly + // created by the modules driver. + MakeInputArg(Args, getOpts(), + Args.MakeArgString("-Wno-reserved-module-identifier")); + if (Args.hasArg(options::OPT_fmodules)) { + Args.eraseArg(options::OPT_fmodules); + Arg *Arg = Args.MakeSeparateArg( + nullptr, getOpts().getOption(options::OPT_fimplicit_modules), + Args.MakeArgString(("-fimplicit-modules"))); + Arg->claim(); + Args.append(Arg); + } + } Arg *FinalPhaseArg; phases::ID FinalPhase = getFinalPhase(Args, &FinalPhaseArg); @@ -4320,33 +4343,6 @@ void Driver::handleArguments(Compilation &C, DerivedArgList &Args, } } -static bool hasCXXModuleInputType(const Driver::InputList &Inputs) { - const auto IsTypeCXXModule = [](const auto &Input) -> bool { - const auto TypeID = Input.first; - return (TypeID == types::TY_CXXModule); - }; - return llvm::any_of(Inputs, IsTypeCXXModule); -} - -llvm::ErrorOr -Driver::ScanInputsForCXX20ModulesUsage(const InputList &Inputs) const { - const auto CXXInputs = llvm::make_filter_range( - Inputs, [](const auto &Input) { return types::isCXX(Input.first); }); - for (const auto &Input : CXXInputs) { - StringRef Filename = Input.second->getSpelling(); - auto ErrOrBuffer = VFS->getBufferForFile(Filename); - if (!ErrOrBuffer) - return ErrOrBuffer.getError(); - const auto Buffer = std::move(*ErrOrBuffer); - - if (scanInputForCXX20ModulesUsage(Buffer->getBuffer())) { - Diags.Report(diag::remark_found_cxx20_module_usage) << Filename; - return true; - } - } - return false; -} - void Driver::BuildActions(Compilation &C, DerivedArgList &Args, const InputList &Inputs, ActionList &Actions) const { llvm::PrettyStackTraceString CrashInfo("Building compilation actions"); @@ -4358,33 +4354,6 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args, handleArguments(C, Args, Inputs, Actions); - if (Args.hasFlag(options::OPT_fmodules_driver, - options::OPT_fno_modules_driver, false)) { - // TODO: Move the logic for implicitly enabling explicit-module-builds out - // of -fmodules-driver once it is no longer experimental. - // Currently, this serves diagnostic purposes only. - bool UsesCXXModules = hasCXXModuleInputType(Inputs); - if (!UsesCXXModules) { - const auto ErrOrScanResult = ScanInputsForCXX20ModulesUsage(Inputs); - if (!ErrOrScanResult) { - Diags.Report(diag::err_cannot_open_file) - << ErrOrScanResult.getError().message(); - return; - } - UsesCXXModules = *ErrOrScanResult; - } - if (UsesCXXModules || Args.hasArg(options::OPT_fmodules)) - BuildDriverManagedModuleBuildActions(C, Args, Inputs, Actions); - return; - } - - BuildDefaultActions(C, Args, Inputs, Actions); -} - -void Driver::BuildDefaultActions(Compilation &C, DerivedArgList &Args, - const InputList &Inputs, - ActionList &Actions) const { - bool UseNewOffloadingDriver = C.isOffloadingHostKind(Action::OFK_OpenMP) || C.isOffloadingHostKind(Action::OFK_SYCL) || @@ -4680,12 +4649,6 @@ void Driver::BuildDefaultActions(Compilation &C, DerivedArgList &Args, Args.ClaimAllArgs(options::OPT_cl_ignored_Group); } -void Driver::BuildDriverManagedModuleBuildActions( - Compilation &C, llvm::opt::DerivedArgList &Args, const InputList &Inputs, - ActionList &Actions) const { - Diags.Report(diag::remark_performing_driver_managed_module_build); -} - /// Returns the canonical name for the offloading architecture when using a HIP /// or CUDA architecture. static StringRef getCanonicalArchString(Compilation &C, @@ -5440,6 +5403,12 @@ void Driver::BuildJobs(Compilation &C) const { } } } + if (C.getArgs().hasFlag(options::OPT_fmodules_driver, + options::OPT_fno_modules_driver, false)) { + auto Success = modules::performDriverModuleBuild(C, C.getDriver().Diags); + if (!Success) + return; + } } namespace { diff --git a/clang/lib/Driver/ModulesDriver.cpp b/clang/lib/Driver/ModulesDriver.cpp new file mode 100644 index 0000000000000..07781f63d7ade --- /dev/null +++ b/clang/lib/Driver/ModulesDriver.cpp @@ -0,0 +1,1495 @@ +//===- DependencyScanner.cpp - Module dependency discovery ----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Driver/ModulesDriver.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticDriver.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/InputInfo.h" +#include "clang/Driver/Job.h" +#include "clang/Driver/Tool.h" +#include "clang/Driver/Types.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" +#include "clang/Tooling/DependencyScanning/ModuleDepCollector.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/DepthFirstIterator.h" +#include "llvm/ADT/DirectedGraph.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/TypeSwitch.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/GraphWriter.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/TargetParser/Host.h" +#include +#include +#include +#include +#include +#include + +using namespace clang; +using namespace clang::driver; +using namespace llvm; +using namespace llvm::opt; + +namespace clang::tooling { +namespace deps = dependencies; +} // namespace clang::tooling + +using OwnedJobList = SmallVector, 4>; + +//===----------------------------------------------------------------------===// +// Check: Enable -fmodules-driver implicitly +//===----------------------------------------------------------------------===// + +namespace clang::driver::modules { + +/// Returns true if any input is a `.cppm` file. +static bool hasCXXModuleInputType(const InputList &Inputs) { + const auto IsTypeCXXModule = [](const auto &Input) -> bool { + const auto TypeID = Input.first; + return (TypeID == types::TY_CXXModule); + }; + return llvm::any_of(Inputs, IsTypeCXXModule); +} + +/// Scans the leading lines of the C++ source inputs to detect C++20 module +/// usage. +/// +/// \returns true if module usage is detected, false otherwise, or an error on +/// failure to read the input source. +static llvm::ErrorOr +ScanInputsForCXX20ModulesUsage(const InputList &Inputs, + llvm::vfs::FileSystem &VFS, + DiagnosticsEngine &Diags) { + const auto CXXInputs = llvm::make_filter_range( + Inputs, [](const auto &Input) { return types::isCXX(Input.first); }); + for (const auto &Input : CXXInputs) { + StringRef Filename = Input.second->getSpelling(); + auto ErrOrBuffer = VFS.getBufferForFile(Filename); + if (!ErrOrBuffer) + return ErrOrBuffer.getError(); + const auto Buffer = std::move(*ErrOrBuffer); + + if (scanInputForCXX20ModulesUsage(Buffer->getBuffer())) { + Diags.Report(diag::remark_found_cxx20_module_usage) << Filename; + return true; + } + } + return false; +} + +/// Checks if the -fmodules-driver feature should be implicitly enabled for this +/// compilation. +/// +/// The -fmodules-driver feature should be implicitly enabled iff (1) any input +/// makes used of C++20 named modules; and (2) there are more than two source +/// input files. +/// +/// \returns true if the -fmodules-driver feature should be enabled, false +/// otherwise. +bool shouldEnableModulesDriver(const InputList &Inputs, + llvm::vfs::FileSystem &VFS, + DiagnosticsEngine &Diags) { + if (Inputs.size() < 2) + return false; + + bool UsesCXXModules = hasCXXModuleInputType(Inputs); + if (UsesCXXModules) + return true; + + const auto ErrOrScanResult = + ScanInputsForCXX20ModulesUsage(Inputs, VFS, Diags); + if (!ErrOrScanResult) { + Diags.Report(diag::err_cannot_open_file) + << ErrOrScanResult.getError().message(); + } + return *ErrOrScanResult; +} + +/// Builds the a C++ named module input for \c InputFile and adds it to \c Args. +static void addCXXModuleInput(InputList &Inputs, DerivedArgList &Args, + const OptTable &Opts, StringRef InputFile) { + Arg *A = new Arg(Opts.getOption(options::OPT_INPUT), InputFile, + Args.getBaseArgs().MakeIndex(InputFile), + Args.getBaseArgs().MakeArgString(InputFile)); + Args.AddSynthesizedArg(A); + A->claim(); + Inputs.push_back(std::make_pair(types::TY_CXXModule, A)); +} + +/// Parses the std modules manifest and builds the inputs for the discovered +/// std modules. +/// +/// \returns true if the modules were added, false failure to read/parse the +/// manifest (with diagnostics reported using the drivers DiagnosticEngine). +bool ensureNamedModuleStdLibraryInputs(Compilation &C, InputList &Inputs) { + const auto &Driver = C.getDriver(); + auto &Diags = Driver.getDiags(); + + const auto ManifestPath = + Driver.GetStdModuleManifestPath(C, C.getDefaultToolChain()); + Diags.Report(diag::remark_std_module_manifest_path) << ManifestPath; + if (ManifestPath == "") + return false; + + llvm::SmallString<256> ManifestDir(ManifestPath); + llvm::sys::path::remove_filename(ManifestDir); + + auto MemBufOrErr = llvm::MemoryBuffer::getFile(ManifestPath); + if (!MemBufOrErr) { + Diags.Report(diag::err_cannot_open_file) + << MemBufOrErr.getError().message(); + return false; + } + const auto MemBuf = std::move(*MemBufOrErr); + + auto ParsedJsonOrErr = llvm::json::parse(MemBuf->getBuffer()); + if (!ParsedJsonOrErr) { + Diags.Report(diag::err_failed_parse_modules_manifest_json); + llvm::consumeError(ParsedJsonOrErr.takeError()); + return false; + } + const auto ParsedJson = std::move(*ParsedJsonOrErr); + + const auto *ModulesInfoList = ParsedJson.getAsObject()->getArray("modules"); + if (!ModulesInfoList) + return false; + + const auto Opts = Driver.getOpts(); + auto &Args = C.getArgs(); + for (const auto &Entry : *ModulesInfoList) { + const auto *ModuleInfoObj = Entry.getAsObject(); + if (!ModuleInfoObj) + return false; + + auto IsStdLib = ModuleInfoObj->getBoolean("is-std-library"); + if (!IsStdLib && !*IsStdLib) + continue; + + if (auto SourcePath = ModuleInfoObj->getString("source-path")) { + SmallString<248> AbsSourcePath(ManifestDir); + llvm::sys::path::append(AbsSourcePath, *SourcePath); + addCXXModuleInput(Inputs, Args, Opts, AbsSourcePath); + } + } + + return true; +} + +} // namespace clang::driver::modules + +//===----------------------------------------------------------------------===// +// Dependency Scan Diagnostic Reporting Utilities +//===----------------------------------------------------------------------===// + +namespace { +/// Represents a CharSourceRange within a StandaloneDiagnostic. +struct SourceOffsetRange { + SourceOffsetRange(CharSourceRange Range, const SourceManager &SrcMgr, + const LangOptions &LangOpts); + unsigned Begin = 0; + unsigned End = 0; + bool IsTokenRange = false; +}; + +/// Represents a FixItHint within a StandaloneDiagnostic. +struct StandaloneFixIt { + StandaloneFixIt(const SourceManager &SrcMgr, const LangOptions &LangOpts, + const FixItHint &FixIt); + + SourceOffsetRange RemoveRange; + SourceOffsetRange InsertFromRange; + std::string CodeToInsert; + bool BeforePreviousInsertions = false; +}; + +/// Represents a StoredDiagnostic in a form that can be retained until after its +/// SourceManager has been destroyed. +/// +/// Source locations are stored as a combination of filename and offsets into +/// that file. +/// To report the diagnostic, it must first be translated back into a +/// StoredDiagnostic with a new associated SourceManager. +struct StandaloneDiagnostic { + explicit StandaloneDiagnostic(const StoredDiagnostic &StoredDiag); + + LangOptions LangOpts; + SrcMgr::CharacteristicKind FileKind; + DiagnosticsEngine::Level Level; + unsigned ID = 0; + unsigned FileOffset = 0; + std::string Filename; + std::string Message; + SmallVector Ranges; + SmallVector FixIts; +}; +} // anonymous namespace + +SourceOffsetRange::SourceOffsetRange(CharSourceRange Range, + const SourceManager &SrcMgr, + const LangOptions &LangOpts) + : IsTokenRange(Range.isTokenRange()) { + const auto FileRange = Lexer::makeFileCharRange(Range, SrcMgr, LangOpts); + Begin = SrcMgr.getFileOffset(FileRange.getBegin()); + End = SrcMgr.getFileOffset(FileRange.getEnd()); +} + +StandaloneFixIt::StandaloneFixIt(const SourceManager &SrcMgr, + const LangOptions &LangOpts, + const FixItHint &FixIt) + : RemoveRange(FixIt.RemoveRange, SrcMgr, LangOpts), + InsertFromRange(FixIt.InsertFromRange, SrcMgr, LangOpts), + CodeToInsert(FixIt.CodeToInsert), + BeforePreviousInsertions(FixIt.BeforePreviousInsertions) {} + +/// If a custom working directory is set for \c SrcMgr, returns the absolute +/// path of \c Filename to make it independent. Otherwise, returns the original +/// string. +static std::string canonicalizeFilename(const SourceManager &SrcMgr, + StringRef Filename) { + SmallString<256> Abs(Filename); + if (!sys::path::is_absolute(Abs)) { + if (const auto &CWD = + SrcMgr.getFileManager().getFileSystemOpts().WorkingDir; + !CWD.empty()) + sys::fs::make_absolute(CWD, Abs); + } + return std::string(Abs.str()); +} + +// FIXME: LangOpts is not properly saved because the LangOptions is not +// copyable! +StandaloneDiagnostic::StandaloneDiagnostic(const StoredDiagnostic &StoredDiag) + : Level(StoredDiag.getLevel()), ID(StoredDiag.getID()), + Message(StoredDiag.getMessage()) { + const FullSourceLoc &FullLoc = StoredDiag.getLocation(); + // This is not an invalid diagnostic; invalid SourceLocations are used to + // represent diagnostics without a specific SourceLocation. + if (FullLoc.isInvalid()) + return; + + const auto &SrcMgr = FullLoc.getManager(); + FileKind = SrcMgr.getFileCharacteristic(static_cast(FullLoc)); + const auto FileLoc = SrcMgr.getFileLoc(static_cast(FullLoc)); + FileOffset = SrcMgr.getFileOffset(FileLoc); + const auto PathRef = SrcMgr.getFilename(FileLoc); + assert(!PathRef.empty() && "diagnostic with location has no source file?"); + Filename = canonicalizeFilename(SrcMgr, PathRef); + + Ranges.reserve(StoredDiag.getRanges().size()); + for (const auto &Range : StoredDiag.getRanges()) + Ranges.emplace_back(Range, SrcMgr, LangOpts); + + FixIts.reserve(StoredDiag.getFixIts().size()); + for (const auto &FixIt : StoredDiag.getFixIts()) + FixIts.emplace_back(SrcMgr, LangOpts, FixIt); +} + +/// Translates \c StandaloneDiag into a StoredDiagnostic, associating it with +/// the provided FileManager and SourceManager. +static StoredDiagnostic +translateStandaloneDiag(FileManager &FileMgr, SourceManager &SrcMgr, + StandaloneDiagnostic &&StandaloneDiag) { + const auto FileRef = FileMgr.getOptionalFileRef(StandaloneDiag.Filename); + if (!FileRef) + return StoredDiagnostic(StandaloneDiag.Level, StandaloneDiag.ID, + std::move(StandaloneDiag.Message)); + + const auto FileID = + SrcMgr.getOrCreateFileID(*FileRef, StandaloneDiag.FileKind); + const auto FileLoc = SrcMgr.getLocForStartOfFile(FileID); + assert(FileLoc.isValid() && "StandaloneDiagnostic should only use FilePath " + "for encoding a valid source location."); + const auto DiagLoc = FileLoc.getLocWithOffset(StandaloneDiag.FileOffset); + const FullSourceLoc Loc(DiagLoc, SrcMgr); + + auto ConvertOffsetRange = [&](const SourceOffsetRange &Range) { + return CharSourceRange(SourceRange(FileLoc.getLocWithOffset(Range.Begin), + FileLoc.getLocWithOffset(Range.End)), + Range.IsTokenRange); + }; + + SmallVector TranslatedRanges; + TranslatedRanges.reserve(StandaloneDiag.Ranges.size()); + transform(StandaloneDiag.Ranges, std::back_inserter(TranslatedRanges), + ConvertOffsetRange); + + SmallVector TranslatedFixIts; + TranslatedFixIts.reserve(StandaloneDiag.FixIts.size()); + for (const auto &FixIt : StandaloneDiag.FixIts) { + FixItHint TranslatedFixIt; + TranslatedFixIt.CodeToInsert = std::string(FixIt.CodeToInsert); + TranslatedFixIt.RemoveRange = ConvertOffsetRange(FixIt.RemoveRange); + TranslatedFixIt.InsertFromRange = ConvertOffsetRange(FixIt.InsertFromRange); + TranslatedFixIt.BeforePreviousInsertions = FixIt.BeforePreviousInsertions; + TranslatedFixIts.push_back(std::move(TranslatedFixIt)); + } + + return StoredDiagnostic(StandaloneDiag.Level, StandaloneDiag.ID, + StandaloneDiag.Message, Loc, TranslatedRanges, + TranslatedFixIts); +} + +namespace { +/// RAII utility to report StandaloneDiagnostics through a DiagnosticsEngine. +/// +/// The driver's DiagnosticsEngine usually does not have a SourceManager at this +/// point in building the compilation, in which case the StandaloneDiagReporter +/// supplies its own. +class StandaloneDiagReporter { +public: + explicit StandaloneDiagReporter(DiagnosticsEngine &Diags) : Diags(Diags) { + if (!Diags.hasSourceManager()) { + FileSystemOptions Opts; + Opts.WorkingDir = "."; + OwnedFileMgr = makeIntrusiveRefCnt(std::move(Opts)); + OwnedSrcMgr = makeIntrusiveRefCnt(Diags, *OwnedFileMgr); + } + } + + void Report(StandaloneDiagnostic &&StandaloneDiag) const { + const auto StoredDiag = translateStandaloneDiag( + getFileManager(), getSourceManager(), std::move(StandaloneDiag)); + Diags.getClient()->BeginSourceFile(StandaloneDiag.LangOpts, nullptr); + Diags.Report(StoredDiag); + Diags.getClient()->EndSourceFile(); + } + +private: + DiagnosticsEngine &Diags; + IntrusiveRefCntPtr OwnedFileMgr; + IntrusiveRefCntPtr OwnedSrcMgr; + + FileManager &getFileManager() const { + if (OwnedFileMgr) + return *OwnedFileMgr; + return Diags.getSourceManager().getFileManager(); + } + + SourceManager &getSourceManager() const { + if (OwnedSrcMgr) + return *OwnedSrcMgr; + return Diags.getSourceManager(); + } +}; + +/// Collects diagnostics in a form that can be retained until after their +/// associated SourceManager is destroyed. +class StandaloneDiagCollector : public DiagnosticConsumer { +public: + void BeginSourceFile(const LangOptions &LangOpts, + const Preprocessor *PP = nullptr) override {} + + void HandleDiagnostic(DiagnosticsEngine::Level Level, + const Diagnostic &Info) override { + StoredDiagnostic StoredDiag(Level, Info); + StandaloneDiags.emplace_back(StoredDiag); + DiagnosticConsumer::HandleDiagnostic(Level, Info); + } + + void EndSourceFile() override {} + + SmallVector takeDiags() { + SmallVector Out; + Out.swap(StandaloneDiags); + return Out; + } + +private: + SmallVector StandaloneDiags; +}; +} // anonymous namespace + +//===----------------------------------------------------------------------===// +// Dependency Scan +//===----------------------------------------------------------------------===// + +namespace { +/// A simple dependency action controller that only provides module lookup for +/// Clang modules. +class ModuleLookupActionController + : public tooling::deps::DependencyActionController { +public: + ModuleLookupActionController(StringRef TempDir) : TempDir(TempDir) {} + + std::string + lookupModuleOutput(const tooling::deps::ModuleDeps &MD, + tooling::deps::ModuleOutputKind Kind) override { + if (Kind == tooling::deps::ModuleOutputKind::ModuleFile) + return constructPCMPath(MD.ID); + // Driver command lines which cause this should be handled either in + // Driver::handleArguments and rejected or in + // buildCommandLineForDummyDriver and modified. + llvm::reportFatalInternalError( + "call to lookupModuleOutput with unexpected ModuleOutputKind"); + } + +private: + SmallString<128> TempDir; + + std::string constructPCMPath(const tooling::deps::ModuleID &ID) const { + SmallString<256> ExplicitPCMPath(TempDir); + llvm::sys::path::append(ExplicitPCMPath, Twine(ID.ModuleName) + "-" + + ID.ContextHash + ".pcm"); + return std::string(ExplicitPCMPath); + } +}; +} // namespace + +/// Returns the full -cc1 command line (incl. executable) for a driver command. +static std::vector buildCC1CommandLine(const Command &Cmd) { + const auto &CmdArgList = Cmd.getArguments(); + std::vector Out; + Out.reserve(CmdArgList.size() + 1); + Out.emplace_back(Cmd.getExecutable()); + for (const auto &Arg : CmdArgList) + Out.emplace_back(Arg); + return Out; +} + +/// Scans the full dependencies and Clang module graphs for all user inputs in +/// \c ScanInputCmds. +/// +/// The scan input commands are expected to be ordered as: +/// [ user input jobs..., std module job, std.compat module job ] +/// The user input jobs are always scanned and the \c std and \c std.compat +/// jobs are scanned on demand if any user input imports them. +/// +/// \returns a list of TranslationUnitDeps on succes or an empty list on +/// failure. +static SmallVector +scanDependencies(const ArrayRef> ScanInputCmds, + DiagnosticsEngine &Diags, StringRef TempDir) { + // The last 2 trailing jobs contain the std modules, always in order of first + // std and then std.compat. + // If the user does not import a standard library module, no scan is performed + // for it, and the corresponding job is later removed from the job list. + const auto InputCount = ScanInputCmds.size(); + const auto UserInputCount = InputCount - 2; + + llvm::DefaultThreadPool Pool; + llvm::ThreadPoolTaskGroup UserInputTaskGroup(Pool); + llvm::ThreadPoolTaskGroup StdModulesTaskGroup(Pool); + + enum class SeenStdModulesState : uint8_t { + None = 0, + Std = 1, + StdAndStdCompat = 2 + }; + std::atomic SeenStdModules = SeenStdModulesState::None; + + // Helper to update the required std module imports after scanning a user + // input. + auto UpdateStdImportFlags = + [&](const tooling::deps::TranslationUnitDeps &Deps) { + if (SeenStdModules.load(std::memory_order_relaxed) == + SeenStdModulesState::StdAndStdCompat) + return; + + for (const auto &NamedDep : Deps.NamedModuleDeps) { + if (NamedDep == "std.compat") { + SeenStdModules.store(SeenStdModulesState::StdAndStdCompat, + std::memory_order_relaxed); + return; + } + if (NamedDep == "std") { + // If this fails, the state is already set to Std or + // StdAndStdCompat. + auto Expected = SeenStdModulesState::None; + (void)SeenStdModules.compare_exchange_strong( + Expected, SeenStdModulesState::Std, std::memory_order_relaxed, + std::memory_order_relaxed); + } + } + }; + + std::atomic NextUserInputIdx = 0; + std::atomic NextStdInputIdx = UserInputCount; + + // Helper to get the next index to process for a worker. + // Workers in the StdModulesTaskGroup wait until all user inputs have finished + // scanning, and then scan only the required standard library modules. + auto GetNextInputIndex = + [&](bool IsStdModulesWorker) -> std::optional { + const size_t CurUserInputIdx = + NextUserInputIdx.fetch_add(1, std::memory_order_relaxed); + if (CurUserInputIdx < UserInputCount) + return CurUserInputIdx; + + if (IsStdModulesWorker) { + // Wait for all scans on user inputs to finish before reading the import + // state. + UserInputTaskGroup.wait(); + + // Because std.compat is always the last list item, and import std.compat + // implies import std, we can simply extend iteration based the required + // imports. + const size_t ExtraCount = + static_cast(SeenStdModules.load(std::memory_order_relaxed)); + const size_t CurStdInputIdx = + NextStdInputIdx.fetch_add(1, std::memory_order_relaxed); + if (CurStdInputIdx < UserInputCount + ExtraCount) + return CurStdInputIdx; + } + + return std::nullopt; + }; + + const IntrusiveRefCntPtr FS = + llvm::vfs::createPhysicalFileSystem(); + tooling::deps::DependencyScanningService Service( + tooling::deps::ScanningMode::DependencyDirectivesScan, + tooling::deps::ScanningOutputFormat::Full); + + SmallVector TUDepsList(InputCount); + SmallVector, 0> DiagLists(InputCount); + std::atomic HasError = false; + + auto RunScanningWorker = [&](bool IsStdModulesTask) -> void { + tooling::deps::DependencyScanningWorker Worker(Service, FS); + DenseSet AlreadySeen; + + while (const auto MaybeIndex = GetNextInputIndex(IsStdModulesTask)) { + const auto Index = *MaybeIndex; + const auto &Cmd = *ScanInputCmds[Index]; + const auto CommandLine = buildCC1CommandLine(Cmd); + + ModuleLookupActionController LookupController(TempDir); + StandaloneDiagCollector DiagConsumer; + tooling::deps::FullDependencyConsumer DepsConsumer(AlreadySeen); + + const bool Success = + Worker.computeDependencies(/*CWD*/ ".", CommandLine, DepsConsumer, + LookupController, DiagConsumer); + if (!Success) + HasError = true; + + DiagLists[Index] = DiagConsumer.takeDiags(); + TUDepsList[Index] = DepsConsumer.takeTranslationUnitDeps(); + + // Check if we additionally need to scan std.cppm or std.compat.cppm. + if (Index < UserInputCount) + UpdateStdImportFlags(TUDepsList[Index]); + } + }; + + // TODO: This will always make the StdModulesTaskGroup have one worker, but + // scan up to two files. For 4 or more inputs, it would make sense to have two + // StdModulesTaskGroup workers. + if (UserInputCount > 3) { + const size_t Concurrency = + std::min(static_cast(Pool.getMaxConcurrency()), UserInputCount); + for (size_t I = 0; I < Concurrency - 1; ++I) + Pool.async(UserInputTaskGroup, [&]() { RunScanningWorker(false); }); + } + Pool.async(StdModulesTaskGroup, [&]() { RunScanningWorker(true); }); + Pool.wait(); + + // Report diagnostics in the original source input order. + const StandaloneDiagReporter DiagReporter(Diags); + for (auto &StandaloneDiagList : DiagLists) + for (auto &StandaloneDiag : StandaloneDiagList) + DiagReporter.Report(std::move(StandaloneDiag)); + + if (HasError) + return {}; + + // Trim output to omit entries for std library modules which weren't + // imported. + const size_t UnusedStdModuleImports = + 2 - static_cast(SeenStdModules.load(std::memory_order_relaxed)); + TUDepsList.truncate(TUDepsList.size() - UnusedStdModuleImports); + return TUDepsList; +} + +//===----------------------------------------------------------------------===// +// Module Dependency Graph +//===----------------------------------------------------------------------===// + +namespace { + +class MDGNode; +class MDGEdge; +using MDGNodeBase = DGNode; +using MDGEdgeBase = DGEdge; +using ModuleDepGraphBase = DirectedGraph; + +/// Abstract base class for all node kinds in the module dependency graph. +class MDGNode : public MDGNodeBase { +public: + enum class NodeKind { + Root, + ClangModule, + NamedCXXModule, + NonModule, + }; + + explicit MDGNode(NodeKind Kind) : Kind(Kind) {} + virtual ~MDGNode() = 0; + + /// Returns this node's kind. + NodeKind getKind() const { return Kind; } + +private: + NodeKind Kind; +}; + +MDGNode::~MDGNode() {} + +/// Represents the root node of the module dependency graph. +/// +/// The root node only serves as an entry point for graph traversal. +/// It should have an edge to each node that would otherwise have no incoming +/// edges, ensuring there is always a path from the root to any node in the +/// graph. +/// There should be exactly one such root node in a given graph. +class RootMDGNode final : public MDGNode { +public: + RootMDGNode() : MDGNode(NodeKind::Root) {} + + /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc. + static bool classof(const MDGNode *N) { + return N->getKind() == NodeKind::Root; + } +}; + +/// Base class defining common functionality for nodes that represent a +/// translation unit from a command line source input. +class TranslationUnitBackedMDGNode : public MDGNode { +protected: + explicit TranslationUnitBackedMDGNode( + const NodeKind Kind, tooling::dependencies::TranslationUnitDeps &TUDeps, + const size_t JobIndex) + : MDGNode(Kind), JobIndex(JobIndex), + NamedModuleDeps(std::move(TUDeps.NamedModuleDeps)), + ClangModuleDeps(std::move(TUDeps.ClangModuleDeps)) { + assert(!TUDeps.FileDeps.empty() && + "TUDeps has to have a corresponding source file"); + FileName = TUDeps.FileDeps.front(); + assert(TUDeps.Commands.size() == 1 && + "Generated multiple commands for a single -cc1 job?"); + BuildArgs = std::move(TUDeps.Commands.front().Arguments); + } + +public: + /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc. + static bool classof(const MDGNode *N) { + auto K = N->getKind(); + return K == NodeKind::NonModule || K == NodeKind::NamedCXXModule; + } + + size_t JobIndex; + std::string FileName; + std::vector NamedModuleDeps; + std::vector ClangModuleDeps; + std::vector BuildArgs; +}; + +/// Subclass of MDGNode representing a translation unit that provides a C++20 +/// named module. +class CXXNamedModuleMDGNode final : public TranslationUnitBackedMDGNode { +public: + CXXNamedModuleMDGNode(tooling::dependencies::TranslationUnitDeps &TUDeps, + const size_t JobIndex) + : TranslationUnitBackedMDGNode(NodeKind::NamedCXXModule, TUDeps, + JobIndex), + ModuleName(std::move(TUDeps.ID.ModuleName)) {} + + /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc. + static bool classof(const MDGNode *N) { + return N->getKind() == MDGNode::NodeKind::NamedCXXModule; + } + + std::string ModuleName; +}; + +/// Subclass of MDGNode representing a translation unit that does not provide +/// any module. +class NonModuleMDGNode final : public TranslationUnitBackedMDGNode { +public: + NonModuleMDGNode(tooling::dependencies::TranslationUnitDeps &TUDeps, + const size_t JobIndex) + : TranslationUnitBackedMDGNode(NodeKind::NonModule, TUDeps, JobIndex) {} + + /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc. + static bool classof(const MDGNode *N) { + return N->getKind() == MDGNode::NodeKind::NonModule; + } +}; + +/// Subclass of MDGNode representing a Clang module unit. +class ClangModuleMDGNode final : public MDGNode { +public: + ClangModuleMDGNode(tooling::dependencies::ModuleDeps &MD, + const size_t ParentJobIndex) + : MDGNode(NodeKind::ClangModule), ParentJobIndex(ParentJobIndex), + ID(std::move(MD.ID)), ClangModuleDeps(std::move(MD.ClangModuleDeps)), + BuildArgs(MD.getBuildArguments()) {} + + /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc. + static bool classof(const MDGNode *N) { + return N->getKind() == MDGNode::NodeKind::ClangModule; + } + + size_t ParentJobIndex; + tooling::deps::ModuleID ID; + std::vector ClangModuleDeps; + std::vector BuildArgs; +}; + +/// Represents an import relation in the module dependency graph, directed +/// from the imported module to the importer. +class MDGEdge : public MDGEdgeBase { +public: + explicit MDGEdge(MDGNode &N) : MDGEdgeBase(N) {} + MDGEdge() = delete; +}; + +class ModuleDepGraphBuilder; + +/// A directed graph describing the discovered module dependency relations. +/// +/// The graph owns all of its nodes and edges. +/// The graph's root node is initialized on construction. +class ModuleDepGraph : public ModuleDepGraphBase { +public: + ModuleDepGraph() { + Root = new (Alloc.Allocate(sizeof(RootMDGNode), alignof(RootMDGNode))) + RootMDGNode(); + } + + MDGNode *getRoot() { return Root; } + + const MDGNode *getRoot() const { return Root; } + + // Gets the graph's allocator which should be used to allocate all of the + // Graph's nodes and edges. + BumpPtrAllocator &getAllocator() { return Alloc; } + +private: + friend class ModuleDepGraphBuilder; + + BumpPtrAllocator Alloc; + RootMDGNode *Root = nullptr; +}; + +} // anonymous namespace + +//===----------------------------------------------------------------------===// +// Module Dependency Graph: GraphTraits specializations +//===----------------------------------------------------------------------===// + +namespace llvm { +/// Non-const versions of the GraphTraits specializations for ModuleDepGraph. +template <> struct GraphTraits { + using NodeRef = MDGNode *; + + static NodeRef MDGGetTargetNode(MDGEdgeBase *E) { + return &E->getTargetNode(); + } + + using ChildIteratorType = + mapped_iterator; + using ChildEdgeIteratorType = MDGNode::iterator; + + static NodeRef getEntryNode(NodeRef N) { return N; } + + static ChildIteratorType child_begin(NodeRef N) { + return ChildIteratorType(N->begin(), &MDGGetTargetNode); + } + + static ChildIteratorType child_end(NodeRef N) { + return ChildIteratorType(N->end(), &MDGGetTargetNode); + } + + static ChildEdgeIteratorType child_edge_begin(NodeRef N) { + return N->begin(); + } + static ChildEdgeIteratorType child_edge_end(NodeRef N) { return N->end(); } +}; + +template <> struct GraphTraits : GraphTraits { + using GraphRef = ModuleDepGraph *; + using NodeRef = MDGNode *; + + using nodes_iterator = ModuleDepGraph::iterator; + + static NodeRef getEntryNode(GraphRef G) { return G->getRoot(); } + + static nodes_iterator nodes_begin(GraphRef G) { return G->begin(); } + + static nodes_iterator nodes_end(GraphRef G) { return G->end(); } +}; + +/// Const versions of the GraphTraits specializations for ModuleDepGraph. +template <> struct GraphTraits { + using NodeRef = const MDGNode *; + + static NodeRef MDGGetTargetNode(const MDGEdgeBase *E) { + return &E->getTargetNode(); + } + + using ChildIteratorType = + mapped_iterator; + using ChildEdgeIteratorType = MDGNode::const_iterator; + + static NodeRef getEntryNode(NodeRef N) { return N; } + + static ChildIteratorType child_begin(NodeRef N) { + return ChildIteratorType(N->begin(), &MDGGetTargetNode); + } + + static ChildIteratorType child_end(NodeRef N) { + return ChildIteratorType(N->end(), &MDGGetTargetNode); + } + + static ChildEdgeIteratorType child_edge_begin(NodeRef N) { + return N->begin(); + } + + static ChildEdgeIteratorType child_edge_end(NodeRef N) { return N->end(); } +}; + +template <> +struct GraphTraits : GraphTraits { + using GraphRef = const ModuleDepGraph *; + using NodeRef = const MDGNode *; + + using nodes_iterator = ModuleDepGraph::const_iterator; + + static NodeRef getEntryNode(GraphRef G) { return G->getRoot(); } + + static nodes_iterator nodes_begin(GraphRef G) { return G->begin(); } + + static nodes_iterator nodes_end(GraphRef G) { return G->end(); } +}; +} // namespace llvm + +//===----------------------------------------------------------------------===// +// Module Dependency Graph: Graph Builder +//===----------------------------------------------------------------------===// + +namespace { +/// Construction helper for the module dependency graph. +class ModuleDepGraphBuilder { +public: + explicit ModuleDepGraphBuilder(DiagnosticsEngine &Diags) : Diags(Diags) {} + + bool build(SmallVectorImpl &&ScanResults); + + ModuleDepGraph takeGraph() { return std::move(Graph); } + +private: + ModuleDepGraph Graph; + DiagnosticsEngine &Diags; + + /// Lookup maps used for connecting the nodes. + DenseMap ClangModuleNodeMap; + llvm::StringMap CXXNamedModuleMap; + + /// Allocation helper using the graph's allocator. + template + MDGComponent *makeWithGraphAlloc(Args &&...args) { + return new ( + Graph.Alloc.Allocate(sizeof(MDGComponent), alignof(MDGComponent))) + MDGComponent(std::forward(args)...); + } + + void addAllClangModuleNodes( + SmallVectorImpl &ScanResults); + void addClangModuleGraph(tooling::deps::ModuleDepsGraph &ClangModuleGraph, + size_t ParentJob); + void addClangModuleNode(tooling::deps::ModuleDeps &MD, size_t ParentJob); + void connectClangModuleNodes(); + + bool addAllTUBackedNodes( + SmallVectorImpl &ScanResults); + bool addCXXNamedModuleNode(tooling::deps::TranslationUnitDeps &&TUDeps, + size_t Job); + void addNonModuleNode(tooling::deps::TranslationUnitDeps &&TUDeps, + size_t Job); + void connectTUBackedNodes(); + + void addImportEdge(MDGNode &Imported, MDGNode &Importer); +}; +} // anonymous namespace + +bool ModuleDepGraphBuilder::build( + SmallVectorImpl &&ScanResults) { + addAllClangModuleNodes(ScanResults); + connectClangModuleNodes(); + if (!addAllTUBackedNodes(ScanResults)) + return false; + connectTUBackedNodes(); + return true; +} + +// Construct all Clang module nodes for this graph. +void ModuleDepGraphBuilder::addAllClangModuleNodes( + SmallVectorImpl &ScanResults) { + for (size_t I = 0, E = ScanResults.size(); I < E; ++I) { + addClangModuleGraph(ScanResults[I].ModuleGraph, I); + } +} + +/// Adds a new Clang module node every not yet seen Clang module in \c +/// MDGraph. +void ModuleDepGraphBuilder::addClangModuleGraph( + tooling::deps::ModuleDepsGraph &ClangModuleGraph, size_t ParentJob) { + for (auto &MD : ClangModuleGraph) { + if (ClangModuleNodeMap.contains(MD.ID)) + continue; + addClangModuleNode(MD, ParentJob); + } +} + +void ModuleDepGraphBuilder::addClangModuleNode(tooling::deps::ModuleDeps &MD, + size_t ParentJob) { + auto *Node = makeWithGraphAlloc(MD, ParentJob); + const auto [_, Inserted] = ClangModuleNodeMap.try_emplace(Node->ID, Node); + assert(Inserted && "Duplicate nodes for a single Clang module!"); + Graph.addNode(*Node); +} + +/// Interconnect all Clang module nodes. +void ModuleDepGraphBuilder::connectClangModuleNodes() { + for (auto *Node : nodes(&Graph)) { + auto *Importer = cast(Node); + + if (Importer->ClangModuleDeps.empty()) { + // No imports: connect to root for reachability and continue. + addImportEdge(*Graph.getRoot(), *Importer); + continue; + } + + for (const auto &ID : Importer->ClangModuleDeps) { + // The dependency scanning worker is expected to error if any required + // dependency is not found. + auto Found = ClangModuleNodeMap.find(ID); + assert(Found != ClangModuleNodeMap.end() && + "Missing imported Clang module node"); + if (Found == ClangModuleNodeMap.end()) + continue; + + MDGNode *Imported = Found->second; + addImportEdge(*Imported, *Importer); + } + } +} + +// Construct all nodes for this graph, which represent the dependencies of a +// source input. +bool ModuleDepGraphBuilder::addAllTUBackedNodes( + SmallVectorImpl &ScanResults) { + for (size_t I = 0, E = ScanResults.size(); I < E; ++I) { + auto &TUDeps = ScanResults[I]; + if (TUDeps.ID.ModuleName.empty()) { + // Non-module TU + addNonModuleNode(std::move(TUDeps), I); + } else { + // C++ named module TU + if (!addCXXNamedModuleNode(std::move(TUDeps), I)) + return false; + } + } + return true; +} + +/// Adds a node representing the non-module translation unit described by \c +/// TUDeps. +void ModuleDepGraphBuilder::addNonModuleNode( + tooling::deps::TranslationUnitDeps &&TUDeps, size_t Job) { + auto *Node = makeWithGraphAlloc(TUDeps, Job); + Graph.addNode(*Node); +} + +/// Adds a node representing the C++ named module unit described by \c +/// TUDeps. +/// +/// \returns false on error if a node for the same module interface already +/// exists in the graph. +bool ModuleDepGraphBuilder::addCXXNamedModuleNode( + tooling::deps::TranslationUnitDeps &&TUDeps, size_t Job) { + CXXNamedModuleMDGNode *Node = + makeWithGraphAlloc(TUDeps, Job); + StringRef ModuleName = Node->ModuleName; + const auto [It, Inserted] = CXXNamedModuleMap.try_emplace(ModuleName, Node); + if (!Inserted) { + StringRef ExistingFile = It->second->FileName; + StringRef ThisFile = Node->FileName; + Diags.Report(diag::err_mod_graph_named_module_redefinition) + << ModuleName << ExistingFile << ThisFile; + return false; + } + + Graph.addNode(*Node); + return true; +} + +// Construct all nodes for this graph, which represent the dependencies of a +// source input. +void ModuleDepGraphBuilder::connectTUBackedNodes() { + for (auto *Node : nodes(&Graph)) { + if (auto *Importer = dyn_cast(Node)) { + if (Importer->NamedModuleDeps.empty() && + Importer->ClangModuleDeps.empty()) { + addImportEdge(*Graph.getRoot(), *Importer); + continue; + } + for (const auto &Name : Importer->NamedModuleDeps) + if (auto *Imported = CXXNamedModuleMap.lookup(Name)) + addImportEdge(*Imported, *Importer); + for (const auto &ID : Importer->ClangModuleDeps) + if (auto *Imported = ClangModuleNodeMap.lookup(ID)) + addImportEdge(*Imported, *Importer); + } + } +} + +/// Creates and adds an edge from \c Imported to \c Importer. +void ModuleDepGraphBuilder::addImportEdge(MDGNode &Imported, + MDGNode &Importer) { + auto *Edge = makeWithGraphAlloc(Importer); + Imported.addEdge(*Edge); +} + +/// Construct the module dependency graph for the given \c ScanResults. +/// +/// \returns The constructed graph, or an std::nullopt on error, if +/// \c ScanResults contains conflicting module definitions. +static std::optional buildModuleDepGraph( + SmallVectorImpl &&ScanResults, + DiagnosticsEngine &Diags) { + ModuleDepGraphBuilder Builder(Diags); + if (!Builder.build(std::move(ScanResults))) + return std::nullopt; + return Builder.takeGraph(); +} + +//===----------------------------------------------------------------------===// +// Module Dependency Graph: GraphViz Output +//===----------------------------------------------------------------------===// + +namespace llvm { +template <> +struct DOTGraphTraits : DefaultDOTGraphTraits { + explicit DOTGraphTraits(bool IsSimple = false) + : DefaultDOTGraphTraits(IsSimple) {} + + static std::string getGraphName(const ModuleDepGraph *) { + return "Module Dependency Graph"; + } + + static std::string getGraphProperties(const ModuleDepGraph *) { + return "\tnode [shape=Mrecord];\n\tedge [dir=\"back\"];\n"; + } + + static bool isNodeHidden(const MDGNode *N, const ModuleDepGraph *) { + return isa(N); + } + + static std::string getNodeIdentifier(const MDGNode *N, + const ModuleDepGraph *) { + SmallString<128> Buf; + raw_svector_ostream OS(Buf); + TypeSwitch(N) + .Case([&](auto *N) { OS << N->ID.ModuleName; }) + .Case([&](auto *N) { OS << N->ModuleName; }) + .Case([&](auto *N) { OS << N->FileName; }) + .Default([](auto *) { llvm_unreachable("Unhandled MDGNode kind!"); }); + OS << " (" << getNodeKindStr(N->getKind()) << ")"; + + return std::string(OS.str()); + } + + static std::string getNodeLabel(const MDGNode *N, const ModuleDepGraph *) { + SmallString<128> Buf; + raw_svector_ostream OS(Buf); + OS << "Type: " << getNodeKindStr(N->getKind()) << " \\| "; + + auto PrintFilename = [](raw_ostream &OS, StringRef Filename) { + OS << "Filename: " << Filename; + }; + auto PrintModuleName = [](raw_ostream &OS, StringRef ModuleName) { + OS << "Provides: " << ModuleName; + }; + TypeSwitch(N) + .Case( + [&](auto *N) { PrintModuleName(OS, N->ID.ModuleName); }) + .Case([&](auto *N) { + PrintModuleName(OS, N->ModuleName); + OS << " \\| "; + PrintFilename(OS, N->FileName); + }) + .Case( + [&](auto *N) { PrintFilename(OS, N->FileName); }) + .Default([](auto *) { + llvm::reportFatalInternalError("Unhandled MDGNode kind!"); + }); + + return std::string(OS.str()); + } + +private: + static StringRef getNodeKindStr(MDGNode::NodeKind Kind) { + using NodeKind = MDGNode::NodeKind; + switch (Kind) { + case NodeKind::NamedCXXModule: + return "C++20 module"; + case NodeKind::ClangModule: + return "Clang module"; + case NodeKind::NonModule: + return "Non-module source"; + default: + llvm::reportFatalInternalError("Unexpected MDGNode kind!"); + }; + } +}; + +/// GraphWriter specialization for ModuleDepGraph. +/// +/// Uses human-readable identifiers instead of raw pointers and separates node +/// definitions from import edges for a more remark-friendly output. +template <> +class GraphWriter + : public GraphWriterBase> { +public: + using GraphType = const ModuleDepGraph *; + using Base = GraphWriterBase>; + + GraphWriter(llvm::raw_ostream &O, const GraphType &G, bool IsSimple) + : Base(O, G, IsSimple) {} + + void writeNodes(); + +private: + using Base::DOTTraits; + using Base::GTraits; + using Base::NodeRef; + + DenseMap NodeIDMap; + + void writeNodeDefinitions(ArrayRef Nodes); + void writeNodeRelations(ArrayRef Nodes); +}; + +void GraphWriter::writeNodes() { + auto IsNodeVisible = [&](NodeRef N) { return !DTraits.isNodeHidden(N, G); }; + const auto VisibleNodeRange = make_filter_range(nodes(G), IsNodeVisible); + const SmallVector VisibleNodes(VisibleNodeRange); + + writeNodeDefinitions(VisibleNodes); + writeNodeRelations(VisibleNodes); +} + +void GraphWriter::writeNodeDefinitions( + ArrayRef Nodes) { + for (const auto &Node : Nodes) { + const auto NodeID = DTraits.getNodeIdentifier(Node, G); + const auto NodeLabel = DTraits.getNodeLabel(Node, G); + O << "\t\"" << DOT::EscapeString(NodeID) << "\" [ " + << DTraits.getNodeAttributes(Node, G) << "label=\"{ " + << DOT::EscapeString(NodeLabel) << " }\"];\n"; + NodeIDMap.try_emplace(Node, std::move(NodeID)); + } + O << "\n"; +} + +void GraphWriter::writeNodeRelations( + ArrayRef Nodes) { + for (const auto &Node : Nodes) { + const auto &SourceNodeID = NodeIDMap.at(Node); + for (const auto &Edge : Node->getEdges()) { + const auto *TargetNode = GTraits::MDGGetTargetNode(Edge); + const auto &TargetNodeID = NodeIDMap.at(TargetNode); + O << "\t\"" << DOT::EscapeString(SourceNodeID) << "\" -> \"" + << DOT::EscapeString(TargetNodeID) << "\";\n"; + } + } +} +} // namespace llvm + +//===----------------------------------------------------------------------===// +// Modules Driver +//===----------------------------------------------------------------------===// + +/// Returns true if a driver command is a viable dependency scan input. +/// We consider only clang -cc1 jobs whose inputs are all source inputs. +static bool isJobForDepScan(const Command &Cmd) { + if (llvm::StringRef(Cmd.getCreator().getName()) != "clang") + return false; + auto IsSrcInput = [](const InputInfo &II) -> bool { + return types::isSrcFile(II.getType()); + }; + return llvm::all_of(Cmd.getInputInfos(), IsSrcInput); +} + +/// Partition \c Cmds into dependency scan input jobs and regular jobs. +static std::pair partitionsCmds(JobList &&Cmds) { + OwnedJobList ScanInputCmds, DependentCmds; + for (auto &Job : Cmds.getJobs()) { + if (isJobForDepScan(*Job)) + ScanInputCmds.push_back(std::move(Job)); + else + DependentCmds.push_back(std::move(Job)); + } + Cmds.getJobs().clear(); + return {std::move(ScanInputCmds), std::move(DependentCmds)}; +} + +/// Modifies \c ScanInputCmds and \c DependentCmds to delete the jobs for std +/// modules which were not imported. +static void pruneUnusedStdModuleJobs( + SmallVectorImpl> &ScanInputCmds, + SmallVectorImpl> &DependentCmds, + SmallVector ScanOutputs) { + const size_t StdModulesToRemove = ScanInputCmds.size() - ScanOutputs.size(); + if (StdModulesToRemove == 0) + return; + + const size_t UnusedStdModulesFirst = ScanOutputs.size(); + const size_t UnusedStdModulesEnd = ScanInputCmds.size(); + + // Remove the object files, created by Standard library module jobs which + // are to be deleted, from the final linker job. + llvm::StringSet<> LinkerInputsToRemove; + for (size_t I = UnusedStdModulesFirst; I < UnusedStdModulesEnd; ++I) { + for (StringRef Out : ScanInputCmds[I]->getOutputFilenames()) + LinkerInputsToRemove.insert(Out); + } + + if (!LinkerInputsToRemove.empty() && !DependentCmds.empty()) { + const auto &MaybeLinkJob = DependentCmds.back(); + if (MaybeLinkJob->getSource().getKind() == + Action::ActionClass::LinkJobClass) { + const auto &LinkJobArguments = MaybeLinkJob->getArguments(); + ArgStringList NewArgList; + NewArgList.reserve(LinkJobArguments.size()); + for (const char *Arg : LinkJobArguments) { + if (LinkerInputsToRemove.contains(StringRef(Arg))) + continue; + NewArgList.push_back(Arg); + } + MaybeLinkJob->replaceArguments(NewArgList); + } + } + + // Delete the unused Standard library module jobs themselves. + ScanInputCmds.erase( + ScanInputCmds.begin() + static_cast(UnusedStdModulesFirst), + ScanInputCmds.begin() + static_cast(UnusedStdModulesEnd)); +} + +/// Returns the list of topologically sorted nodes for \c Graph, excluding the +/// root node. +static SmallVector +getTopologicallySortedNodes(ModuleDepGraph &Graph) { + SmallVector PostOrder; + PostOrder.reserve(Graph.size()); + + for (auto *N : llvm::post_order(&Graph)) + PostOrder.push_back(N); + + SmallVector TopoSortedGraph = PostOrder; + std::reverse(TopoSortedGraph.begin(), TopoSortedGraph.end()); + // Drop the root node! + return SmallVector(TopoSortedGraph.begin() + 1, + TopoSortedGraph.end()); +} + +/// Replaces the build arguments for each job with those generated by the +/// dependency scan. For Clang modules, new jobs are created before. +static OwnedJobList installBuildArgsAndClangModuleJobs( + Compilation &C, SmallVectorImpl &NodesInBuildOrder, + OwnedJobList &ScanInputCmds) { + OwnedJobList Out; + Out.reserve(ScanInputCmds.size()); + + auto InstallArgStrList = [&](Command &Cmd, ArrayRef Args) { + ArgStringList NewArgs; + NewArgs.reserve(Args.size()); + + auto &TCArgs = C.getArgsForToolChain( + &Cmd.getCreator().getToolChain(), Cmd.getSource().getOffloadingArch(), + Cmd.getSource().getOffloadingDeviceKind()); + for (const auto &S : Args) + NewArgs.push_back(TCArgs.MakeArgString(S)); + + Cmd.replaceArguments(NewArgs); + }; + + /// TODO: The 'linkBuildArguments' function can be implemented alot cleaner if + /// we store the jobs inside of the graph instead of modifying their original + /// index. + for (size_t I = 0, E = NodesInBuildOrder.size(); I < E; ++I) { + auto *Node = NodesInBuildOrder[I]; + TypeSwitch(Node) + .Case([&](auto *ClangModuleNode) { + const auto &ParentJob = + ScanInputCmds[ClangModuleNode->ParentJobIndex]; + auto ClangModuleJob = std::make_unique(*ParentJob); + InstallArgStrList(*ClangModuleJob, ClangModuleNode->BuildArgs); + ClangModuleNode->ParentJobIndex = I; + Out.push_back(std::move(ClangModuleJob)); + }) + .Case([&](auto *TUBackedNode) { + auto Job = std::move(ScanInputCmds[TUBackedNode->JobIndex]); + TUBackedNode->JobIndex = I; + InstallArgStrList(*Job, TUBackedNode->BuildArgs); + Out.push_back(std::move(Job)); + }) + .Default([](auto *) { + llvm::reportFatalInternalError("Unhandled MDGNode kind!"); + }); + } + return Out; +} + +/// Returns the \c Argv as InputArgList. The returned InputArgList is only valid +/// for the lifetime of \c Argv. +/// TODO: The 'linkBuildArguments' function can be implemented alot cleaner if +/// we store the jobs inside of the graph instead of modifying their original +/// index. +static llvm::opt::InputArgList +parseInputArgList(llvm::opt::ArgStringList Argv) { + const auto &Opts = getDriverOptTable(); + unsigned MissingArgIndex = 0, MissingArgCount = 0; + return Opts.ParseArgs(Argv, MissingArgIndex, MissingArgCount, + llvm::opt::Visibility(options::CC1Option)); +} + +/// Modifies the job's command lines to properly output and pass C++ named +/// module dependencies to each other. +static void +linkBuildArguments(Compilation &C, ArrayRef NodesInBuildOrder, + SmallVectorImpl> &OrderedCC1Jobs, + SmallVectorImpl> &JobListTail) { + // TODO: Use same temporary directory as used for the dependency scan. + // TODO: Output files which are not associated with a job action do not get + // cleaned up properly. + auto constructPCMPath = [](Compilation &C, const StringRef &ModuleName) { + return C.getDriver().CreateTempFile(C, ModuleName, "pcm"); + }; + // Propagate -fmodule-file== + for (auto *Node : NodesInBuildOrder) { + if (auto *TUNode = llvm::dyn_cast(Node)) { + StringRef ModuleOutput; + auto &Job = OrderedCC1Jobs[TUNode->JobIndex]; + auto &Args = Job->getArguments(); + auto InputArgsList = parseInputArgList(Args); + ModuleOutput = constructPCMPath(C, TUNode->ModuleName); + auto &TCArgs = + C.getArgsForToolChain(&Job->getCreator().getToolChain(), + Job->getSource().getOffloadingArch(), + Job->getSource().getOffloadingDeviceKind()); + // Always create an -fmodule-output to take control of the output path, + // even for .cppm files, where this was already added. + Args.push_back( + TCArgs.MakeArgString(Twine("-fmodule-output=") + ModuleOutput)); + Args.push_back("-fmodules-reduced-bmi"); + // Hack: Hack to omit the warning for the std module, because I did not + // get this working from the driver command line yet. + Args.push_back("-Wno-reserved-module-identifier"); + + // Propogate to dependent -cc1 commands in the graph + for (auto *ChildNode : llvm::depth_first(Node)) { + auto *ChildTUNode = llvm::cast(ChildNode); + if (ChildTUNode->JobIndex == TUNode->JobIndex) + continue; + auto &ChildJob = OrderedCC1Jobs[ChildTUNode->JobIndex]; + auto &TCArgs = C.getArgsForToolChain( + &ChildJob->getCreator().getToolChain(), + ChildJob->getSource().getOffloadingArch(), + ChildJob->getSource().getOffloadingDeviceKind()); + ChildJob->getArguments().push_back(TCArgs.MakeArgString( + Twine("-fmodule-file=") + TUNode->ModuleName + "=" + ModuleOutput)); + } + + // Propogate to dependent commands, which we not part of the dependency + // scan. + for (auto &TailCmd : JobListTail) { + if (StringRef(TailCmd->getCreator().getName()) != "clang") + continue; + auto II = TailCmd->getInputInfos(); + if (II.empty()) + continue; + if (II.front().isFilename() && + II.front().getFilename() == TUNode->FileName) { + auto &TCArgs = C.getArgsForToolChain( + &TailCmd->getCreator().getToolChain(), + TailCmd->getSource().getOffloadingArch(), + TailCmd->getSource().getOffloadingDeviceKind()); + TailCmd->getArguments().push_back( + TCArgs.MakeArgString(Twine("-fmodule-file=") + + TUNode->ModuleName + "=" + ModuleOutput)); + } + } + } + } +} + +namespace clang::driver::modules { + +bool performDriverModuleBuild(Compilation &C, DiagnosticsEngine &Diags) { + auto [ScanInputCmds, JobListTail] = partitionsCmds(std::move(C.getJobs())); + + // The directory containing all module artifacts. + // TODO: Output files which are not associated with a job action do not get + // cleaned up properly. + const auto TempDir = C.getDriver().GetTemporaryDirectory("modules-driver"); + C.addTempFile(C.getArgs().MakeArgString(TempDir)); + + auto ScanOutputs = scanDependencies(ScanInputCmds, Diags, TempDir); + if (ScanOutputs.empty()) { + Diags.Report(diag::err_failed_depdendency_scan); + return false; + } + // If the Standard library modules are not needed, remove them fully. + pruneUnusedStdModuleJobs(ScanInputCmds, JobListTail, ScanOutputs); + + auto DepGraph = buildModuleDepGraph(std::move(ScanOutputs), Diags); + if (!DepGraph) { + Diags.Report(diag::err_building_depdendency_graph); + return false; + } + Diags.Report(diag::remark_printing_module_graph); + if (!Diags.isLastDiagnosticIgnored()) + WriteGraph(llvm::errs(), &(*DepGraph)); + + auto NodesInBuildOrder = getTopologicallySortedNodes(*DepGraph); + + auto OrderedCC1Jobs = + installBuildArgsAndClangModuleJobs(C, NodesInBuildOrder, ScanInputCmds); + + linkBuildArguments(C, NodesInBuildOrder, OrderedCC1Jobs, JobListTail); + + // Reconstruct the job list. + C.getJobs().getJobs() = std::move(OrderedCC1Jobs); + for (auto &NonScanJob : JobListTail) + C.getJobs().getJobs().push_back(std::move(NonScanJob)); + return true; +} + +} // namespace clang::driver::modules diff --git a/clang/test/Driver/modules-driver-compile-both-kinds.cpp b/clang/test/Driver/modules-driver-compile-both-kinds.cpp new file mode 100644 index 0000000000000..151ea405b55c7 --- /dev/null +++ b/clang/test/Driver/modules-driver-compile-both-kinds.cpp @@ -0,0 +1,104 @@ +// Tests that the modules driver properly compiles both C++20 named modules +// and Clang modules. +// This does not test importing a Clang module into a C++20 named module +// interface unit, or vice versa, is not yet supported. +// TODO: Support imports between different module types. +// Because the std library is not available in the CI, this does not test for it. +// TODO: Add tests for the Standard library modules. + +// RUN: split-file %s %t +// RUN: %clang++ -std=c++23 -fmodules -fmodules-driver \ +// RUN: -fmodule-map-file=%t/module.modulemap %t/main.cpp \ +// RUN: %t/A.cpp %t/A-part1.cpp %t/A-part1-impl.cpp %t/A-part2.cpp \ +// RUN: %t/B.cppm + +//--- main.cpp +#include "root.h" +import A; +import B; + +int main() { + // *** Testing C++20 named modules *** + A(); // From the A's primary module partition interface . + APart1(); // From a public module partition interface unit of A. + + // *** Testing Clang modules *** + theAnswer(); +} + +//--- A.cpp +export module A; +export import :part1; +import :part2; +import B; + +export int A() { + doesNothing(); // Imported from B + return APart1() + APart2(); // From public and private module partition interface units. +} + +//--- A-part1.cpp +export module A:part1; + +export int APart1(); // Implemented in module implementation unit A-part1-impl.cpp. + +//--- A-part1-impl.cpp +module A:part1_impl; +import :part2; + +int APart1() { + return 2 + APart2(); +} + +//--- A-part2.cpp +export module A:part2; + +export int APart2() { + return 2; +} + +//--- B.cppm +export module B; + +export void doesNothing() { + return; +} + +//--- module.modulemap +module root { header "root.h" export *} +module direct1 { header "direct1.h" export *} +module direct2 { header "direct2.h" export *} +module transitive1 { header "transitive1.h" export *} +module transitive2 { header "transitive2.h" export * } + +//--- root.h +#include "direct1.h" +#include "direct2.h" +int theAnswer() { + return fromDirect1() + fromDirect2(); +} + +//--- direct1.h +#include "transitive1.h" +#include "transitive2.h" + +int fromDirect1() { + return fromTransitive1() + fromTransitive2(); +} + +//--- direct2.h +#include "transitive1.h" + +int fromDirect2() { + return fromTransitive1() + 2; +} + +//--- transitive1.h +int fromTransitive1() { + return 10; +} + +//--- transitive2.h +int fromTransitive2() { + return 10; +} diff --git a/clang/test/Driver/modules-driver-dep-scan-graphviz.cpp b/clang/test/Driver/modules-driver-dep-scan-graphviz.cpp new file mode 100644 index 0000000000000..71b5719927949 --- /dev/null +++ b/clang/test/Driver/modules-driver-dep-scan-graphviz.cpp @@ -0,0 +1,89 @@ +// Tests that the module dependency scan and the module dependency graph +// generation are correct. + +// RUN: split-file %s %t + +// RUN: %clang -std=c++23 -fmodules -fmodules-driver -Rmodules-driver \ +// RUN: -fmodule-map-file=%t/module.modulemap %t/main.cpp \ +// RUN: %t/A.cpp %t/A-B.cpp %t/A-C.cpp %t/B.cpp -### 2>&1 \ +// RUN: | sed 's:\\\\\?:/:g' \ +// RUN: | FileCheck -DPREFIX=%/t --check-prefixes=CHECK %s + +// CHECK: clang: remark: printing module dependency graph [-Rmodules-driver] +// CHECK-NEXT: digraph "Module Dependency Graph" { +// CHECK-NEXT: label="Module Dependency Graph"; +// CHECK-NEXT: node [shape=Mrecord]; + +// CHECK: "transitive1 (Clang module)" [ label="{ Type: Clang module | Provides: transitive1 }"]; +// CHECK-NEXT: "transitive2 (Clang module)" [ label="{ Type: Clang module | Provides: transitive2 }"]; +// CHECK-NEXT: "direct1 (Clang module)" [ label="{ Type: Clang module | Provides: direct1 }"]; +// CHECK-NEXT: "direct2 (Clang module)" [ label="{ Type: Clang module | Provides: direct2 }"]; +// CHECK-NEXT: "root (Clang module)" [ label="{ Type: Clang module | Provides: root }"]; +// CHECK-NEXT: "[[PREFIX]]/main.cpp (Non-module source)" [ label="{ Type: Non-module source | Filename: [[PREFIX]]/main.cpp }"]; +// CHECK-NEXT: "A (C++20 module)" [ label="{ Type: C++20 module | Provides: A | Filename: [[PREFIX]]/A.cpp }"]; +// CHECK-NEXT: "A:B (C++20 module)" [ label="{ Type: C++20 module | Provides: A:B | Filename: [[PREFIX]]/A-B.cpp }"]; +// CHECK-NEXT: "A:C (C++20 module)" [ label="{ Type: C++20 module | Provides: A:C | Filename: [[PREFIX]]/A-C.cpp }"]; +// CHECK-NEXT: "B (C++20 module)" [ label="{ Type: C++20 module | Provides: B | Filename: [[PREFIX]]/B.cpp }"]; + +// CHECK: "transitive1 (Clang module)" -> "direct1 (Clang module)"; +// CHECK-NEXT: "transitive1 (Clang module)" -> "direct2 (Clang module)"; +// CHECK-NEXT: "transitive2 (Clang module)" -> "direct1 (Clang module)"; +// CHECK-NEXT: "direct1 (Clang module)" -> "root (Clang module)"; +// CHECK-NEXT: "direct1 (Clang module)" -> "A:B (C++20 module)"; +// CHECK-NEXT: "direct2 (Clang module)" -> "root (Clang module)"; +// CHECK-NEXT: "root (Clang module)" -> "[[PREFIX]]/main.cpp (Non-module source)"; +// CHECK-NEXT: "root (Clang module)" -> "B (C++20 module)"; +// CHECK-NEXT: "A (C++20 module)" -> "[[PREFIX]]/main.cpp (Non-module source)"; +// CHECK-NEXT: "A (C++20 module)" -> "B (C++20 module)"; +// CHECK-NEXT: "A:B (C++20 module)" -> "A (C++20 module)"; +// CHECK-NEXT: "A:C (C++20 module)" -> "A (C++20 module)"; +// CHECK-NEXT: "B (C++20 module)" -> "[[PREFIX]]/main.cpp (Non-module source)"; +// CHECK-NEXT: } + +//--- module.modulemap +module root { header "root.h" } +module direct1 { header "direct1.h" } +module direct2 { header "direct2.h" } +module transitive1 { header "transitive1.h" } +module transitive2 { header "transitive2.h" } + +//--- root.h +#include "direct1.h" +#include "direct2.h" + +//--- direct1.h +#include "transitive1.h" +#include "transitive2.h" + +//--- direct2.h +#include "transitive1.h" + +//--- transitive1.h +// empty + +//--- transitive2.h +// empty + +//--- A.cpp +export module A; +export import :B; +import :C; + +//--- A-B.cpp +module; +#include "direct1.h" +export module A:B; + +//--- A-C.cpp +export module A:C; + +//--- B.cpp +module; +#include "root.h" +export module B; +import A; + +//--- main.cpp +#include "root.h" +import A; +import B; diff --git a/clang/test/Driver/modules-driver-duplicate-named-module.cpp b/clang/test/Driver/modules-driver-duplicate-named-module.cpp new file mode 100644 index 0000000000000..15fe3894b9be8 --- /dev/null +++ b/clang/test/Driver/modules-driver-duplicate-named-module.cpp @@ -0,0 +1,20 @@ +// Verify that the modules driver rejects ambiguous module definitions. + +// RUN: split-file %s %t + +// RUN: not %clang -std=c++23 -fmodules -fmodules-driver -Rmodules-driver \ +// RUN: %t/main.cpp %t/A1.cpp %t/A2.cpp 2>&1 \ +// RUN: | sed 's:\\\\\?:/:g' \ +// RUN: | FileCheck -DPREFIX=%/t --check-prefixes=CHECK %s + +/// CHECK: clang: error: duplicate definitions of C++20 named module 'A' in '[[PREFIX]]/A1.cpp' and '[[PREFIX]]/A2.cpp' +/// CHECK: clang: error: failed to construct the module dependency graph + +//--- main.cpp +import A; + +//--- A1.cpp +export module A; + +//--- A2.cpp +export module A;