Ninja: generate scanning and build rules for C++20 module synthetic targets

This commit is contained in:
Ben Boeckel
2023-02-01 10:40:19 -05:00
parent 80ef50a191
commit 9b9ec70b54
5 changed files with 322 additions and 24 deletions

View File

@@ -2643,7 +2643,9 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
for (cmScanDepInfo const& object : objects) {
for (auto const& p : object.Provides) {
std::string mod;
if (!p.CompiledModulePath.empty()) {
if (cmDyndepCollation::IsBmiOnly(export_info, object.PrimaryOutput)) {
mod = object.PrimaryOutput;
} else if (!p.CompiledModulePath.empty()) {
// The scanner provided the path to the module file.
mod = p.CompiledModulePath;
if (!cmSystemTools::FileIsFullPath(mod)) {
@@ -2714,8 +2716,12 @@ bool cmGlobalNinjaGenerator::WriteDyndepFile(
build.Outputs[0] = this->ConvertToNinjaPath(object.PrimaryOutput);
build.ImplicitOuts.clear();
for (auto const& p : object.Provides) {
build.ImplicitOuts.push_back(
this->ConvertToNinjaPath(mod_files[p.LogicalName].BmiPath));
auto const implicitOut =
this->ConvertToNinjaPath(mod_files[p.LogicalName].BmiPath);
// Ignore the `provides` when the BMI is the output.
if (implicitOut != build.Outputs[0]) {
build.ImplicitOuts.emplace_back(implicitOut);
}
}
build.ImplicitDeps.clear();
for (auto const& r : object.Requires) {

View File

@@ -62,12 +62,15 @@ cmNinjaNormalTargetGenerator::~cmNinjaNormalTargetGenerator() = default;
void cmNinjaNormalTargetGenerator::Generate(const std::string& config)
{
std::string lang = this->GeneratorTarget->GetLinkerLanguage(config);
if (this->TargetLinkLanguage(config).empty()) {
cmSystemTools::Error(
cmStrCat("CMake can not determine linker language for target: ",
this->GetGeneratorTarget()->GetName()));
return;
if (this->GetGeneratorTarget()->GetType() !=
cmStateEnums::INTERFACE_LIBRARY) {
std::string lang = this->GeneratorTarget->GetLinkerLanguage(config);
if (this->TargetLinkLanguage(config).empty()) {
cmSystemTools::Error(
cmStrCat("CMake can not determine linker language for target: ",
this->GetGeneratorTarget()->GetName()));
return;
}
}
// Write the rules for each language.
@@ -87,6 +90,34 @@ void cmNinjaNormalTargetGenerator::Generate(const std::string& config)
if (this->GetGeneratorTarget()->GetType() == cmStateEnums::OBJECT_LIBRARY) {
this->WriteObjectLibStatement(config);
} else if (this->GetGeneratorTarget()->GetType() ==
cmStateEnums::INTERFACE_LIBRARY) {
bool haveCxxModuleSources = false;
if (this->GetGeneratorTarget()->HaveCxx20ModuleSources()) {
haveCxxModuleSources = true;
}
if (!haveCxxModuleSources) {
cmSystemTools::Error(cmStrCat(
"Ninja does not support INTERFACE libraries without C++ module "
"sources as a normal target: ",
this->GetGeneratorTarget()->GetName()));
return;
}
firstForConfig = true;
for (auto const& fileConfig : this->GetConfigNames()) {
if (!this->GetGlobalGenerator()
->GetCrossConfigs(fileConfig)
.count(config)) {
continue;
}
if (haveCxxModuleSources) {
this->WriteCxxModuleLibraryStatement(config, fileConfig,
firstForConfig);
}
firstForConfig = false;
}
} else {
firstForConfig = true;
for (auto const& fileConfig : this->GetConfigNames()) {
@@ -123,12 +154,26 @@ void cmNinjaNormalTargetGenerator::WriteLanguagesRules(
#endif
// Write rules for languages compiled in this target.
std::set<std::string> languages;
std::vector<cmSourceFile const*> sourceFiles;
this->GetGeneratorTarget()->GetObjectSources(sourceFiles, config);
if (this->HaveRequiredLanguages(sourceFiles, languages)) {
for (std::string const& language : languages) {
this->WriteLanguageRules(language, config);
{
std::set<std::string> languages;
std::vector<cmSourceFile const*> sourceFiles;
this->GetGeneratorTarget()->GetObjectSources(sourceFiles, config);
if (this->HaveRequiredLanguages(sourceFiles, languages)) {
for (std::string const& language : languages) {
this->WriteLanguageRules(language, config);
}
}
}
// Write rules for languages in BMI-only rules.
{
std::set<std::string> languages;
std::vector<cmSourceFile const*> sourceFiles;
this->GetGeneratorTarget()->GetCxxModuleSources(sourceFiles, config);
if (this->HaveRequiredLanguages(sourceFiles, languages)) {
for (std::string const& language : languages) {
this->WriteLanguageRules(language, config);
}
}
}
}
@@ -1637,6 +1682,34 @@ void cmNinjaNormalTargetGenerator::WriteObjectLibStatement(
this->GetTargetName(), this->GetGeneratorTarget(), config);
}
void cmNinjaNormalTargetGenerator::WriteCxxModuleLibraryStatement(
const std::string& config, const std::string& /*fileConfig*/,
bool firstForConfig)
{
// TODO: How to use `fileConfig` properly?
// Write a phony output that depends on the scanning output.
{
cmNinjaBuild build("phony");
build.Comment =
cmStrCat("Imported C++ module library ", this->GetTargetName());
this->GetLocalGenerator()->AppendTargetOutputs(this->GetGeneratorTarget(),
build.Outputs, config);
if (firstForConfig) {
this->GetLocalGenerator()->AppendTargetOutputs(
this->GetGeneratorTarget(),
this->GetGlobalGenerator()->GetByproductsForCleanTarget(config),
config);
}
build.ExplicitDeps.emplace_back(this->GetDyndepFilePath("CXX", config));
this->GetGlobalGenerator()->WriteBuild(this->GetCommonFileStream(), build);
}
// Add aliases for the target name.
this->GetGlobalGenerator()->AddTargetAlias(
this->GetTargetName(), this->GetGeneratorTarget(), config);
}
cmGeneratorTarget::Names cmNinjaNormalTargetGenerator::TargetNames(
const std::string& config) const
{

View File

@@ -49,6 +49,9 @@ private:
const std::string& output);
void WriteObjectLibStatement(const std::string& config);
void WriteCxxModuleLibraryStatement(const std::string& config,
const std::string& fileConfig,
bool firstForConfig);
std::vector<std::string> ComputeLinkCmd(const std::string& config);
std::vector<std::string> ComputeDeviceLinkCmd();

View File

@@ -28,6 +28,7 @@
#include "cmGeneratedFileStream.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalCommonGenerator.h"
#include "cmGlobalNinjaGenerator.h"
#include "cmList.h"
#include "cmLocalGenerator.h"
@@ -59,8 +60,13 @@ std::unique_ptr<cmNinjaTargetGenerator> cmNinjaTargetGenerator::New(
case cmStateEnums::OBJECT_LIBRARY:
return cm::make_unique<cmNinjaNormalTargetGenerator>(target);
case cmStateEnums::UTILITY:
case cmStateEnums::INTERFACE_LIBRARY:
if (target->HaveCxx20ModuleSources()) {
return cm::make_unique<cmNinjaNormalTargetGenerator>(target);
}
CM_FALLTHROUGH;
case cmStateEnums::UTILITY:
case cmStateEnums::GLOBAL_TARGET:
return cm::make_unique<cmNinjaUtilityTargetGenerator>(target);
@@ -167,7 +173,7 @@ std::string cmNinjaTargetGenerator::OrderDependsTargetForTarget(
// Refactor it.
std::string cmNinjaTargetGenerator::ComputeFlagsForObject(
cmSourceFile const* source, const std::string& language,
const std::string& config)
const std::string& config, const std::string& objectFileName)
{
std::unordered_map<std::string, std::string> pchSources;
std::vector<std::string> architectures =
@@ -247,6 +253,18 @@ std::string cmNinjaTargetGenerator::ComputeFlagsForObject(
"\nin a file set of type \"", fs->GetType(),
R"(" but the source is not classified as a "CXX" source.)"));
}
if (!this->GeneratorTarget->Target->IsNormal()) {
auto flag = this->GetMakefile()->GetSafeDefinition(
"CMAKE_EXPERIMENTAL_CXX_MODULE_BMI_ONLY_FLAG");
cmRulePlaceholderExpander::RuleVariables compileObjectVars;
compileObjectVars.Object = objectFileName.c_str();
auto rulePlaceholderExpander =
this->GetLocalGenerator()->CreateRulePlaceholderExpander();
rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(),
flag, compileObjectVars);
this->LocalGenerator->AppendCompileOptions(flags, flag);
}
}
return flags;
@@ -394,6 +412,31 @@ std::string cmNinjaTargetGenerator::GetObjectFilePath(
return path;
}
std::string cmNinjaTargetGenerator::GetBmiFilePath(
cmSourceFile const* source, const std::string& config) const
{
std::string path = this->LocalGenerator->GetHomeRelativeOutputPath();
if (!path.empty()) {
path += '/';
}
auto& importedConfigInfo = this->Configs.at(config).ImportedCxxModules;
if (!importedConfigInfo.Initialized()) {
std::string configUpper = cmSystemTools::UpperCase(config);
std::string propName = cmStrCat("IMPORTED_CXX_MODULES_", configUpper);
auto value = this->GeneratorTarget->GetSafeProperty(propName);
importedConfigInfo.Initialize(value);
}
std::string bmiName =
importedConfigInfo.BmiNameForSource(source->GetFullPath());
path += cmStrCat(
this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget),
this->GetGlobalGenerator()->ConfigDirectory(config), '/', bmiName);
return path;
}
std::string cmNinjaTargetGenerator::GetClangTidyReplacementsFilePath(
std::string const& directory, cmSourceFile const& source,
std::string const& config) const
@@ -1027,6 +1070,16 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatements(
}
}
{
std::vector<cmSourceFile const*> bmiOnlySources;
this->GeneratorTarget->GetCxxModuleSources(bmiOnlySources, config);
for (cmSourceFile const* sf : bmiOnlySources) {
this->WriteCxxModuleBmiBuildStatement(sf, config, fileConfig,
firstForConfig);
}
}
for (auto const& langScanningFiles : this->Configs[config].ScanningInfo) {
std::string const& language = langScanningFiles.first;
std::vector<ScanningFiles> const& scanningFiles = langScanningFiles.second;
@@ -1149,22 +1202,22 @@ cmNinjaBuild GetScanBuildStatement(const std::string& ruleName,
scanBuild.Variables["OBJ_FILE"] = objectFileName;
// Tell dependency scanner where to store dyndep intermediate results.
std::string const& ddiFile = cmStrCat(objectFileName, ".ddi");
scanBuild.Variables["DYNDEP_INTERMEDIATE_FILE"] = ddiFile;
std::string ddiFileName = cmStrCat(objectFileName, ".ddi");
scanBuild.Variables["DYNDEP_INTERMEDIATE_FILE"] = ddiFileName;
// Outputs of the scan/preprocessor build statement.
if (compilePP) {
scanBuild.Outputs.push_back(ppFileName);
scanBuild.ImplicitOuts.push_back(ddiFile);
scanBuild.ImplicitOuts.push_back(ddiFileName);
} else {
scanBuild.Outputs.push_back(ddiFile);
scanBuild.Outputs.push_back(ddiFileName);
scanBuild.Variables["PREPROCESSED_OUTPUT_FILE"] = ppFileName;
if (!compilationPreprocesses) {
// Compilation does not preprocess and we are not compiling an
// already-preprocessed source. Make compilation depend on the scan
// results to honor implicit dependencies discovered during scanning
// (such as Fortran INCLUDE directives).
objBuild.ImplicitDeps.emplace_back(ddiFile);
objBuild.ImplicitDeps.emplace_back(ddiFileName);
}
}
@@ -1214,7 +1267,8 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
cmNinjaBuild objBuild(this->LanguageCompilerRule(
language, config, needDyndep ? WithScanning::Yes : WithScanning::No));
cmNinjaVars& vars = objBuild.Variables;
vars["FLAGS"] = this->ComputeFlagsForObject(source, language, config);
vars["FLAGS"] =
this->ComputeFlagsForObject(source, language, config, objectFileName);
vars["DEFINES"] = this->ComputeDefines(source, language, config);
vars["INCLUDES"] = this->ComputeIncludes(source, language, config);
@@ -1545,6 +1599,155 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
}
}
void cmNinjaTargetGenerator::WriteCxxModuleBmiBuildStatement(
cmSourceFile const* source, const std::string& config,
const std::string& fileConfig, bool firstForConfig)
{
std::string const language = source->GetLanguage();
if (language != "CXX"_s) {
this->GetMakefile()->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Source file '", source->GetFullPath(), "' of target '",
this->GetTargetName(), "' is a '", language,
"' source but must be 'CXX' in order to have a BMI build "
"statement generated."));
return;
}
std::string const sourceFilePath = this->GetCompiledSourceNinjaPath(source);
std::string const bmiDir = this->ConvertToNinjaPath(
cmStrCat(this->GeneratorTarget->GetSupportDirectory(),
this->GetGlobalGenerator()->ConfigDirectory(config)));
std::string const bmiFileName =
this->ConvertToNinjaPath(this->GetBmiFilePath(source, config));
std::string const bmiFileDir = cmSystemTools::GetFilenamePath(bmiFileName);
int const commandLineLengthLimit = this->ForceResponseFile() ? -1 : 0;
cmNinjaBuild bmiBuild(
this->LanguageCompilerRule(language, config, WithScanning::Yes));
cmNinjaVars& vars = bmiBuild.Variables;
vars["FLAGS"] =
this->ComputeFlagsForObject(source, language, config, bmiFileName);
vars["DEFINES"] = this->ComputeDefines(source, language, config);
vars["INCLUDES"] = this->ComputeIncludes(source, language, config);
if (this->GetMakefile()->GetSafeDefinition(
cmStrCat("CMAKE_", language, "_DEPFILE_FORMAT")) != "msvc"_s) {
bool replaceExt(false);
if (!language.empty()) {
std::string repVar =
cmStrCat("CMAKE_", language, "_DEPFILE_EXTENSION_REPLACE");
replaceExt = this->Makefile->IsOn(repVar);
}
if (!replaceExt) {
// use original code
vars["DEP_FILE"] = this->GetLocalGenerator()->ConvertToOutputFormat(
cmStrCat(bmiFileName, ".d"), cmOutputConverter::SHELL);
} else {
// Replace the original source file extension with the
// depend file extension.
std::string dependFileName = cmStrCat(
cmSystemTools::GetFilenameWithoutLastExtension(bmiFileName), ".d");
vars["DEP_FILE"] = this->GetLocalGenerator()->ConvertToOutputFormat(
cmStrCat(bmiFileDir, '/', dependFileName), cmOutputConverter::SHELL);
}
}
std::string d =
this->GeneratorTarget->GetClangTidyExportFixesDirectory(language);
if (!d.empty()) {
this->GlobalCommonGenerator->AddClangTidyExportFixesDir(d);
std::string fixesFile =
this->GetClangTidyReplacementsFilePath(d, *source, config);
this->GlobalCommonGenerator->AddClangTidyExportFixesFile(fixesFile);
cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(fixesFile));
fixesFile = this->ConvertToNinjaPath(fixesFile);
vars["CLANG_TIDY_EXPORT_FIXES"] = fixesFile;
}
if (firstForConfig) {
this->ExportObjectCompileCommand(
language, sourceFilePath, bmiDir, bmiFileName, bmiFileDir, vars["FLAGS"],
vars["DEFINES"], vars["INCLUDES"], config);
}
bmiBuild.Outputs.push_back(bmiFileName);
bmiBuild.ExplicitDeps.push_back(sourceFilePath);
std::vector<std::string> depList;
std::vector<std::string> architectures =
this->GeneratorTarget->GetAppleArchs(config, language);
if (architectures.empty()) {
architectures.emplace_back();
}
bmiBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget(config));
// For some cases we scan to dynamically discover dependencies.
std::string modmapFormat;
if (true) {
std::string const modmapFormatVar =
cmStrCat("CMAKE_EXPERIMENTAL_", language, "_MODULE_MAP_FORMAT");
modmapFormat = this->Makefile->GetSafeDefinition(modmapFormatVar);
}
{
bool const compilePPWithDefines = this->CompileWithDefines(language);
std::string scanRuleName = this->LanguageScanRule(language, config);
std::string ppFileName = cmStrCat(bmiFileName, ".ddi.i");
cmNinjaBuild ppBuild = GetScanBuildStatement(
scanRuleName, ppFileName, false, compilePPWithDefines, true, bmiBuild,
vars, bmiFileName, this->LocalGenerator);
ScanningFiles scanningFiles;
if (firstForConfig) {
scanningFiles.ScanningOutput = cmStrCat(bmiFileName, ".ddi");
}
this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(),
ppBuild.Variables);
this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
ppBuild, commandLineLengthLimit);
std::string const dyndep = this->GetDyndepFilePath(language, config);
bmiBuild.OrderOnlyDeps.push_back(dyndep);
vars["dyndep"] = dyndep;
if (!modmapFormat.empty()) {
std::string ddModmapFile = cmStrCat(bmiFileName, ".modmap");
vars["DYNDEP_MODULE_MAP_FILE"] = ddModmapFile;
scanningFiles.ModuleMapFile = std::move(ddModmapFile);
}
if (!scanningFiles.IsEmpty()) {
this->Configs[config].ScanningInfo[language].emplace_back(scanningFiles);
}
}
this->EnsureParentDirectoryExists(bmiFileName);
vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(
bmiDir, cmOutputConverter::SHELL);
vars["OBJECT_FILE_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(
bmiFileDir, cmOutputConverter::SHELL);
this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(),
vars);
this->SetMsvcTargetPdbVariable(vars, config);
bmiBuild.RspFile = cmStrCat(bmiFileName, ".rsp");
this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
bmiBuild, commandLineLengthLimit);
}
void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
const std::string& config)
{
@@ -1605,6 +1808,9 @@ void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
cb.ObjectFilePath = [this](cmSourceFile const* sf, std::string const& cnf) {
return this->GetObjectFilePath(sf, cnf);
};
cb.BmiFilePath = [this](cmSourceFile const* sf, std::string const& cnf) {
return this->GetBmiFilePath(sf, cnf);
};
#if !defined(CMAKE_BOOTSTRAP)
cmDyndepCollation::AddCollationInformation(tdi, this->GeneratorTarget,

View File

@@ -15,6 +15,7 @@
#include "cmCommonTargetGenerator.h"
#include "cmGlobalNinjaGenerator.h"
#include "cmImportedCxxModuleInfo.h"
#include "cmNinjaTypes.h"
#include "cmOSXBundleGenerator.h"
@@ -91,7 +92,8 @@ protected:
*/
std::string ComputeFlagsForObject(cmSourceFile const* source,
const std::string& language,
const std::string& config);
const std::string& config,
const std::string& objectFileName);
void AddIncludeFlags(std::string& flags, std::string const& lang,
const std::string& config) override;
@@ -129,6 +131,8 @@ protected:
/// @return the object file path for the given @a source.
std::string GetObjectFilePath(cmSourceFile const* source,
const std::string& config) const;
std::string GetBmiFilePath(cmSourceFile const* source,
const std::string& config) const;
/// @return the preprocessed source file path for the given @a source.
std::string GetPreprocessedFilePath(cmSourceFile const* source,
@@ -163,6 +167,10 @@ protected:
void WriteObjectBuildStatements(const std::string& config,
const std::string& fileConfig,
bool firstForConfig);
void WriteCxxModuleBmiBuildStatement(cmSourceFile const* source,
const std::string& config,
const std::string& fileConfig,
bool firstForConfig);
void WriteObjectBuildStatement(cmSourceFile const* source,
const std::string& config,
const std::string& fileConfig,
@@ -239,6 +247,8 @@ private:
cmNinjaDeps Objects;
// Dyndep Support
std::map<std::string, std::vector<ScanningFiles>> ScanningInfo;
// Imported C++ module info.
mutable ImportedCxxModuleLookup ImportedCxxModules;
// Swift Support
Json::Value SwiftOutputMap;
std::vector<cmCustomCommand const*> CustomCommands;