mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
Autogen: Add AUTOMOC support for C++ module units
Process C++ module interface and partition units that are members of a FILE_SET of type CXX_MODULES. moc is run on such units and its generated output is compiled as a module implementation unit of the same module, as its own scanned translation unit rather than folded into mocs_compilation.cpp. Macro-less units get an empty placeholder; module units and their dependencies are tracked in the autogen depfile and as byproducts for correct incremental rebuilds. This requires Qt 6.13, whose moc supports C++ module units. With older Qt nothing can be generated for a module unit, so a meta-object macro found in one is reported as an error rather than left to fail later in the compiler or the linker. Such a unit is no longer handled as an ordinary source, which also means its moc include directives are not honored and AUTOUIC does not process it. moc rejects a meta-object macro in a module implementation unit or a private module fragment, and AUTOMOC does not work around that.
This commit is contained in:
@@ -107,6 +107,29 @@ be generated in a different location than if it was not included. This is
|
||||
described in the section `Output file location`_.
|
||||
|
||||
|
||||
C++ module unit processing
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
C++ module interface and partition units that are members of a
|
||||
``FILE_SET`` of type ``CXX_MODULES`` (see :prop_tgt:`CXX_MODULE_SETS`)
|
||||
are scanned by ``AUTOMOC`` for a Qt macro from
|
||||
:prop_tgt:`AUTOMOC_MACRO_NAMES`, just like other source files. Running
|
||||
``moc`` on them requires Qt 6.13 or newer, whose ``moc`` supports C++
|
||||
module units. With older Qt, a Qt macro found in such a unit is
|
||||
reported as an error, and a unit without one is left alone.
|
||||
|
||||
If a Qt macro is found, ``moc`` is run on the module unit and its
|
||||
generated output is compiled as its own translation unit, as a module
|
||||
implementation unit of the same module. This output participates in
|
||||
the target's C++ module dependency scanning like any other source. It
|
||||
is not added to the ``mocs_compilation.cpp`` file described in
|
||||
`Output file location`_.
|
||||
|
||||
Module implementation units and private module fragments are not
|
||||
supported: ``moc`` rejects a Qt macro found in either of these, and
|
||||
``AUTOMOC`` does not work around that restriction.
|
||||
|
||||
|
||||
Output file location
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
automoc-cxx-modules
|
||||
-------------------
|
||||
|
||||
* :prop_tgt:`AUTOMOC` now processes C++ module interface and partition
|
||||
units that are members of a ``FILE_SET`` of type ``CXX_MODULES``.
|
||||
``moc`` is run on such units and its generated output is compiled as a
|
||||
module implementation unit of the same module. This requires Qt 6.13
|
||||
or newer, whose ``moc`` supports C++ module units.
|
||||
@@ -33,10 +33,12 @@
|
||||
#include "cmCustomCommandLines.h"
|
||||
#include "cmDiagnostics.h"
|
||||
#include "cmEvaluatedTargetProperty.h"
|
||||
#include "cmFileSetMetadata.h"
|
||||
#include "cmGenExContext.h"
|
||||
#include "cmGeneratedFileStream.h"
|
||||
#include "cmGeneratorExpression.h"
|
||||
#include "cmGeneratorExpressionDAGChecker.h"
|
||||
#include "cmGeneratorFileSet.h"
|
||||
#include "cmGeneratorTarget.h"
|
||||
#include "cmGlobalGenerator.h"
|
||||
#include "cmLinkItem.h"
|
||||
@@ -1009,6 +1011,17 @@ bool cmQtAutoGenInitializer::InitScanFiles()
|
||||
this->AutogenTarget.Sources.emplace(muf->SF, std::move(muf));
|
||||
};
|
||||
|
||||
auto addMUModuleUnit = [this](MUFileHandle&& muf) {
|
||||
if (muf->SkipMoc) {
|
||||
return;
|
||||
}
|
||||
// AUTOUIC is not wired up for module units (the uic eval pass ignores
|
||||
// the ModuleUnits collection), so don't carry a UicIt flag nothing acts
|
||||
// on.
|
||||
muf->UicIt = false;
|
||||
this->AutogenTarget.ModuleUnits.emplace(muf->SF, std::move(muf));
|
||||
};
|
||||
|
||||
// Scan through target files
|
||||
{
|
||||
// Scan through target files
|
||||
@@ -1020,7 +1033,15 @@ bool cmQtAutoGenInitializer::InitScanFiles()
|
||||
|
||||
// Register files that will be scanned by moc or uic
|
||||
if (this->MocOrUicEnabled()) {
|
||||
if (cm->IsAHeaderExtension(extLower)) {
|
||||
// Query one config only: file-set membership can differ per
|
||||
// config, but a per-config module unit kind is not modeled here.
|
||||
cmGeneratorFileSet const* fileSet =
|
||||
this->GenTarget->GetFileSetForSource(this->ConfigDefault,
|
||||
acs.Source);
|
||||
if (fileSet &&
|
||||
fileSet->GetType() == cm::FileSetMetadata::CXX_MODULES) {
|
||||
addMUModuleUnit(makeMUFile(acs.Source, fullPath, acs.Configs, true));
|
||||
} else if (cm->IsAHeaderExtension(extLower)) {
|
||||
addMUHeader(makeMUFile(acs.Source, fullPath, acs.Configs, true),
|
||||
extLower);
|
||||
} else if (cm->IsACLikeSourceExtension(extLower)) {
|
||||
@@ -1406,6 +1427,68 @@ bool cmQtAutoGenInitializer::InitAutogenTarget()
|
||||
} else {
|
||||
autogenByproducts.push_back(this->Moc.CompilationFileGenex);
|
||||
}
|
||||
|
||||
// Module-unit moc outputs are implementation units ("module M;") that
|
||||
// are compiled individually rather than folded into
|
||||
// mocs_compilation.cpp, and must be scanned so dyndep can order them
|
||||
// after the module's BMI.
|
||||
// Sort by path so GetMocBuildPath's dedup-suffix assignment and the
|
||||
// AddSource order below do not depend on unordered_map hash order.
|
||||
// moc can only process C++ module units since Qt 6.13. With older Qt
|
||||
// there is no moc output to compile, and cmQtAutoMocUic reports any
|
||||
// meta-object macro found in such a unit instead.
|
||||
std::vector<MUFile*> moduleUnits;
|
||||
if (this->QtVersion >= IntegerVersion(6, 13)) {
|
||||
moduleUnits.reserve(this->AutogenTarget.ModuleUnits.size());
|
||||
for (auto const& pair : this->AutogenTarget.ModuleUnits) {
|
||||
moduleUnits.push_back(pair.second.get());
|
||||
}
|
||||
std::sort(moduleUnits.begin(), moduleUnits.end(),
|
||||
[](MUFile const* a, MUFile const* b) {
|
||||
return (a->FullPath < b->FullPath);
|
||||
});
|
||||
}
|
||||
for (MUFile* mufPtr : moduleUnits) {
|
||||
MUFile& muf = *mufPtr;
|
||||
if (!muf.MocIt) {
|
||||
continue;
|
||||
}
|
||||
std::string const& mocBuildPath = this->GetMocBuildPath(muf);
|
||||
if (!this->MultiConfig || this->GlobalGen->IsXcode()) {
|
||||
std::string const outPath =
|
||||
cmStrCat(this->Dir.Include.Default, '/', mocBuildPath);
|
||||
cmSourceFile* sf = this->RegisterGeneratedSource(outPath, true);
|
||||
// A PCH force-include would inject declarations ahead of the
|
||||
// module implementation unit's "module M;", which may only be
|
||||
// preceded by comments and preprocessor directives.
|
||||
sf->SetProperty("SKIP_PRECOMPILE_HEADERS", "ON");
|
||||
this->GenTarget->AddSource(outPath);
|
||||
// Declare as a byproduct so Ninja re-stats it after autogen reruns.
|
||||
if (useDepfile) {
|
||||
timestampByproducts.push_back(outPath);
|
||||
} else {
|
||||
autogenByproducts.push_back(outPath);
|
||||
}
|
||||
} else {
|
||||
for (auto const& cfg : this->ConfigsList) {
|
||||
std::string const outPath =
|
||||
cmStrCat(this->Dir.Include.Config.at(cfg), '/', mocBuildPath);
|
||||
cmSourceFile* sf = this->RegisterGeneratedSource(outPath, true);
|
||||
// A PCH force-include would inject declarations ahead of the
|
||||
// module implementation unit's "module M;", which may only be
|
||||
// preceded by comments and preprocessor directives.
|
||||
sf->SetProperty("SKIP_PRECOMPILE_HEADERS", "ON");
|
||||
this->GenTarget->AddSource(
|
||||
cmStrCat("$<$<CONFIG:"_s, cfg, ">:"_s, outPath, ">"_s));
|
||||
// Declare as a byproduct so Ninja re-stats it after autogen reruns.
|
||||
if (useDepfile) {
|
||||
timestampByproducts.push_back(outPath);
|
||||
} else {
|
||||
autogenByproducts.push_back(outPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->Uic.Enabled) {
|
||||
@@ -1906,6 +1989,7 @@ bool cmQtAutoGenInitializer::SetupWriteAutogenInfo()
|
||||
std::set<std::string> uic_skip;
|
||||
std::vector<MUFile const*> headers;
|
||||
std::vector<MUFile const*> sources;
|
||||
std::vector<MUFile const*> moduleUnits;
|
||||
|
||||
// Filter headers
|
||||
{
|
||||
@@ -1955,6 +2039,21 @@ bool cmQtAutoGenInitializer::SetupWriteAutogenInfo()
|
||||
});
|
||||
}
|
||||
|
||||
// Filter module units
|
||||
{
|
||||
moduleUnits.reserve(this->AutogenTarget.ModuleUnits.size());
|
||||
for (auto const& pair : this->AutogenTarget.ModuleUnits) {
|
||||
MUFile const* const muf = pair.second.get();
|
||||
if (muf->MocIt) {
|
||||
moduleUnits.emplace_back(muf);
|
||||
}
|
||||
}
|
||||
std::sort(moduleUnits.begin(), moduleUnits.end(),
|
||||
[](MUFile const* a, MUFile const* b) {
|
||||
return (a->FullPath < b->FullPath);
|
||||
});
|
||||
}
|
||||
|
||||
// Info writer
|
||||
InfoWriter info;
|
||||
|
||||
@@ -2016,6 +2115,15 @@ bool cmQtAutoGenInitializer::SetupWriteAutogenInfo()
|
||||
jval[1u] = cmStrCat(muf->MocIt ? 'M' : 'm', muf->UicIt ? 'U' : 'u');
|
||||
jval[2u] = cfgArray(muf->Configs);
|
||||
});
|
||||
info.SetArrayArray("CXX_MODULE_UNITS", moduleUnits,
|
||||
[this, &cfgArray](Json::Value& jval, MUFile const* muf) {
|
||||
jval.resize(4u);
|
||||
jval[0u] = muf->FullPath;
|
||||
jval[1u] = cmStrCat(muf->MocIt ? 'M' : 'm',
|
||||
muf->UicIt ? 'U' : 'u');
|
||||
jval[2u] = cfgArray(muf->Configs);
|
||||
jval[3u] = this->GetMocBuildPath(*muf);
|
||||
});
|
||||
|
||||
// Write moc settings
|
||||
if (this->Moc.Enabled) {
|
||||
@@ -2170,7 +2278,7 @@ bool cmQtAutoGenInitializer::SetupWriteRccInfo()
|
||||
}
|
||||
|
||||
cmSourceFile* cmQtAutoGenInitializer::RegisterGeneratedSource(
|
||||
std::string const& filename)
|
||||
std::string const& filename, bool scanForModules)
|
||||
{
|
||||
cmSourceFile* gFile = this->Makefile->GetOrCreateSource(filename, true);
|
||||
gFile->SetSpecialSourceType(
|
||||
@@ -2178,7 +2286,7 @@ cmSourceFile* cmQtAutoGenInitializer::RegisterGeneratedSource(
|
||||
gFile->MarkAsGenerated();
|
||||
gFile->SetProperty("SKIP_AUTOGEN", "1");
|
||||
gFile->SetProperty("SKIP_LINTING", "ON");
|
||||
gFile->SetProperty("CXX_SCAN_FOR_MODULES", "0");
|
||||
gFile->SetProperty("CXX_SCAN_FOR_MODULES", scanForModules ? "1" : "0");
|
||||
return gFile;
|
||||
}
|
||||
|
||||
@@ -2457,9 +2565,15 @@ cmQtAutoGenInitializer::GetQtVersion(cmGeneratorTarget const* target,
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string cmQtAutoGenInitializer::GetMocBuildPath(MUFile const& muf)
|
||||
std::string const& cmQtAutoGenInitializer::GetMocBuildPath(MUFile const& muf)
|
||||
{
|
||||
std::string res;
|
||||
// The de-duplication below is not idempotent: without memoizing, a second
|
||||
// call for the same file would hand out a different path.
|
||||
if (!muf.MocBuildPath.empty()) {
|
||||
return muf.MocBuildPath;
|
||||
}
|
||||
|
||||
std::string& res = muf.MocBuildPath;
|
||||
if (!muf.MocIt) {
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ public:
|
||||
bool SkipUic = false;
|
||||
bool MocIt = false;
|
||||
bool UicIt = false;
|
||||
// Memoized GetMocBuildPath() result. Mutable because the info file
|
||||
// writer reaches this through const references.
|
||||
mutable std::string MocBuildPath;
|
||||
};
|
||||
using MUFileHandle = std::unique_ptr<MUFile>;
|
||||
|
||||
@@ -119,7 +122,8 @@ private:
|
||||
bool SetupWriteAutogenInfo();
|
||||
bool SetupWriteRccInfo();
|
||||
|
||||
cmSourceFile* RegisterGeneratedSource(std::string const& filename);
|
||||
cmSourceFile* RegisterGeneratedSource(std::string const& filename,
|
||||
bool scanForModules = false);
|
||||
cmSourceFile* AddGeneratedSource(std::string const& filename,
|
||||
GenVarsT const& genVars,
|
||||
bool prepend = false);
|
||||
@@ -140,7 +144,7 @@ private:
|
||||
std::string const& fileName);
|
||||
void ConfigFileClean(ConfigString& configString);
|
||||
|
||||
std::string GetMocBuildPath(MUFile const& muf);
|
||||
std::string const& GetMocBuildPath(MUFile const& muf);
|
||||
|
||||
bool GetQtExecutable(GenVarsT& genVars, std::string const& executable,
|
||||
bool ignoreMissingTarget) const;
|
||||
@@ -204,6 +208,7 @@ private:
|
||||
// Sources to process
|
||||
std::unordered_map<cmSourceFile*, MUFileHandle> Headers;
|
||||
std::unordered_map<cmSourceFile*, MUFileHandle> Sources;
|
||||
std::unordered_map<cmSourceFile*, MUFileHandle> ModuleUnits;
|
||||
std::vector<MUFile*> FilesGenerated;
|
||||
std::vector<cmSourceFile*> CMP0100HeadersWarn;
|
||||
} AutogenTarget;
|
||||
|
||||
+151
-37
@@ -125,6 +125,15 @@ public:
|
||||
std::unordered_map<std::string, FileHandleT> Map_;
|
||||
};
|
||||
|
||||
/** Kind of source file processed by moc. Headers and module units carry
|
||||
a moc build path in the info file, sources do not. */
|
||||
enum class SourceFileKind
|
||||
{
|
||||
Header,
|
||||
Source,
|
||||
ModuleUnit,
|
||||
};
|
||||
|
||||
/** Source file data. */
|
||||
class SourceFileT
|
||||
{
|
||||
@@ -138,7 +147,7 @@ public:
|
||||
cmFileTime FileTime;
|
||||
ParseCacheT::FileHandleT ParseData;
|
||||
std::string BuildPath;
|
||||
bool IsHeader = false;
|
||||
SourceFileKind Kind = SourceFileKind::Source;
|
||||
bool Moc = false;
|
||||
bool Uic = false;
|
||||
};
|
||||
@@ -201,6 +210,7 @@ public:
|
||||
// -- Sources
|
||||
SourceFileMapT Headers;
|
||||
SourceFileMapT Sources;
|
||||
SourceFileMapT ModuleUnits;
|
||||
};
|
||||
|
||||
/** Moc settings. */
|
||||
@@ -249,6 +259,8 @@ public:
|
||||
// -- Mappings
|
||||
MappingMapT HeaderMappings;
|
||||
MappingMapT SourceMappings;
|
||||
// Module unit outputs are implementation units, kept out of CompFiles.
|
||||
MappingMapT ModuleMappings;
|
||||
MappingMapT Includes;
|
||||
// -- Discovered files
|
||||
SourceFileMapT HeadersDiscovered;
|
||||
@@ -420,6 +432,7 @@ public:
|
||||
{
|
||||
void Process() override;
|
||||
bool EvalHeader(SourceFileHandleT source);
|
||||
bool EvalModuleUnit(SourceFileHandleT source);
|
||||
bool EvalSource(SourceFileHandleT const& source);
|
||||
bool FindIncludedHeader(SourceFileHandleT& headerHandle,
|
||||
cm::string_view includerDir,
|
||||
@@ -560,19 +573,11 @@ public:
|
||||
void CreateParseJobs(SourceFileMapT const& sourceMap);
|
||||
|
||||
private:
|
||||
// Kind of source-entry array in the info file. Headers are 4-tuples carrying
|
||||
// a moc build path; sources are 3-tuples without one.
|
||||
enum class SourceEntryKind
|
||||
{
|
||||
Header,
|
||||
Source,
|
||||
};
|
||||
|
||||
// -- Abstract processing interface
|
||||
bool InitFromInfo(InfoT const& info) override;
|
||||
// Read the source-entry array of the given kind from the info file into the
|
||||
// matching source map.
|
||||
bool InitSourceEntries(InfoT const& info, SourceEntryKind kind);
|
||||
bool InitSourceEntries(InfoT const& info, SourceFileKind kind);
|
||||
void InitJobs();
|
||||
bool Process() override;
|
||||
// -- Settings file
|
||||
@@ -1191,6 +1196,12 @@ void cmQtAutoMocUicT::JobEvalCacheMocT::Process()
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Evaluate module units
|
||||
for (auto const& pair : this->BaseEval().ModuleUnits) {
|
||||
if (!this->EvalModuleUnit(pair.second)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Evaluate sources
|
||||
for (auto const& pair : this->BaseEval().Sources) {
|
||||
if (!this->EvalSource(pair.second)) {
|
||||
@@ -1212,8 +1223,11 @@ bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalHeader(SourceFileHandleT source)
|
||||
MappingHandleT handle = std::make_shared<MappingT>();
|
||||
handle->SourceFile = std::move(source);
|
||||
|
||||
// Absolute build path
|
||||
if (this->BaseConst().MultiConfig) {
|
||||
// A module unit's moc output is added as a target source at the include
|
||||
// dir for every config, so it must be written there too, regardless of
|
||||
// single- vs multi-config.
|
||||
if (sourceFile.Kind == SourceFileKind::ModuleUnit ||
|
||||
this->BaseConst().MultiConfig) {
|
||||
handle->OutputFile =
|
||||
this->Gen()->AbsoluteIncludePath(sourceFile.BuildPath);
|
||||
} else {
|
||||
@@ -1223,11 +1237,55 @@ bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalHeader(SourceFileHandleT source)
|
||||
|
||||
// Register mapping in headers map
|
||||
this->RegisterMapping(handle);
|
||||
} else if (sourceFile.Kind == SourceFileKind::ModuleUnit) {
|
||||
// A CXX_MODULES member without a meta-object macro still needs its
|
||||
// registered moc output to exist; an empty TU is valid and scans clean.
|
||||
std::string const outputFile =
|
||||
this->Gen()->AbsoluteIncludePath(sourceFile.BuildPath);
|
||||
std::string const placeholder =
|
||||
"enum some_compilers { need_more_than_nothing };\n";
|
||||
if (cmQtAutoGenerator::FileDiffers(outputFile, placeholder)) {
|
||||
if (!cmQtAutoGenerator::FileWrite(outputFile, placeholder)) {
|
||||
this->LogError(GenT::MOC,
|
||||
cmStrCat("Writing MOC placeholder ",
|
||||
this->MessagePath(outputFile), " failed."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalModuleUnit(
|
||||
SourceFileHandleT source)
|
||||
{
|
||||
if (this->BaseConst().QtVersion >= IntegerVersion(6, 13)) {
|
||||
return this->EvalHeader(std::move(source));
|
||||
}
|
||||
|
||||
// Older moc cannot process C++ module units at all, so nothing can be
|
||||
// generated for this one. Report a meta-object macro in it instead of
|
||||
// leaving the user with a missing meta-object at link time.
|
||||
SourceFileT const& sourceFile = *source;
|
||||
auto const& parseData = sourceFile.ParseData->Moc;
|
||||
if (!sourceFile.Moc || parseData.Macro.empty()) {
|
||||
return true;
|
||||
}
|
||||
this->LogError(
|
||||
GenT::MOC,
|
||||
cmStrCat(this->MessagePath(sourceFile.FileName), "\ncontains a ",
|
||||
Quoted(parseData.Macro),
|
||||
" macro, but it is a C++ module unit and moc from Qt ",
|
||||
this->BaseConst().QtVersion.Major, '.',
|
||||
this->BaseConst().QtVersion.Minor,
|
||||
" cannot process those.\nAUTOMOC handles meta-object macros in"
|
||||
" C++ module units with Qt 6.13 or newer.\nConsider to\n"
|
||||
" - move the affected class out of the module unit\n"
|
||||
" - enable SKIP_AUTOMOC for this file"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalSource(
|
||||
SourceFileHandleT const& source)
|
||||
{
|
||||
@@ -1475,7 +1533,7 @@ bool cmQtAutoMocUicT::JobEvalCacheMocT::FindIncludedHeader(
|
||||
if (!handle) {
|
||||
handle = std::make_shared<SourceFileT>(testPath);
|
||||
handle->FileTime = fileTime;
|
||||
handle->IsHeader = true;
|
||||
handle->Kind = SourceFileKind::Header;
|
||||
handle->Moc = true;
|
||||
}
|
||||
headerHandle = handle;
|
||||
@@ -1553,11 +1611,23 @@ bool cmQtAutoMocUicT::JobEvalCacheMocT::RegisterIncluded(
|
||||
void cmQtAutoMocUicT::JobEvalCacheMocT::RegisterMapping(
|
||||
MappingHandleT mappingHandle) const
|
||||
{
|
||||
auto& regMap = mappingHandle->SourceFile->IsHeader
|
||||
? this->MocEval().HeaderMappings
|
||||
: this->MocEval().SourceMappings;
|
||||
// Module units must never land in HeaderMappings: that map feeds
|
||||
// CompFiles/mocs_compilation.cpp, and amalgamating multiple "module M;"
|
||||
// implementation units into one TU is invalid.
|
||||
MappingMapT* regMap = nullptr;
|
||||
switch (mappingHandle->SourceFile->Kind) {
|
||||
case SourceFileKind::Header:
|
||||
regMap = &this->MocEval().HeaderMappings;
|
||||
break;
|
||||
case SourceFileKind::Source:
|
||||
regMap = &this->MocEval().SourceMappings;
|
||||
break;
|
||||
case SourceFileKind::ModuleUnit:
|
||||
regMap = &this->MocEval().ModuleMappings;
|
||||
break;
|
||||
}
|
||||
// Check if source file already gets mapped
|
||||
auto& regHandle = regMap[mappingHandle->SourceFile->FileName];
|
||||
auto& regHandle = (*regMap)[mappingHandle->SourceFile->FileName];
|
||||
if (!regHandle) {
|
||||
// Yet unknown mapping
|
||||
regHandle = std::move(mappingHandle);
|
||||
@@ -1773,6 +1843,14 @@ void cmQtAutoMocUicT::JobProbeDepsMocT::Process()
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Create moc module unit jobs. Never added to CompFiles: each output is
|
||||
// a module implementation unit and must be compiled as its own TU.
|
||||
for (auto const& pair : this->MocEval().ModuleMappings) {
|
||||
if (!this->Generate(pair.second, false)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool cmQtAutoMocUicT::JobProbeDepsMocT::Generate(MappingHandleT const& mapping,
|
||||
@@ -2067,8 +2145,10 @@ void cmQtAutoMocUicT::JobCompileMocT::Process()
|
||||
cmd.emplace_back("--include");
|
||||
cmd.push_back(this->MocConst().PredefsFileAbs);
|
||||
}
|
||||
// Add path prefix on demand
|
||||
if (this->MocConst().PathPrefix && this->Mapping->SourceFile->IsHeader) {
|
||||
// Add path prefix on demand. Module units are compiled directly, not
|
||||
// included, so the prefix is meaningless for them.
|
||||
if (this->MocConst().PathPrefix &&
|
||||
this->Mapping->SourceFile->Kind == SourceFileKind::Header) {
|
||||
for (std::string const& dir : this->MocConst().IncludePaths) {
|
||||
cm::string_view prefix = sourceFile;
|
||||
if (cmHasPrefix(prefix, dir)) {
|
||||
@@ -2278,9 +2358,9 @@ std::vector<std::string>
|
||||
cmQtAutoMocUicT::JobDepFilesMergeT::initialDependencies() const
|
||||
{
|
||||
std::vector<std::string> dependencies;
|
||||
dependencies.reserve(this->BaseConst().ListFiles.size() +
|
||||
this->BaseEval().Headers.size() +
|
||||
this->BaseEval().Sources.size());
|
||||
dependencies.reserve(
|
||||
this->BaseConst().ListFiles.size() + this->BaseEval().Headers.size() +
|
||||
this->BaseEval().Sources.size() + this->BaseEval().ModuleUnits.size());
|
||||
cm::append(dependencies, this->BaseConst().ListFiles);
|
||||
auto append_file_path =
|
||||
[&dependencies](SourceFileMapT::value_type const& p) {
|
||||
@@ -2290,6 +2370,9 @@ cmQtAutoMocUicT::JobDepFilesMergeT::initialDependencies() const
|
||||
this->BaseEval().Headers.end(), append_file_path);
|
||||
std::for_each(this->BaseEval().Sources.begin(),
|
||||
this->BaseEval().Sources.end(), append_file_path);
|
||||
// Module unit sources must also trigger autogen reruns.
|
||||
std::for_each(this->BaseEval().ModuleUnits.begin(),
|
||||
this->BaseEval().ModuleUnits.end(), append_file_path);
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@@ -2325,6 +2408,10 @@ void cmQtAutoMocUicT::JobDepFilesMergeT::Process()
|
||||
this->MocEval().HeaderMappings.end(), processMappingEntry);
|
||||
std::for_each(this->MocEval().SourceMappings.begin(),
|
||||
this->MocEval().SourceMappings.end(), processMappingEntry);
|
||||
// Module units also produce a moc ".d" file that must feed the
|
||||
// merged depfile, even though they are excluded from CompFiles.
|
||||
std::for_each(this->MocEval().ModuleMappings.begin(),
|
||||
this->MocEval().ModuleMappings.end(), processMappingEntry);
|
||||
|
||||
// Remove SKIP_AUTOMOC files.
|
||||
// Also remove AUTOUIC header files to avoid cyclic dependency.
|
||||
@@ -2391,33 +2478,53 @@ cmQtAutoMocUicT::cmQtAutoMocUicT()
|
||||
}
|
||||
cmQtAutoMocUicT::~cmQtAutoMocUicT() = default;
|
||||
|
||||
bool cmQtAutoMocUicT::InitSourceEntries(InfoT const& info,
|
||||
SourceEntryKind kind)
|
||||
bool cmQtAutoMocUicT::InitSourceEntries(InfoT const& info, SourceFileKind kind)
|
||||
{
|
||||
cm::string_view key;
|
||||
cm::string_view fileNoun;
|
||||
bool optional = false;
|
||||
SourceFileMapT* map = nullptr;
|
||||
// Sources are 3-tuples of name, flags and configs. Headers and module
|
||||
// units add a moc build path: ahead of the configs for headers, behind
|
||||
// them for module units. buildPathIndex is read only when hasBuildPath.
|
||||
bool hasBuildPath = false;
|
||||
Json::ArrayIndex entrySize = 3u;
|
||||
Json::ArrayIndex configsIndex = 2u;
|
||||
Json::ArrayIndex buildPathIndex = 0u;
|
||||
switch (kind) {
|
||||
case SourceEntryKind::Header:
|
||||
case SourceFileKind::Header:
|
||||
key = "HEADERS";
|
||||
fileNoun = "header";
|
||||
map = &this->BaseEval().Headers;
|
||||
hasBuildPath = true;
|
||||
entrySize = 4u;
|
||||
configsIndex = 3u;
|
||||
buildPathIndex = 2u;
|
||||
break;
|
||||
case SourceEntryKind::Source:
|
||||
case SourceFileKind::Source:
|
||||
key = "SOURCES";
|
||||
fileNoun = "source";
|
||||
map = &this->BaseEval().Sources;
|
||||
break;
|
||||
case SourceFileKind::ModuleUnit:
|
||||
key = "CXX_MODULE_UNITS";
|
||||
fileNoun = "module unit";
|
||||
optional = true;
|
||||
map = &this->BaseEval().ModuleUnits;
|
||||
hasBuildPath = true;
|
||||
entrySize = 4u;
|
||||
configsIndex = 2u;
|
||||
buildPathIndex = 3u;
|
||||
break;
|
||||
}
|
||||
// Sources are 3-tuples; headers add a moc build path.
|
||||
bool const isHeader = kind != SourceEntryKind::Source;
|
||||
|
||||
Json::Value const& entries = info.GetValue(std::string(key));
|
||||
if (optional && entries.isNull()) {
|
||||
return true;
|
||||
}
|
||||
if (!entries.isArray()) {
|
||||
return info.LogError(cmStrCat(key, " JSON value is not an array."));
|
||||
}
|
||||
Json::ArrayIndex const entrySize = isHeader ? 4u : 3u;
|
||||
Json::ArrayIndex const configsIndex = isHeader ? 3u : 2u;
|
||||
Json::ArrayIndex const arraySize = entries.size();
|
||||
for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
|
||||
auto testEntry = [&info, key, ii](bool test, cm::string_view msg) -> bool {
|
||||
@@ -2444,8 +2551,8 @@ bool cmQtAutoMocUicT::InitSourceEntries(InfoT const& info,
|
||||
"JSON value for configs is not null or array.")) {
|
||||
return false;
|
||||
}
|
||||
if (isHeader &&
|
||||
testEntry(entry[2u].isString(),
|
||||
if (hasBuildPath &&
|
||||
testEntry(entry[buildPathIndex].isString(),
|
||||
"JSON value for build path is not a string.")) {
|
||||
return false;
|
||||
}
|
||||
@@ -2481,11 +2588,11 @@ bool cmQtAutoMocUicT::InitSourceEntries(InfoT const& info,
|
||||
|
||||
SourceFileHandleT sourceHandle = std::make_shared<SourceFileT>(name);
|
||||
sourceHandle->FileTime = fileTime;
|
||||
sourceHandle->IsHeader = isHeader;
|
||||
sourceHandle->Kind = kind;
|
||||
sourceHandle->Moc = (flags[0] == 'M');
|
||||
sourceHandle->Uic = (flags[1] == 'U');
|
||||
if (isHeader && sourceHandle->Moc && this->MocConst().Enabled) {
|
||||
std::string build = entry[2u].asString();
|
||||
if (hasBuildPath && sourceHandle->Moc && this->MocConst().Enabled) {
|
||||
std::string build = entry[buildPathIndex].asString();
|
||||
if (build.empty()) {
|
||||
return info.LogError(cmStrCat("The ", fileNoun, " file ",
|
||||
this->MessagePath(name),
|
||||
@@ -2734,12 +2841,17 @@ bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info)
|
||||
}
|
||||
|
||||
// -- Headers
|
||||
if (!this->InitSourceEntries(info, SourceEntryKind::Header)) {
|
||||
if (!this->InitSourceEntries(info, SourceFileKind::Header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- C++ module units
|
||||
if (!this->InitSourceEntries(info, SourceFileKind::ModuleUnit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- Sources
|
||||
if (!this->InitSourceEntries(info, SourceEntryKind::Source)) {
|
||||
if (!this->InitSourceEntries(info, SourceFileKind::Source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2813,6 +2925,8 @@ void cmQtAutoMocUicT::InitJobs()
|
||||
|
||||
// Add header parse jobs
|
||||
this->CreateParseJobs<JobParseHeaderT>(this->BaseEval().Headers);
|
||||
// Add module unit parse jobs (header-style: macro scan, no moc_/.moc scan)
|
||||
this->CreateParseJobs<JobParseHeaderT>(this->BaseEval().ModuleUnits);
|
||||
// Add source parse jobs
|
||||
this->CreateParseJobs<JobParseSourceT>(this->BaseEval().Sources);
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# Driven via RunCMake_TEST_SOURCE_DIR pointing at cxx_modules/.
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1 @@
|
||||
is not supported in a module implementation unit
|
||||
@@ -0,0 +1 @@
|
||||
# Driven via RunCMake_TEST_SOURCE_DIR pointing at cxx_modules_impl_unit/.
|
||||
@@ -0,0 +1 @@
|
||||
# Driven via RunCMake_TEST_SOURCE_DIR pointing at cxx_modules_no_macro/.
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1 @@
|
||||
contains a "Q_OBJECT" macro, but it is a C\+\+ module unit and moc from Qt [0-9]+\.[0-9]+ cannot process those\.
|
||||
@@ -0,0 +1 @@
|
||||
# Driven via RunCMake_TEST_SOURCE_DIR pointing at cxx_modules/.
|
||||
@@ -0,0 +1,22 @@
|
||||
enable_language(CXX)
|
||||
|
||||
set(info "")
|
||||
foreach(var
|
||||
CMAKE_CXX_COMPILE_FEATURES
|
||||
CMAKE_MAKE_PROGRAM
|
||||
)
|
||||
if(DEFINED ${var})
|
||||
string(APPEND info "set(${var} \"${${var}}\")\n")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# A compiler can only take part in C++ module dependency scanning if it has a
|
||||
# scandep rule. Report it as a flag: the rule itself contains characters that
|
||||
# would not survive a round trip through this file.
|
||||
if(CMAKE_CXX_SCANDEP_SOURCE)
|
||||
string(APPEND info "set(have_cxx_scandep 1)\n")
|
||||
else()
|
||||
string(APPEND info "set(have_cxx_scandep 0)\n")
|
||||
endif()
|
||||
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/info.cmake" "${info}")
|
||||
@@ -10,4 +10,132 @@ if (DEFINED with_qt_version)
|
||||
if (RunCMake_GENERATOR MATCHES "(Ninja|Makefiles|Visual Studio)")
|
||||
run_cmake(AutoMocIncludeDirectoriesShort)
|
||||
endif ()
|
||||
|
||||
# Detect information from the toolchain:
|
||||
# - CMAKE_CXX_COMPILE_FEATURES
|
||||
# - CMAKE_MAKE_PROGRAM
|
||||
run_cmake(Inspect)
|
||||
include("${RunCMake_BINARY_DIR}/Inspect-build/info.cmake")
|
||||
|
||||
# Building C++ module units at all needs a toolchain that can scan C++
|
||||
# module dependencies and a generator that can use the result. The scandep
|
||||
# rule covers the compiler side: it is absent both for compilers that cannot
|
||||
# scan at all, such as AppleClang, and for versions that are too old.
|
||||
set(cxx_modules_supported 0)
|
||||
if (have_cxx_scandep AND "cxx_std_20" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
|
||||
if (RunCMake_GENERATOR MATCHES "Ninja")
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_MAKE_PROGRAM}" --version
|
||||
RESULT_VARIABLE _res
|
||||
OUTPUT_VARIABLE _ninja_version
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if (NOT _res AND _ninja_version VERSION_GREATER_EQUAL "1.11")
|
||||
set(cxx_modules_supported 1)
|
||||
endif ()
|
||||
elseif (RunCMake_GENERATOR MATCHES "Visual Studio")
|
||||
set(cxx_modules_supported 1)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# AUTOMOC processes C++ module units only with Qt 6.13 or newer, whose moc
|
||||
# supports them.
|
||||
set(automoc_modules_supported 0)
|
||||
if (cxx_modules_supported AND QtCore_VERSION VERSION_GREATER_EQUAL "6.13")
|
||||
set(automoc_modules_supported 1)
|
||||
endif ()
|
||||
|
||||
# A successful build IS the assertion: main.cpp uses the module classes, so
|
||||
# if AUTOMOC did not moc the module units into their own module-attached
|
||||
# translation units, the meta-object symbols would be undefined at link time.
|
||||
# This exercises moc's compiler-predefines (--include) plus module-declaration
|
||||
# handling, which only the C++-module-capable Clang/MSVC path reaches (GCC is
|
||||
# skipped in the project due to QTBUG-142513).
|
||||
if (automoc_modules_supported)
|
||||
block()
|
||||
set(RunCMake_TEST_SOURCE_DIR "${RunCMake_SOURCE_DIR}/cxx_modules")
|
||||
set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/CxxModules-build")
|
||||
run_cmake_with_options(CxxModules ${RunCMake_TEST_OPTIONS})
|
||||
set(RunCMake_TEST_NO_CLEAN 1)
|
||||
run_cmake_command(CxxModules-build ${CMAKE_COMMAND} --build . --config Debug)
|
||||
endblock()
|
||||
endif ()
|
||||
|
||||
# Toggling a module unit's macro must not require a reconfigure: autogen's
|
||||
# depfile lists module-unit sources so ninja rescans them on rebuild. Use
|
||||
# a writable copy of the sources so the edit never touches the tracked
|
||||
# test files (mirrors how Autogen_6's incremental test avoids that).
|
||||
if (automoc_modules_supported)
|
||||
block()
|
||||
set(incremental_src_dir "${RunCMake_BINARY_DIR}/CxxModulesIncremental-src")
|
||||
file(REMOVE_RECURSE "${incremental_src_dir}")
|
||||
file(COPY "${RunCMake_SOURCE_DIR}/cxx_modules/" DESTINATION "${incremental_src_dir}")
|
||||
|
||||
set(RunCMake_TEST_SOURCE_DIR "${incremental_src_dir}")
|
||||
set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/CxxModulesIncremental-build")
|
||||
run_cmake_with_options(CxxModulesIncremental ${RunCMake_TEST_OPTIONS})
|
||||
set(RunCMake_TEST_NO_CLEAN 1)
|
||||
run_cmake_command(CxxModulesIncremental-build1 ${CMAKE_COMMAND} --build . --config Debug)
|
||||
|
||||
file(WRITE "${incremental_src_dir}/mod-empty.cppm" [[
|
||||
module;
|
||||
#include <QObject>
|
||||
export module Mod:Empty;
|
||||
|
||||
export class EmptyObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using QObject::QObject;
|
||||
signals:
|
||||
void emptySignal(int value);
|
||||
};
|
||||
]])
|
||||
|
||||
run_cmake_command(CxxModulesIncremental-build2 ${CMAKE_COMMAND} --build . --config Debug)
|
||||
endblock()
|
||||
endif ()
|
||||
|
||||
# moc rejects a meta-object macro in a module implementation unit, and
|
||||
# AUTOMOC does not work around that. Such a unit is not a member of the
|
||||
# CXX_MODULES file set, so it goes through the ordinary source handling and
|
||||
# its ".moc" include is honored; moc then fails.
|
||||
if (automoc_modules_supported)
|
||||
block()
|
||||
set(RunCMake_TEST_SOURCE_DIR "${RunCMake_SOURCE_DIR}/cxx_modules_impl_unit")
|
||||
set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/CxxModulesImplUnit-build")
|
||||
run_cmake_with_options(CxxModulesImplUnit ${RunCMake_TEST_OPTIONS})
|
||||
set(RunCMake_TEST_NO_CLEAN 1)
|
||||
run_cmake_command(CxxModulesImplUnit-build ${CMAKE_COMMAND} --build . --config Debug)
|
||||
endblock()
|
||||
endif ()
|
||||
|
||||
# A C++ module unit without a meta-object macro must not disturb AUTOMOC,
|
||||
# regardless of the Qt version: nothing is generated for the module unit,
|
||||
# while the ordinary header of the target is still moc'd. The module unit
|
||||
# pulls in no Qt, so this also covers compilers that cannot yet compile
|
||||
# QObject into a module unit.
|
||||
if (cxx_modules_supported)
|
||||
block()
|
||||
set(RunCMake_TEST_SOURCE_DIR "${RunCMake_SOURCE_DIR}/cxx_modules_no_macro")
|
||||
set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/CxxModulesNoMacro-build")
|
||||
run_cmake_with_options(CxxModulesNoMacro ${RunCMake_TEST_OPTIONS})
|
||||
set(RunCMake_TEST_NO_CLEAN 1)
|
||||
run_cmake_command(CxxModulesNoMacro-build ${CMAKE_COMMAND} --build . --config Debug)
|
||||
endblock()
|
||||
endif ()
|
||||
|
||||
# With older Qt, moc cannot process C++ module units at all. A meta-object
|
||||
# macro in one must be reported as such, rather than left to fail later in
|
||||
# the compiler or the linker. The project still configures; only the build
|
||||
# is expected to fail.
|
||||
if (cxx_modules_supported AND QtCore_VERSION VERSION_LESS "6.13")
|
||||
block()
|
||||
set(RunCMake_TEST_SOURCE_DIR "${RunCMake_SOURCE_DIR}/cxx_modules")
|
||||
set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/CxxModulesUnsupportedQt-build")
|
||||
run_cmake_with_options(CxxModulesUnsupportedQt ${RunCMake_TEST_OPTIONS})
|
||||
set(RunCMake_TEST_NO_CLEAN 1)
|
||||
run_cmake_command(CxxModulesUnsupportedQt-build ${CMAKE_COMMAND} --build . --config Debug)
|
||||
endblock()
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
project(cxx_modules_automoc CXX)
|
||||
|
||||
find_package(Qt${with_qt_version} REQUIRED COMPONENTS Core)
|
||||
|
||||
# QObject pulled into a module unit triggers exposure violations under GCC
|
||||
# (QTBUG-142513); skip cleanly there, matching qtbase's own module tests.
|
||||
# With older Qt no module unit is ever compiled here, because the build is
|
||||
# expected to fail in AUTOMOC first, so the exclusion does not apply.
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND
|
||||
Qt${with_qt_version}Core_VERSION VERSION_GREATER_EQUAL "6.13")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(app main.cpp mod-impl.cpp)
|
||||
target_sources(app
|
||||
PRIVATE FILE_SET CXX_MODULES FILES
|
||||
mod.cppm
|
||||
mod-part.cppm
|
||||
mod-internal.cppm
|
||||
mod-empty.cppm)
|
||||
|
||||
set_target_properties(app PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
AUTOMOC ON)
|
||||
|
||||
target_link_libraries(app PRIVATE Qt${with_qt_version}::Core)
|
||||
@@ -0,0 +1,8 @@
|
||||
import Mod;
|
||||
|
||||
int main()
|
||||
{
|
||||
PrimaryObject primary;
|
||||
PartObject part;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export module Mod:Empty;
|
||||
|
||||
export int emptyValue() { return 0; }
|
||||
@@ -0,0 +1,14 @@
|
||||
module;
|
||||
#include <QObject>
|
||||
module Mod;
|
||||
|
||||
// The implementation partition is imported here rather than from mod.cppm,
|
||||
// where Clang diagnoses it with
|
||||
// -Wimport-implementation-partition-unit-in-interface-unit. Referencing the
|
||||
// meta-object makes the link fail if AUTOMOC did not moc mod-internal.cppm.
|
||||
import :Internal;
|
||||
|
||||
int internalMethodCount()
|
||||
{
|
||||
return InternalObject::staticMetaObject.methodCount();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
module;
|
||||
#include <QObject>
|
||||
module Mod:Internal;
|
||||
|
||||
class InternalObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using QObject::QObject;
|
||||
signals:
|
||||
void internalSignal(int value);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
module;
|
||||
#include <QObject>
|
||||
export module Mod:Part;
|
||||
|
||||
export class PartObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using QObject::QObject;
|
||||
signals:
|
||||
void partSignal(int value);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
module;
|
||||
#include <QObject>
|
||||
export module Mod;
|
||||
export import :Part;
|
||||
|
||||
export class PrimaryObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using QObject::QObject;
|
||||
signals:
|
||||
void primarySignal(int value);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
project(cxx_modules_impl_unit_automoc CXX)
|
||||
|
||||
find_package(Qt${with_qt_version} REQUIRED COMPONENTS Core)
|
||||
|
||||
# moc rejects a meta-object macro in a module implementation unit. The build
|
||||
# is expected to fail in moc, so no compiler ever sees the module unit and no
|
||||
# compiler-specific exclusion is needed here.
|
||||
add_executable(app main.cpp mod-impl.cpp)
|
||||
target_sources(app
|
||||
PRIVATE FILE_SET CXX_MODULES FILES
|
||||
mod.cppm)
|
||||
|
||||
set_target_properties(app PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
AUTOMOC ON)
|
||||
|
||||
target_link_libraries(app PRIVATE Qt${with_qt_version}::Core)
|
||||
@@ -0,0 +1,6 @@
|
||||
import Mod;
|
||||
|
||||
int main()
|
||||
{
|
||||
return implObjectWorks() ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
module;
|
||||
#include <QObject>
|
||||
module Mod;
|
||||
|
||||
// A module implementation unit is not a member of the CXX_MODULES file set, so
|
||||
// AUTOMOC treats it like any other source and honors the ".moc" include below.
|
||||
// moc then rejects the macro.
|
||||
class ImplObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using QObject::QObject;
|
||||
signals:
|
||||
void implSignal();
|
||||
};
|
||||
|
||||
bool implObjectWorks()
|
||||
{
|
||||
return ImplObject::staticMetaObject.methodCount() > 0;
|
||||
}
|
||||
|
||||
#include "mod-impl.moc"
|
||||
@@ -0,0 +1,3 @@
|
||||
export module Mod;
|
||||
|
||||
export bool implObjectWorks();
|
||||
@@ -0,0 +1,19 @@
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
project(cxx_modules_no_macro_automoc CXX)
|
||||
|
||||
find_package(Qt${with_qt_version} REQUIRED COMPONENTS Core)
|
||||
|
||||
# The module unit pulls in no Qt at all, so this builds with any compiler that
|
||||
# can scan C++ modules, and with any Qt version: nothing is generated for a
|
||||
# module unit without a meta-object macro, while object.h is still moc'd.
|
||||
add_executable(app main.cpp object.h)
|
||||
target_sources(app
|
||||
PRIVATE FILE_SET CXX_MODULES FILES
|
||||
mod.cppm)
|
||||
|
||||
set_target_properties(app PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
AUTOMOC ON)
|
||||
|
||||
target_link_libraries(app PRIVATE Qt${with_qt_version}::Core)
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "object.h"
|
||||
|
||||
import Mod;
|
||||
|
||||
int main()
|
||||
{
|
||||
Object object;
|
||||
return object.metaObject()->methodCount() > 0 && modValue() == 42 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export module Mod;
|
||||
|
||||
export int modValue()
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
|
||||
class Object : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using QObject::QObject;
|
||||
signals:
|
||||
void objectSignal(int value);
|
||||
};
|
||||
Reference in New Issue
Block a user