1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmQtAutoMocUic.h"
12 #include <unordered_map>
13 #include <unordered_set>
18 #include <cm/string_view>
19 #include <cmext/algorithm>
21 #include "cmsys/FStream.hxx"
22 #include "cmsys/RegularExpression.hxx"
24 #include "cm_jsoncpp_value.h"
26 #include "cmCryptoHash.h"
27 #include "cmFileTime.h"
28 #include "cmGccDepfileReader.h"
29 #include "cmGccDepfileReaderTypes.h"
30 #include "cmGeneratedFileStream.h"
31 #include "cmQtAutoGen.h"
32 #include "cmQtAutoGenerator.h"
33 #include "cmStringAlgorithms.h"
34 #include "cmSystemTools.h"
35 #include "cmWorkerPool.h"
37 #if defined(__APPLE__)
43 constexpr std::size_t MocUnderscoreLength = 4; // Length of "moc_"
44 constexpr std::size_t UiUnderscoreLength = 3; // Length of "ui_"
46 /** \class cmQtAutoMocUicT
47 * \brief AUTOMOC and AUTOUIC generator
49 class cmQtAutoMocUicT : public cmQtAutoGenerator
53 ~cmQtAutoMocUicT() override;
55 cmQtAutoMocUicT(cmQtAutoMocUicT const&) = delete;
56 cmQtAutoMocUicT& operator=(cmQtAutoMocUicT const&) = delete;
61 /** Include string with sub parts. */
64 IncludeKeyT(std::string const& key, std::size_t basePrefixLength);
66 std::string Key; // Full include string
67 std::string Dir; // Include directory
68 std::string Base; // Base part of the include file name
71 /** Search key plus regular expression pair. */
74 KeyExpT(std::string key, std::string const& exp)
81 cmsys::RegularExpression Exp;
84 /** Source file parsing cache. */
90 /** Entry of the file parsing cache. */
100 std::vector<IncludeKeyT> Underscore;
101 std::vector<IncludeKeyT> Dot;
103 std::vector<std::string> Depends;
108 std::vector<IncludeKeyT> Include;
109 std::vector<std::string> Depends;
112 using FileHandleT = std::shared_ptr<FileT>;
113 using GetOrInsertT = std::pair<FileHandleT, bool>;
119 bool ReadFromFile(std::string const& fileName);
120 bool WriteToFile(std::string const& fileName);
122 //! Always returns a valid handle
123 GetOrInsertT GetOrInsert(std::string const& fileName);
126 std::unordered_map<std::string, FileHandleT> Map_;
129 /** Source file data. */
133 SourceFileT(std::string fileName)
134 : FileName(std::move(fileName))
139 std::string FileName;
141 ParseCacheT::FileHandleT ParseData;
142 std::string BuildPath;
143 bool IsHeader = false;
147 using SourceFileHandleT = std::shared_ptr<SourceFileT>;
148 using SourceFileMapT = std::map<std::string, SourceFileHandleT>;
150 /** Meta compiler file mapping information. */
153 SourceFileHandleT SourceFile;
154 std::string OutputFile;
155 std::string IncludeString;
156 std::vector<SourceFileHandleT> IncluderFiles;
158 using MappingHandleT = std::shared_ptr<MappingT>;
159 using MappingMapT = std::map<std::string, MappingHandleT>;
161 /** Common settings. */
169 BaseSettingsT(BaseSettingsT const&) = delete;
170 BaseSettingsT& operator=(BaseSettingsT const&) = delete;
174 bool MultiConfig = false;
175 IntegerVersion QtVersion = { 4, 0 };
176 unsigned int ThreadCount = 0;
178 std::string AutogenBuildDir;
179 std::string AutogenIncludeDir;
181 std::string CMakeExecutable;
182 cmFileTime CMakeExecutableTime;
183 std::string ParseCacheFile;
185 std::string DepFileRuleName;
186 std::vector<std::string> HeaderExtensions;
187 std::vector<std::string> ListFiles;
190 /** Shared common variables. */
195 bool ParseCacheChanged = false;
196 cmFileTime ParseCacheTime;
197 ParseCacheT ParseCache;
200 SourceFileMapT Headers;
201 SourceFileMapT Sources;
212 MocSettingsT(MocSettingsT const&) = delete;
213 MocSettingsT& operator=(MocSettingsT const&) = delete;
216 bool skipped(std::string const& fileName) const;
217 std::string MacrosString() const;
220 bool Enabled = false;
221 bool SettingsChanged = false;
222 bool RelaxedMode = false;
223 bool PathPrefix = false;
224 bool CanOutputDependencies = false;
225 cmFileTime ExecutableTime;
226 std::string Executable;
227 std::string CompFileAbs;
228 std::string PredefsFileAbs;
229 std::unordered_set<std::string> SkipList;
230 std::vector<std::string> IncludePaths;
231 std::vector<std::string> Definitions;
232 std::vector<std::string> OptionsIncludes;
233 std::vector<std::string> OptionsDefinitions;
234 std::vector<std::string> OptionsExtra;
235 std::vector<std::string> PredefsCmd;
236 std::vector<KeyExpT> DependFilters;
237 std::vector<KeyExpT> MacroFilters;
238 cmsys::RegularExpression RegExpInclude;
241 /** Moc shared variables. */
245 // -- predefines file
246 cmFileTime PredefsTime;
248 MappingMapT HeaderMappings;
249 MappingMapT SourceMappings;
250 MappingMapT Includes;
251 // -- Discovered files
252 SourceFileMapT HeadersDiscovered;
253 // -- Output directories
254 std::unordered_set<std::string> OutputDirs;
255 // -- Mocs compilation
256 bool CompUpdated = false;
257 std::vector<std::string> CompFiles;
266 std::vector<std::string> Options;
273 UicSettingsT(UicSettingsT const&) = delete;
274 UicSettingsT& operator=(UicSettingsT const&) = delete;
277 bool skipped(std::string const& fileName) const;
280 bool Enabled = false;
281 bool SettingsChanged = false;
282 cmFileTime ExecutableTime;
283 std::string Executable;
284 std::unordered_set<std::string> SkipList;
285 std::vector<std::string> Options;
286 std::unordered_map<std::string, UiFile> UiFiles;
287 std::vector<std::string> SearchPaths;
288 cmsys::RegularExpression RegExpInclude;
291 /** Uic shared variables. */
295 // -- Discovered files
296 SourceFileMapT UiFiles;
298 MappingMapT Includes;
299 // -- Output directories
300 std::unordered_set<std::string> OutputDirs;
303 /** Abstract job class for concurrent job processing. */
304 class JobT : public cmWorkerPool::JobT
307 /** Protected default constructor. */
308 JobT(bool fence = false)
309 : cmWorkerPool::JobT(fence)
313 //! Get the generator. Only valid during Process() call!
314 cmQtAutoMocUicT* Gen() const
316 return static_cast<cmQtAutoMocUicT*>(UserData());
319 // -- Accessors. Only valid during Process() call!
320 Logger const& Log() const { return Gen()->Log(); }
321 BaseSettingsT const& BaseConst() const { return Gen()->BaseConst(); }
322 BaseEvalT& BaseEval() const { return Gen()->BaseEval(); }
323 MocSettingsT const& MocConst() const { return Gen()->MocConst(); }
324 MocEvalT& MocEval() const { return Gen()->MocEval(); }
325 UicSettingsT const& UicConst() const { return Gen()->UicConst(); }
326 UicEvalT& UicEval() const { return Gen()->UicEval(); }
329 std::string MessagePath(cm::string_view path) const
331 return Gen()->MessagePath(path);
333 // - Error logging with automatic abort
334 void LogError(GenT genType, cm::string_view message) const;
335 void LogCommandError(GenT genType, cm::string_view message,
336 std::vector<std::string> const& command,
337 std::string const& output) const;
339 /** @brief Run an external process. Use only during Process() call! */
340 bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result,
341 std::vector<std::string> const& command,
342 std::string* infoMessage = nullptr);
345 /** Fence job utility class. */
346 class JobFenceT : public JobT
353 void Process() override{};
356 /** Generate moc_predefs.h. */
357 class JobMocPredefsT : public JobFenceT
359 void Process() override;
360 bool Update(std::string* reason) const;
363 /** File parse job base class. */
364 class JobParseT : public JobT
367 JobParseT(SourceFileHandleT fileHandle)
368 : FileHandle(std::move(fileHandle))
374 void CreateKeys(std::vector<IncludeKeyT>& container,
375 std::set<std::string> const& source,
376 std::size_t basePrefixLength);
378 void MocDependecies();
383 SourceFileHandleT FileHandle;
387 /** Header file parse job. */
388 class JobParseHeaderT : public JobParseT
391 using JobParseT::JobParseT;
392 void Process() override;
395 /** Source file parse job. */
396 class JobParseSourceT : public JobParseT
399 using JobParseT::JobParseT;
400 void Process() override;
403 /** Evaluate cached file parse data - moc. */
404 class JobEvalCacheT : public JobT
407 std::string MessageSearchLocations() const;
408 std::vector<std::string> SearchLocations;
411 /** Evaluate cached file parse data - moc. */
412 class JobEvalCacheMocT : public JobEvalCacheT
414 void Process() override;
415 bool EvalHeader(SourceFileHandleT source);
416 bool EvalSource(SourceFileHandleT const& source);
417 bool FindIncludedHeader(SourceFileHandleT& headerHandle,
418 cm::string_view includerDir,
419 cm::string_view includeBase);
420 bool RegisterIncluded(std::string const& includeString,
421 SourceFileHandleT includerFileHandle,
422 SourceFileHandleT sourceFileHandle) const;
423 void RegisterMapping(MappingHandleT mappingHandle) const;
424 std::string MessageHeader(cm::string_view headerBase) const;
427 /** Evaluate cached file parse data - uic. */
428 class JobEvalCacheUicT : public JobEvalCacheT
430 void Process() override;
431 bool EvalFile(SourceFileHandleT const& sourceFileHandle);
432 bool FindIncludedUi(cm::string_view sourceDirPrefix,
433 cm::string_view includePrefix);
434 bool RegisterMapping(std::string const& includeString,
435 SourceFileHandleT includerFileHandle);
438 SourceFileHandleT UiFileHandle;
441 /** Evaluate cached file parse data - finish */
442 class JobEvalCacheFinishT : public JobFenceT
444 void Process() override;
447 /** Dependency probing base job. */
448 class JobProbeDepsT : public JobT
452 /** Probes file dependencies and generates moc compile jobs. */
453 class JobProbeDepsMocT : public JobProbeDepsT
455 void Process() override;
456 bool Generate(MappingHandleT const& mapping, bool compFile) const;
457 bool Probe(MappingT const& mapping, std::string* reason) const;
458 std::pair<std::string, cmFileTime> FindDependency(
459 std::string const& sourceDir, std::string const& includeString) const;
462 /** Probes file dependencies and generates uic compile jobs. */
463 class JobProbeDepsUicT : public JobProbeDepsT
465 void Process() override;
466 bool Probe(MappingT const& mapping, std::string* reason) const;
469 /** Dependency probing finish job. */
470 class JobProbeDepsFinishT : public JobFenceT
472 void Process() override;
475 /** Meta compiler base job. */
476 class JobCompileT : public JobT
479 JobCompileT(MappingHandleT uicMapping, std::unique_ptr<std::string> reason)
480 : Mapping(std::move(uicMapping))
481 , Reason(std::move(reason))
486 MappingHandleT Mapping;
487 std::unique_ptr<std::string> Reason;
490 /** moc compiles a file. */
491 class JobCompileMocT : public JobCompileT
494 JobCompileMocT(MappingHandleT uicMapping,
495 std::unique_ptr<std::string> reason,
496 ParseCacheT::FileHandleT cacheEntry)
497 : JobCompileT(std::move(uicMapping), std::move(reason))
498 , CacheEntry(std::move(cacheEntry))
501 void Process() override;
504 ParseCacheT::FileHandleT CacheEntry;
507 /** uic compiles a file. */
508 class JobCompileUicT : public JobCompileT
511 using JobCompileT::JobCompileT;
512 void Process() override;
515 /** Generate mocs_compilation.cpp. */
516 class JobMocsCompilationT : public JobFenceT
519 void Process() override;
522 class JobDepFilesMergeT : public JobFenceT
525 void Process() override;
528 /** @brief The last job. */
529 class JobFinishT : public JobFenceT
532 void Process() override;
535 // -- Const settings interface
536 BaseSettingsT const& BaseConst() const { return this->BaseConst_; }
537 BaseEvalT& BaseEval() { return this->BaseEval_; }
538 MocSettingsT const& MocConst() const { return this->MocConst_; }
539 MocEvalT& MocEval() { return this->MocEval_; }
540 UicSettingsT const& UicConst() const { return this->UicConst_; }
541 UicEvalT& UicEval() { return this->UicEval_; }
543 // -- Parallel job processing interface
544 cmWorkerPool& WorkerPool() { return WorkerPool_; }
545 void AbortError() { Abort(true); }
546 void AbortSuccess() { Abort(false); }
549 std::string AbsoluteBuildPath(cm::string_view relativePath) const;
550 std::string AbsoluteIncludePath(cm::string_view relativePath) const;
551 template <class JOBTYPE>
552 void CreateParseJobs(SourceFileMapT const& sourceMap);
553 std::string CollapseFullPathTS(std::string const& path) const;
556 // -- Abstract processing interface
557 bool InitFromInfo(InfoT const& info) override;
559 bool Process() override;
561 void SettingsFileRead();
562 bool SettingsFileWrite();
564 void ParseCacheRead();
565 bool ParseCacheWrite();
566 // -- Thread processing
567 void Abort(bool error);
569 bool CreateDirectories();
570 // -- Support for depfiles
571 static std::vector<std::string> dependenciesFromDepFile(
572 const char* filePath);
576 BaseSettingsT BaseConst_;
578 MocSettingsT MocConst_;
580 UicSettingsT UicConst_;
583 std::string SettingsFile_;
584 std::string SettingsStringMoc_;
585 std::string SettingsStringUic_;
586 // -- Worker thread pool
587 std::atomic<bool> JobError_ = ATOMIC_VAR_INIT(false);
588 cmWorkerPool WorkerPool_;
589 // -- Concurrent processing
590 mutable std::mutex CMakeLibMutex_;
593 cmQtAutoMocUicT::IncludeKeyT::IncludeKeyT(std::string const& key,
594 std::size_t basePrefixLength)
596 , Dir(SubDirPrefix(key))
597 , Base(cmSystemTools::GetFilenameWithoutLastExtension(key))
599 if (basePrefixLength != 0) {
600 Base = Base.substr(basePrefixLength);
604 void cmQtAutoMocUicT::ParseCacheT::FileT::Clear()
607 Moc.Include.Underscore.clear();
608 Moc.Include.Dot.clear();
615 cmQtAutoMocUicT::ParseCacheT::GetOrInsertT
616 cmQtAutoMocUicT::ParseCacheT::GetOrInsert(std::string const& fileName)
618 // Find existing entry
620 auto it = Map_.find(fileName);
621 if (it != Map_.end()) {
622 return GetOrInsertT{ it->second, false };
628 Map_.emplace(fileName, std::make_shared<FileT>()).first->second, true
632 cmQtAutoMocUicT::ParseCacheT::ParseCacheT() = default;
633 cmQtAutoMocUicT::ParseCacheT::~ParseCacheT() = default;
635 bool cmQtAutoMocUicT::ParseCacheT::ReadFromFile(std::string const& fileName)
637 cmsys::ifstream fin(fileName.c_str());
641 FileHandleT fileHandle;
644 while (std::getline(fin, line)) {
645 // Check if this an empty or a comment line
646 if (line.empty() || line.front() == '#') {
649 // Drop carriage return character at the end
650 if (line.back() == '\r') {
656 // Check if this a file name line
657 if (line.front() != ' ') {
658 fileHandle = GetOrInsert(line).first;
662 // Bad line or bad file handle
663 if (!fileHandle || (line.size() < 6)) {
667 constexpr std::size_t offset = 5;
668 if (cmHasLiteralPrefix(line, " mmc:")) {
669 fileHandle->Moc.Macro = line.substr(offset);
672 if (cmHasLiteralPrefix(line, " miu:")) {
673 fileHandle->Moc.Include.Underscore.emplace_back(line.substr(offset),
674 MocUnderscoreLength);
677 if (cmHasLiteralPrefix(line, " mid:")) {
678 fileHandle->Moc.Include.Dot.emplace_back(line.substr(offset), 0);
681 if (cmHasLiteralPrefix(line, " mdp:")) {
682 fileHandle->Moc.Depends.emplace_back(line.substr(offset));
685 if (cmHasLiteralPrefix(line, " uic:")) {
686 fileHandle->Uic.Include.emplace_back(line.substr(offset),
690 if (cmHasLiteralPrefix(line, " udp:")) {
691 fileHandle->Uic.Depends.emplace_back(line.substr(offset));
698 bool cmQtAutoMocUicT::ParseCacheT::WriteToFile(std::string const& fileName)
700 cmGeneratedFileStream ofs(fileName);
704 ofs << "# Generated by CMake. Changes will be overwritten." << std::endl;
705 for (auto const& pair : Map_) {
706 ofs << pair.first << std::endl;
707 FileT const& file = *pair.second;
708 if (!file.Moc.Macro.empty()) {
709 ofs << " mmc:" << file.Moc.Macro << std::endl;
711 for (IncludeKeyT const& item : file.Moc.Include.Underscore) {
712 ofs << " miu:" << item.Key << std::endl;
714 for (IncludeKeyT const& item : file.Moc.Include.Dot) {
715 ofs << " mid:" << item.Key << std::endl;
717 for (std::string const& item : file.Moc.Depends) {
718 ofs << " mdp:" << item << std::endl;
720 for (IncludeKeyT const& item : file.Uic.Include) {
721 ofs << " uic:" << item.Key << std::endl;
723 for (std::string const& item : file.Uic.Depends) {
724 ofs << " udp:" << item << std::endl;
730 cmQtAutoMocUicT::BaseSettingsT::BaseSettingsT() = default;
731 cmQtAutoMocUicT::BaseSettingsT::~BaseSettingsT() = default;
733 cmQtAutoMocUicT::MocSettingsT::MocSettingsT()
735 RegExpInclude.compile(
736 "(^|\n)[ \t]*#[ \t]*include[ \t]+"
737 "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
740 cmQtAutoMocUicT::MocSettingsT::~MocSettingsT() = default;
742 bool cmQtAutoMocUicT::MocSettingsT::skipped(std::string const& fileName) const
744 return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
747 std::string cmQtAutoMocUicT::MocSettingsT::MacrosString() const
750 const auto itB = MacroFilters.cbegin();
751 const auto itE = MacroFilters.cend();
752 const auto itL = itE - 1;
754 for (; itC != itE; ++itC) {
769 cmQtAutoMocUicT::UicSettingsT::UicSettingsT()
771 RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+"
772 "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
775 cmQtAutoMocUicT::UicSettingsT::~UicSettingsT() = default;
777 bool cmQtAutoMocUicT::UicSettingsT::skipped(std::string const& fileName) const
779 return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
782 void cmQtAutoMocUicT::JobT::LogError(GenT genType,
783 cm::string_view message) const
786 Gen()->Log().Error(genType, message);
789 void cmQtAutoMocUicT::JobT::LogCommandError(
790 GenT genType, cm::string_view message,
791 std::vector<std::string> const& command, std::string const& output) const
794 Gen()->Log().ErrorCommand(genType, message, command, output);
797 bool cmQtAutoMocUicT::JobT::RunProcess(GenT genType,
798 cmWorkerPool::ProcessResultT& result,
799 std::vector<std::string> const& command,
800 std::string* infoMessage)
803 if (Log().Verbose()) {
804 cm::string_view info;
805 if (infoMessage != nullptr) {
810 info.empty() || cmHasSuffix(info, '\n') ? "" : "\n",
811 QuotedCommand(command), '\n'));
814 return cmWorkerPool::JobT::RunProcess(result, command,
815 BaseConst().AutogenBuildDir);
818 void cmQtAutoMocUicT::JobMocPredefsT::Process()
820 // (Re)generate moc_predefs.h on demand
821 std::unique_ptr<std::string> reason;
822 if (Log().Verbose()) {
823 reason = cm::make_unique<std::string>();
825 if (!Update(reason.get())) {
828 std::string const& predefsFileAbs = MocConst().PredefsFileAbs;
830 cmWorkerPool::ProcessResultT result;
833 std::vector<std::string> cmd = MocConst().PredefsCmd;
835 cm::append(cmd, MocConst().OptionsDefinitions);
837 cm::append(cmd, MocConst().OptionsIncludes);
839 if (!RunProcess(GenT::MOC, result, cmd, reason.get())) {
840 LogCommandError(GenT::MOC,
841 cmStrCat("The content generation command for ",
842 MessagePath(predefsFileAbs), " failed.\n",
843 result.ErrorMessage),
849 // (Re)write predefs file only on demand
850 if (cmQtAutoGenerator::FileDiffers(predefsFileAbs, result.StdOut)) {
851 if (!cmQtAutoGenerator::FileWrite(predefsFileAbs, result.StdOut)) {
854 cmStrCat("Writing ", MessagePath(predefsFileAbs), " failed."));
858 // Touch to update the time stamp
859 if (Log().Verbose()) {
860 Log().Info(GenT::MOC, "Touching " + MessagePath(predefsFileAbs));
862 if (!cmSystemTools::Touch(predefsFileAbs, false)) {
865 cmStrCat("Touching ", MessagePath(predefsFileAbs), " failed."));
871 // Read file time afterwards
872 if (!MocEval().PredefsTime.Load(predefsFileAbs)) {
874 cmStrCat("Reading the file time of ", MessagePath(predefsFileAbs),
880 bool cmQtAutoMocUicT::JobMocPredefsT::Update(std::string* reason) const
882 // Test if the file exists
883 if (!MocEval().PredefsTime.Load(MocConst().PredefsFileAbs)) {
884 if (reason != nullptr) {
885 *reason = cmStrCat("Generating ", MessagePath(MocConst().PredefsFileAbs),
886 ", because it doesn't exist.");
891 // Test if the settings changed
892 if (MocConst().SettingsChanged) {
893 if (reason != nullptr) {
894 *reason = cmStrCat("Generating ", MessagePath(MocConst().PredefsFileAbs),
895 ", because the moc settings changed.");
900 // Test if the executable is newer
902 std::string const& exec = MocConst().PredefsCmd.at(0);
904 if (execTime.Load(exec)) {
905 if (MocEval().PredefsTime.Older(execTime)) {
906 if (reason != nullptr) {
908 cmStrCat("Generating ", MessagePath(MocConst().PredefsFileAbs),
909 " because it is older than ", MessagePath(exec), '.');
919 bool cmQtAutoMocUicT::JobParseT::ReadFile()
921 // Clear old parse information
922 FileHandle->ParseData->Clear();
923 std::string const& fileName = FileHandle->FileName;
925 if (Log().Verbose()) {
926 Log().Info(GenT::GEN, cmStrCat("Parsing ", MessagePath(fileName)));
931 if (!cmQtAutoGenerator::FileRead(Content, fileName, &error)) {
934 cmStrCat("Could not read ", MessagePath(fileName), ".\n", error));
939 if (Content.empty()) {
940 Log().Warning(GenT::GEN, cmStrCat(MessagePath(fileName), " is empty."));
946 void cmQtAutoMocUicT::JobParseT::CreateKeys(
947 std::vector<IncludeKeyT>& container, std::set<std::string> const& source,
948 std::size_t basePrefixLength)
950 if (source.empty()) {
953 container.reserve(source.size());
954 for (std::string const& src : source) {
955 container.emplace_back(src, basePrefixLength);
959 void cmQtAutoMocUicT::JobParseT::MocMacro()
961 for (KeyExpT const& filter : MocConst().MacroFilters) {
962 // Run a simple find string check
963 if (Content.find(filter.Key) == std::string::npos) {
966 // Run the expensive regular expression check loop
967 cmsys::RegularExpressionMatch match;
968 if (filter.Exp.find(Content.c_str(), match)) {
969 // Keep detected macro name
970 FileHandle->ParseData->Moc.Macro = filter.Key;
976 void cmQtAutoMocUicT::JobParseT::MocDependecies()
978 if (MocConst().DependFilters.empty() || MocConst().CanOutputDependencies) {
982 // Find dependency strings
983 std::set<std::string> parseDepends;
984 for (KeyExpT const& filter : MocConst().DependFilters) {
985 // Run a simple find string check
986 if (Content.find(filter.Key) == std::string::npos) {
989 // Run the expensive regular expression check loop
990 const char* contentChars = Content.c_str();
991 cmsys::RegularExpressionMatch match;
992 while (filter.Exp.find(contentChars, match)) {
994 std::string dep = match.match(1);
996 parseDepends.emplace(std::move(dep));
999 contentChars += match.end();
1003 // Store dependency strings
1005 auto& Depends = FileHandle->ParseData->Moc.Depends;
1006 Depends.reserve(parseDepends.size());
1007 for (std::string const& item : parseDepends) {
1008 Depends.emplace_back(item);
1009 // Replace end of line characters in filenames
1010 std::string& path = Depends.back();
1011 std::replace(path.begin(), path.end(), '\n', ' ');
1012 std::replace(path.begin(), path.end(), '\r', ' ');
1017 void cmQtAutoMocUicT::JobParseT::MocIncludes()
1019 if (Content.find("moc") == std::string::npos) {
1023 std::set<std::string> underscore;
1024 std::set<std::string> dot;
1026 const char* contentChars = Content.c_str();
1027 cmsys::RegularExpression const& regExp = MocConst().RegExpInclude;
1028 cmsys::RegularExpressionMatch match;
1029 while (regExp.find(contentChars, match)) {
1030 std::string incString = match.match(2);
1031 std::string const incBase =
1032 cmSystemTools::GetFilenameWithoutLastExtension(incString);
1033 if (cmHasLiteralPrefix(incBase, "moc_")) {
1035 // Remove the moc_ part from the base name
1036 underscore.emplace(std::move(incString));
1039 dot.emplace(std::move(incString));
1041 // Forward content pointer
1042 contentChars += match.end();
1045 auto& Include = FileHandle->ParseData->Moc.Include;
1046 CreateKeys(Include.Underscore, underscore, MocUnderscoreLength);
1047 CreateKeys(Include.Dot, dot, 0);
1050 void cmQtAutoMocUicT::JobParseT::UicIncludes()
1052 if (Content.find("ui_") == std::string::npos) {
1056 std::set<std::string> includes;
1058 const char* contentChars = Content.c_str();
1059 cmsys::RegularExpression const& regExp = UicConst().RegExpInclude;
1060 cmsys::RegularExpressionMatch match;
1061 while (regExp.find(contentChars, match)) {
1062 includes.emplace(match.match(2));
1063 // Forward content pointer
1064 contentChars += match.end();
1067 CreateKeys(FileHandle->ParseData->Uic.Include, includes, UiUnderscoreLength);
1070 void cmQtAutoMocUicT::JobParseHeaderT::Process()
1076 if (FileHandle->Moc) {
1081 if (FileHandle->Uic) {
1086 void cmQtAutoMocUicT::JobParseSourceT::Process()
1092 if (FileHandle->Moc) {
1098 if (FileHandle->Uic) {
1103 std::string cmQtAutoMocUicT::JobEvalCacheT::MessageSearchLocations() const
1107 for (std::string const& path : SearchLocations) {
1109 res += MessagePath(path);
1115 void cmQtAutoMocUicT::JobEvalCacheMocT::Process()
1118 for (auto const& pair : BaseEval().Headers) {
1119 if (!EvalHeader(pair.second)) {
1124 for (auto const& pair : BaseEval().Sources) {
1125 if (!EvalSource(pair.second)) {
1131 bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalHeader(SourceFileHandleT source)
1133 SourceFileT const& sourceFile = *source;
1134 auto const& parseData = sourceFile.ParseData->Moc;
1139 if (!parseData.Macro.empty()) {
1140 // Create a new mapping
1141 MappingHandleT handle = std::make_shared<MappingT>();
1142 handle->SourceFile = std::move(source);
1144 // Absolute build path
1145 if (BaseConst().MultiConfig) {
1146 handle->OutputFile = Gen()->AbsoluteIncludePath(sourceFile.BuildPath);
1148 handle->OutputFile = Gen()->AbsoluteBuildPath(sourceFile.BuildPath);
1151 // Register mapping in headers map
1152 RegisterMapping(handle);
1158 bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalSource(
1159 SourceFileHandleT const& source)
1161 SourceFileT const& sourceFile = *source;
1162 auto const& parseData = sourceFile.ParseData->Moc;
1163 if (!sourceFile.Moc ||
1164 (parseData.Macro.empty() && parseData.Include.Underscore.empty() &&
1165 parseData.Include.Dot.empty())) {
1169 std::string const sourceDirPrefix = SubDirPrefix(sourceFile.FileName);
1170 std::string const sourceBase =
1171 cmSystemTools::GetFilenameWithoutLastExtension(sourceFile.FileName);
1173 // For relaxed mode check if the own "moc_" or ".moc" file is included
1174 bool const relaxedMode = MocConst().RelaxedMode;
1175 bool sourceIncludesMocUnderscore = false;
1176 bool sourceIncludesDotMoc = false;
1177 // Check if the sources own "moc_" or ".moc" file is included
1179 for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
1180 if (incKey.Base == sourceBase) {
1181 sourceIncludesMocUnderscore = true;
1186 for (IncludeKeyT const& incKey : parseData.Include.Dot) {
1187 if (incKey.Base == sourceBase) {
1188 sourceIncludesDotMoc = true;
1193 // Check if this source needs to be moc processed but doesn't.
1194 if (!sourceIncludesDotMoc && !parseData.Macro.empty() &&
1195 !(relaxedMode && sourceIncludesMocUnderscore)) {
1197 cmStrCat(MessagePath(sourceFile.FileName), "\ncontains a ",
1198 Quoted(parseData.Macro), " macro, but does not include ",
1199 MessagePath(sourceBase + ".moc"),
1200 "!\nConsider to\n - add #include \"", sourceBase,
1201 ".moc\"\n - enable SKIP_AUTOMOC for this file"));
1205 // Evaluate "moc_" includes
1206 for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
1207 SourceFileHandleT headerHandle;
1209 std::string const headerBase = cmStrCat(incKey.Dir, incKey.Base);
1210 if (!FindIncludedHeader(headerHandle, sourceDirPrefix, headerBase)) {
1212 cmStrCat(MessagePath(sourceFile.FileName),
1213 "\nincludes the moc file ", MessagePath(incKey.Key),
1214 ",\nbut a header ", MessageHeader(headerBase),
1215 "\ncould not be found "
1216 "in the following directories\n",
1217 MessageSearchLocations()));
1221 // The include might be handled differently in relaxed mode
1222 if (relaxedMode && !sourceIncludesDotMoc && !parseData.Macro.empty() &&
1223 (incKey.Base == sourceBase)) {
1224 // The <BASE>.cpp file includes a Qt macro but does not include the
1225 // <BASE>.moc file. In this case, the moc_<BASE>.cpp should probably
1226 // be generated from <BASE>.cpp instead of <BASE>.h, because otherwise
1227 // it won't build. But warn, since this is not how it is supposed to be
1228 // used. This is for KDE4 compatibility.
1233 cmStrCat(MessagePath(sourceFile.FileName), "\ncontains a ",
1234 Quoted(parseData.Macro), " macro, but does not include ",
1235 MessagePath(sourceBase + ".moc"), ".\nInstead it includes ",
1236 MessagePath(incKey.Key), ".\nRunning moc on the source\n ",
1237 MessagePath(sourceFile.FileName), "!\nBetter include ",
1238 MessagePath(sourceBase + ".moc"),
1239 " for compatibility with regular mode.\n",
1240 "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
1243 if (!RegisterIncluded(incKey.Key, source, source)) {
1249 // Check if header is skipped
1250 if (MocConst().skipped(headerHandle->FileName)) {
1254 if (!RegisterIncluded(incKey.Key, source, std::move(headerHandle))) {
1259 // Evaluate ".moc" includes
1262 for (IncludeKeyT const& incKey : parseData.Include.Dot) {
1263 // Check if this is the sources own .moc file
1264 bool const ownMoc = (incKey.Base == sourceBase);
1265 if (ownMoc && !parseData.Macro.empty()) {
1266 // Create mapping for the regular use case
1267 if (!RegisterIncluded(incKey.Key, source, source)) {
1272 // Try to find a header instead but issue a warning.
1273 // This is for KDE4 compatibility.
1274 SourceFileHandleT headerHandle;
1276 std::string const headerBase = cmStrCat(incKey.Dir, incKey.Base);
1277 if (!FindIncludedHeader(headerHandle, sourceDirPrefix, headerBase)) {
1281 MessagePath(sourceFile.FileName), "\nincludes the moc file ",
1282 MessagePath(incKey.Key),
1283 ",\nwhich seems to be the moc file from a different source "
1284 "file.\nCMAKE_AUTOMOC_RELAXED_MODE:\nAlso a matching header ",
1285 MessageHeader(headerBase),
1286 "\ncould not be found in the following directories\n",
1287 MessageSearchLocations()));
1291 // Check if header is skipped
1292 if (MocConst().skipped(headerHandle->FileName)) {
1296 if (ownMoc && parseData.Macro.empty()) {
1299 cmStrCat(MessagePath(sourceFile.FileName),
1300 "\nincludes the moc file ", MessagePath(incKey.Key),
1301 ", but does not contain a\n", MocConst().MacrosString(),
1302 " macro.\nRunning moc on the header\n ",
1303 MessagePath(headerHandle->FileName), "!\nBetter include ",
1304 MessagePath("moc_" + incKey.Base + ".cpp"),
1305 " for a compatibility with regular mode.\n",
1306 "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
1310 cmStrCat(MessagePath(sourceFile.FileName),
1311 "\nincludes the moc file ", MessagePath(incKey.Key),
1312 " instead of ", MessagePath("moc_" + incKey.Base + ".cpp"),
1313 ".\nRunning moc on the header\n ",
1314 MessagePath(headerHandle->FileName), "!\nBetter include ",
1315 MessagePath("moc_" + incKey.Base + ".cpp"),
1316 " for compatibility with regular mode.\n",
1317 "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
1320 if (!RegisterIncluded(incKey.Key, source, std::move(headerHandle))) {
1326 for (IncludeKeyT const& incKey : parseData.Include.Dot) {
1327 // Check if this is the sources own .moc file
1328 bool const ownMoc = (incKey.Base == sourceBase);
1330 // Don't allow <BASE>.moc include other than own in regular mode
1332 cmStrCat(MessagePath(sourceFile.FileName),
1333 "\nincludes the moc file ", MessagePath(incKey.Key),
1334 ",\nwhich seems to be the moc file from a different "
1335 "source file.\nThis is not supported. Include ",
1336 MessagePath(sourceBase + ".moc"),
1337 " to run moc on this source file."));
1340 // Accept but issue a warning if moc isn't required
1341 if (parseData.Macro.empty()) {
1342 Log().Warning(GenT::MOC,
1343 cmStrCat(MessagePath(sourceFile.FileName),
1344 "\nincludes the moc file ",
1345 MessagePath(incKey.Key),
1346 ", but does not contain a ",
1347 MocConst().MacrosString(), " macro."));
1350 if (!RegisterIncluded(incKey.Key, source, source)) {
1359 bool cmQtAutoMocUicT::JobEvalCacheMocT::FindIncludedHeader(
1360 SourceFileHandleT& headerHandle, cm::string_view includerDir,
1361 cm::string_view includeBase)
1363 // Clear search locations
1364 SearchLocations.clear();
1366 auto findHeader = [this,
1367 &headerHandle](std::string const& basePath) -> bool {
1369 for (std::string const& ext : this->BaseConst().HeaderExtensions) {
1370 std::string const testPath =
1371 this->Gen()->CollapseFullPathTS(cmStrCat(basePath, '.', ext));
1372 cmFileTime fileTime;
1373 if (!fileTime.Load(testPath)) {
1378 // Return a known file if it exists already
1380 auto it = BaseEval().Headers.find(testPath);
1381 if (it != BaseEval().Headers.end()) {
1382 headerHandle = it->second;
1388 // Created and return discovered file entry
1390 SourceFileHandleT& handle = MocEval().HeadersDiscovered[testPath];
1392 handle = std::make_shared<SourceFileT>(testPath);
1393 handle->FileTime = fileTime;
1394 handle->IsHeader = true;
1397 headerHandle = handle;
1403 this->SearchLocations.emplace_back(cmQtAutoGen::ParentDir(basePath));
1408 // Search in vicinity of the source
1409 if (findHeader(cmStrCat(includerDir, includeBase))) {
1412 // Search in include directories
1413 for (std::string const& path : MocConst().IncludePaths) {
1414 if (findHeader(cmStrCat(path, '/', includeBase))) {
1418 // Return without success
1422 bool cmQtAutoMocUicT::JobEvalCacheMocT::RegisterIncluded(
1423 std::string const& includeString, SourceFileHandleT includerFileHandle,
1424 SourceFileHandleT sourceFileHandle) const
1426 // Check if this file is already included
1427 MappingHandleT& handle = MocEval().Includes[includeString];
1429 // Check if the output file would be generated from different source files
1430 if (handle->SourceFile != sourceFileHandle) {
1432 cmStrCat(" ", MessagePath(includerFileHandle->FileName), '\n');
1433 for (auto const& item : handle->IncluderFiles) {
1434 files += cmStrCat(" ", MessagePath(item->FileName), '\n');
1438 cmStrCat("The source files\n", files,
1439 "contain the same include string ",
1440 MessagePath(includeString),
1441 ", but\nthe moc file would be generated from different "
1443 MessagePath(sourceFileHandle->FileName), " and\n ",
1444 MessagePath(handle->SourceFile->FileName),
1446 " - not include the \"moc_<NAME>.cpp\" file\n"
1447 " - add a directory prefix to a \"<NAME>.moc\" include "
1448 "(e.g \"sub/<NAME>.moc\")\n"
1449 " - rename the source file(s)\n"));
1453 // The same mapping already exists. Just add to the includers list.
1454 handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
1458 // Create a new mapping
1459 handle = std::make_shared<MappingT>();
1460 handle->IncludeString = includeString;
1461 handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
1462 handle->SourceFile = std::move(sourceFileHandle);
1463 handle->OutputFile = Gen()->AbsoluteIncludePath(includeString);
1465 // Register mapping in sources/headers map
1466 RegisterMapping(handle);
1470 void cmQtAutoMocUicT::JobEvalCacheMocT::RegisterMapping(
1471 MappingHandleT mappingHandle) const
1473 auto& regMap = mappingHandle->SourceFile->IsHeader
1474 ? MocEval().HeaderMappings
1475 : MocEval().SourceMappings;
1476 // Check if source file already gets mapped
1477 auto& regHandle = regMap[mappingHandle->SourceFile->FileName];
1479 // Yet unknown mapping
1480 regHandle = std::move(mappingHandle);
1482 // Mappings with include string override those without
1483 if (!mappingHandle->IncludeString.empty()) {
1484 regHandle = std::move(mappingHandle);
1489 std::string cmQtAutoMocUicT::JobEvalCacheMocT::MessageHeader(
1490 cm::string_view headerBase) const
1492 return MessagePath(cmStrCat(
1493 headerBase, ".{", cmJoin(this->BaseConst().HeaderExtensions, ","), '}'));
1496 void cmQtAutoMocUicT::JobEvalCacheUicT::Process()
1499 SearchLocations.reserve((UicConst().SearchPaths.size() + 1) * 2);
1502 for (auto const& pair : BaseEval().Headers) {
1503 if (!EvalFile(pair.second)) {
1508 for (auto const& pair : BaseEval().Sources) {
1509 if (!EvalFile(pair.second)) {
1515 bool cmQtAutoMocUicT::JobEvalCacheUicT::EvalFile(
1516 SourceFileHandleT const& sourceFileHandle)
1518 SourceFileT const& sourceFile = *sourceFileHandle;
1519 auto const& Include = sourceFile.ParseData->Uic.Include;
1520 if (!sourceFile.Uic || Include.empty()) {
1524 std::string const sourceDirPrefix = SubDirPrefix(sourceFile.FileName);
1525 for (IncludeKeyT const& incKey : Include) {
1527 UiName = cmStrCat(incKey.Base, ".ui");
1528 if (!FindIncludedUi(sourceDirPrefix, incKey.Dir)) {
1530 cmStrCat(MessagePath(sourceFile.FileName),
1531 "\nincludes the uic file ", MessagePath(incKey.Key),
1532 ",\nbut the user interface file ", MessagePath(UiName),
1533 "\ncould not be found in the following directories\n",
1534 MessageSearchLocations()));
1537 // Check if the file is skipped
1538 if (UicConst().skipped(UiFileHandle->FileName)) {
1542 if (!RegisterMapping(incKey.Key, sourceFileHandle)) {
1550 bool cmQtAutoMocUicT::JobEvalCacheUicT::FindIncludedUi(
1551 cm::string_view sourceDirPrefix, cm::string_view includePrefix)
1553 // Clear locations buffer
1554 SearchLocations.clear();
1556 auto findUi = [this](std::string const& testPath) -> bool {
1557 std::string const fullPath = this->Gen()->CollapseFullPathTS(testPath);
1558 cmFileTime fileTime;
1559 if (!fileTime.Load(fullPath)) {
1560 this->SearchLocations.emplace_back(cmQtAutoGen::ParentDir(fullPath));
1563 // .ui file found in files system!
1564 // Get or create .ui file handle
1565 SourceFileHandleT& handle = this->UicEval().UiFiles[fullPath];
1567 // The file wasn't registered, yet
1568 handle = std::make_shared<SourceFileT>(fullPath);
1569 handle->FileTime = fileTime;
1571 this->UiFileHandle = handle;
1575 // Vicinity of the source
1576 if (findUi(cmStrCat(sourceDirPrefix, UiName))) {
1579 if (!includePrefix.empty()) {
1580 if (findUi(cmStrCat(sourceDirPrefix, includePrefix, UiName))) {
1584 // Additional AUTOUIC search paths
1585 auto const& searchPaths = UicConst().SearchPaths;
1586 if (!searchPaths.empty()) {
1587 for (std::string const& sPath : searchPaths) {
1588 if (findUi(cmStrCat(sPath, '/', UiName))) {
1592 if (!includePrefix.empty()) {
1593 for (std::string const& sPath : searchPaths) {
1594 if (findUi(cmStrCat(sPath, '/', includePrefix, UiName))) {
1604 bool cmQtAutoMocUicT::JobEvalCacheUicT::RegisterMapping(
1605 std::string const& includeString, SourceFileHandleT includerFileHandle)
1607 auto& Includes = Gen()->UicEval().Includes;
1608 auto it = Includes.find(includeString);
1609 if (it != Includes.end()) {
1610 MappingHandleT const& handle = it->second;
1611 if (handle->SourceFile != UiFileHandle) {
1612 // The output file already gets generated - from a different .ui file!
1614 cmStrCat(" ", MessagePath(includerFileHandle->FileName), '\n');
1615 for (auto const& item : handle->IncluderFiles) {
1616 files += cmStrCat(" ", MessagePath(item->FileName), '\n');
1621 "The source files\n", files, "contain the same include string ",
1622 Quoted(includeString),
1623 ", but\nthe uic file would be generated from different "
1624 "user interface files\n ",
1625 MessagePath(UiFileHandle->FileName), " and\n ",
1626 MessagePath(handle->SourceFile->FileName),
1628 " - add a directory prefix to a \"ui_<NAME>.h\" include "
1629 "(e.g \"sub/ui_<NAME>.h\")\n"
1630 " - rename the <NAME>.ui file(s) and adjust the \"ui_<NAME>.h\" "
1634 // Add includer file to existing mapping
1635 handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
1637 // New mapping handle
1638 MappingHandleT handle = std::make_shared<MappingT>();
1639 handle->IncludeString = includeString;
1640 handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
1641 handle->SourceFile = UiFileHandle;
1642 handle->OutputFile = Gen()->AbsoluteIncludePath(includeString);
1644 Includes.emplace(includeString, std::move(handle));
1649 void cmQtAutoMocUicT::JobEvalCacheFinishT::Process()
1651 // Add discovered header parse jobs
1652 Gen()->CreateParseJobs<JobParseHeaderT>(MocEval().HeadersDiscovered);
1654 // Add dependency probing jobs
1656 // Add fence job to ensure all parsing has finished
1657 Gen()->WorkerPool().EmplaceJob<JobFenceT>();
1658 if (MocConst().Enabled) {
1659 Gen()->WorkerPool().EmplaceJob<JobProbeDepsMocT>();
1661 if (UicConst().Enabled) {
1662 Gen()->WorkerPool().EmplaceJob<JobProbeDepsUicT>();
1664 // Add probe finish job
1665 Gen()->WorkerPool().EmplaceJob<JobProbeDepsFinishT>();
1669 void cmQtAutoMocUicT::JobProbeDepsMocT::Process()
1671 // Create moc header jobs
1672 for (auto const& pair : MocEval().HeaderMappings) {
1673 // Register if this mapping is a candidate for mocs_compilation.cpp
1674 bool const compFile = pair.second->IncludeString.empty();
1676 MocEval().CompFiles.emplace_back(pair.second->SourceFile->BuildPath);
1678 if (!Generate(pair.second, compFile)) {
1683 // Create moc source jobs
1684 for (auto const& pair : MocEval().SourceMappings) {
1685 if (!Generate(pair.second, false)) {
1691 bool cmQtAutoMocUicT::JobProbeDepsMocT::Generate(MappingHandleT const& mapping,
1692 bool compFile) const
1694 std::unique_ptr<std::string> reason;
1695 if (Log().Verbose()) {
1696 reason = cm::make_unique<std::string>();
1698 if (Probe(*mapping, reason.get())) {
1699 // Register the parent directory for creation
1700 MocEval().OutputDirs.emplace(cmQtAutoGen::ParentDir(mapping->OutputFile));
1701 // Fetch the cache entry for the source file
1702 std::string const& sourceFile = mapping->SourceFile->FileName;
1703 ParseCacheT::GetOrInsertT cacheEntry =
1704 BaseEval().ParseCache.GetOrInsert(sourceFile);
1706 Gen()->WorkerPool().EmplaceJob<JobCompileMocT>(
1707 mapping, std::move(reason), std::move(cacheEntry.first));
1708 // Check if a moc job for a mocs_compilation.cpp entry was generated
1710 MocEval().CompUpdated = true;
1716 bool cmQtAutoMocUicT::JobProbeDepsMocT::Probe(MappingT const& mapping,
1717 std::string* reason) const
1719 std::string const& sourceFile = mapping.SourceFile->FileName;
1720 std::string const& outputFile = mapping.OutputFile;
1722 // Test if the output file exists
1723 cmFileTime outputFileTime;
1724 if (!outputFileTime.Load(outputFile)) {
1725 if (reason != nullptr) {
1727 cmStrCat("Generating ", MessagePath(outputFile),
1728 ", because it doesn't exist, from ", MessagePath(sourceFile));
1733 // Test if any setting changed
1734 if (MocConst().SettingsChanged) {
1735 if (reason != nullptr) {
1736 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1737 ", because the uic settings changed, from ",
1738 MessagePath(sourceFile));
1743 // Test if the source file is newer
1744 if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
1745 if (reason != nullptr) {
1746 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1747 ", because it's older than its source file, from ",
1748 MessagePath(sourceFile));
1753 // Test if the moc_predefs file is newer
1754 if (!MocConst().PredefsFileAbs.empty()) {
1755 if (outputFileTime.Older(MocEval().PredefsTime)) {
1756 if (reason != nullptr) {
1757 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1758 ", because it's older than ",
1759 MessagePath(MocConst().PredefsFileAbs), ", from ",
1760 MessagePath(sourceFile));
1766 // Test if the moc executable is newer
1767 if (outputFileTime.Older(MocConst().ExecutableTime)) {
1768 if (reason != nullptr) {
1769 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1770 ", because it's older than the moc executable, from ",
1771 MessagePath(sourceFile));
1776 // Test if a dependency file is newer
1778 // Check dependency timestamps
1779 std::string const sourceDir = SubDirPrefix(sourceFile);
1780 for (std::string const& dep : mapping.SourceFile->ParseData->Moc.Depends) {
1781 // Find dependency file
1782 auto const depMatch = FindDependency(sourceDir, dep);
1783 if (depMatch.first.empty()) {
1784 Log().Warning(GenT::MOC,
1785 cmStrCat(MessagePath(sourceFile), " depends on ",
1787 " but the file does not exist."));
1790 // Test if dependency file is older
1791 if (outputFileTime.Older(depMatch.second)) {
1792 if (reason != nullptr) {
1793 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1794 ", because it's older than its dependency file ",
1795 MessagePath(depMatch.first), ", from ",
1796 MessagePath(sourceFile));
1806 std::pair<std::string, cmFileTime>
1807 cmQtAutoMocUicT::JobProbeDepsMocT::FindDependency(
1808 std::string const& sourceDir, std::string const& includeString) const
1810 using ResPair = std::pair<std::string, cmFileTime>;
1811 // moc's dependency file contains absolute paths
1812 if (MocConst().CanOutputDependencies) {
1813 ResPair res{ includeString, {} };
1814 if (res.second.Load(res.first)) {
1819 // Search in vicinity of the source
1821 ResPair res{ sourceDir + includeString, {} };
1822 if (res.second.Load(res.first)) {
1826 // Search in include directories
1827 for (std::string const& includePath : MocConst().IncludePaths) {
1828 ResPair res{ cmStrCat(includePath, '/', includeString), {} };
1829 if (res.second.Load(res.first)) {
1837 void cmQtAutoMocUicT::JobProbeDepsUicT::Process()
1839 for (auto const& pair : Gen()->UicEval().Includes) {
1840 MappingHandleT const& mapping = pair.second;
1841 std::unique_ptr<std::string> reason;
1842 if (Log().Verbose()) {
1843 reason = cm::make_unique<std::string>();
1845 if (!Probe(*mapping, reason.get())) {
1849 // Register the parent directory for creation
1850 UicEval().OutputDirs.emplace(cmQtAutoGen::ParentDir(mapping->OutputFile));
1852 Gen()->WorkerPool().EmplaceJob<JobCompileUicT>(mapping, std::move(reason));
1856 bool cmQtAutoMocUicT::JobProbeDepsUicT::Probe(MappingT const& mapping,
1857 std::string* reason) const
1859 std::string const& sourceFile = mapping.SourceFile->FileName;
1860 std::string const& outputFile = mapping.OutputFile;
1862 // Test if the build file exists
1863 cmFileTime outputFileTime;
1864 if (!outputFileTime.Load(outputFile)) {
1865 if (reason != nullptr) {
1867 cmStrCat("Generating ", MessagePath(outputFile),
1868 ", because it doesn't exist, from ", MessagePath(sourceFile));
1873 // Test if the uic settings changed
1874 if (UicConst().SettingsChanged) {
1875 if (reason != nullptr) {
1876 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1877 ", because the uic settings changed, from ",
1878 MessagePath(sourceFile));
1883 // Test if the source file is newer
1884 if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
1885 if (reason != nullptr) {
1886 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1887 " because it's older than the source file ",
1888 MessagePath(sourceFile));
1893 // Test if the uic executable is newer
1894 if (outputFileTime.Older(UicConst().ExecutableTime)) {
1895 if (reason != nullptr) {
1896 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1897 ", because it's older than the uic executable, from ",
1898 MessagePath(sourceFile));
1906 void cmQtAutoMocUicT::JobProbeDepsFinishT::Process()
1908 // Create output directories
1910 using StringSet = std::unordered_set<std::string>;
1911 auto createDirs = [this](GenT genType, StringSet const& dirSet) {
1912 for (std::string const& dirName : dirSet) {
1913 if (!cmSystemTools::MakeDirectory(dirName)) {
1916 cmStrCat("Creating directory ", MessagePath(dirName), " failed."));
1921 if (MocConst().Enabled && UicConst().Enabled) {
1922 StringSet outputDirs = MocEval().OutputDirs;
1923 outputDirs.insert(UicEval().OutputDirs.begin(),
1924 UicEval().OutputDirs.end());
1925 createDirs(GenT::GEN, outputDirs);
1926 } else if (MocConst().Enabled) {
1927 createDirs(GenT::MOC, MocEval().OutputDirs);
1928 } else if (UicConst().Enabled) {
1929 createDirs(GenT::UIC, UicEval().OutputDirs);
1933 if (MocConst().Enabled) {
1934 // Add mocs compilations job
1935 Gen()->WorkerPool().EmplaceJob<JobMocsCompilationT>();
1938 if (!BaseConst().DepFile.empty()) {
1939 // Add job to merge dep files
1940 Gen()->WorkerPool().EmplaceJob<JobDepFilesMergeT>();
1944 Gen()->WorkerPool().EmplaceJob<JobFinishT>();
1947 void cmQtAutoMocUicT::JobCompileMocT::Process()
1949 std::string const& sourceFile = Mapping->SourceFile->FileName;
1950 std::string const& outputFile = Mapping->OutputFile;
1952 // Compose moc command
1953 std::vector<std::string> cmd;
1955 // Reserve large enough
1956 cmd.reserve(MocConst().OptionsDefinitions.size() +
1957 MocConst().OptionsIncludes.size() +
1958 MocConst().OptionsExtra.size() + 16);
1959 cmd.push_back(MocConst().Executable);
1961 cm::append(cmd, MocConst().OptionsDefinitions);
1963 cm::append(cmd, MocConst().OptionsIncludes);
1964 // Add predefs include
1965 if (!MocConst().PredefsFileAbs.empty()) {
1966 cmd.emplace_back("--include");
1967 cmd.push_back(MocConst().PredefsFileAbs);
1969 // Add path prefix on demand
1970 if (MocConst().PathPrefix && Mapping->SourceFile->IsHeader) {
1971 for (std::string const& dir : MocConst().IncludePaths) {
1972 cm::string_view prefix = sourceFile;
1973 if (cmHasPrefix(prefix, dir)) {
1974 prefix.remove_prefix(dir.size());
1975 if (cmHasPrefix(prefix, '/')) {
1976 prefix.remove_prefix(1);
1977 auto slashPos = prefix.rfind('/');
1978 if (slashPos != cm::string_view::npos) {
1979 cmd.emplace_back("-p");
1980 cmd.emplace_back(prefix.substr(0, slashPos));
1982 cmd.emplace_back("-p");
1983 cmd.emplace_back("./");
1990 // Add extra options
1991 cm::append(cmd, MocConst().OptionsExtra);
1992 if (MocConst().CanOutputDependencies) {
1993 cmd.emplace_back("--output-dep-file");
1996 cmd.emplace_back("-o");
1997 cmd.push_back(outputFile);
1999 cmd.push_back(sourceFile);
2002 // Execute moc command
2003 cmWorkerPool::ProcessResultT result;
2004 if (!RunProcess(GenT::MOC, result, cmd, Reason.get())) {
2005 // Moc command failed
2006 std::string includers;
2007 if (!Mapping->IncluderFiles.empty()) {
2008 includers = "included by\n";
2009 for (auto const& item : Mapping->IncluderFiles) {
2010 includers += cmStrCat(" ", MessagePath(item->FileName), '\n');
2013 LogCommandError(GenT::MOC,
2014 cmStrCat("The moc process failed to compile\n ",
2015 MessagePath(sourceFile), "\ninto\n ",
2016 MessagePath(outputFile), '\n', includers,
2017 result.ErrorMessage),
2018 cmd, result.StdOut);
2022 // Moc command success. Print moc output.
2023 if (!result.StdOut.empty()) {
2024 Log().Info(GenT::MOC, result.StdOut);
2027 // Extract dependencies from the dep file moc generated for us
2028 if (MocConst().CanOutputDependencies) {
2029 const std::string depfile = outputFile + ".d";
2030 if (Log().Verbose()) {
2031 Log().Info(GenT::MOC,
2032 "Reading dependencies from " + MessagePath(depfile));
2034 if (!cmSystemTools::FileExists(depfile)) {
2035 Log().Warning(GenT::MOC,
2036 "Dependency file " + MessagePath(depfile) +
2037 " does not exist.");
2040 CacheEntry->Moc.Depends = dependenciesFromDepFile(depfile.c_str());
2044 void cmQtAutoMocUicT::JobCompileUicT::Process()
2046 std::string const& sourceFile = Mapping->SourceFile->FileName;
2047 std::string const& outputFile = Mapping->OutputFile;
2049 // Compose uic command
2050 std::vector<std::string> cmd;
2051 cmd.push_back(UicConst().Executable);
2053 std::vector<std::string> allOpts = UicConst().Options;
2054 auto optionIt = UicConst().UiFiles.find(sourceFile);
2055 if (optionIt != UicConst().UiFiles.end()) {
2056 UicMergeOptions(allOpts, optionIt->second.Options,
2057 (BaseConst().QtVersion.Major == 5));
2059 cm::append(cmd, allOpts);
2061 cmd.emplace_back("-o");
2062 cmd.emplace_back(outputFile);
2063 cmd.emplace_back(sourceFile);
2065 cmWorkerPool::ProcessResultT result;
2066 if (RunProcess(GenT::UIC, result, cmd, Reason.get())) {
2067 // Uic command success
2069 if (!result.StdOut.empty()) {
2070 Log().Info(GenT::UIC, result.StdOut);
2073 // Uic command failed
2074 std::string includers;
2075 for (auto const& item : Mapping->IncluderFiles) {
2076 includers += cmStrCat(" ", MessagePath(item->FileName), '\n');
2078 LogCommandError(GenT::UIC,
2079 cmStrCat("The uic process failed to compile\n ",
2080 MessagePath(sourceFile), "\ninto\n ",
2081 MessagePath(outputFile), "\nincluded by\n",
2082 includers, result.ErrorMessage),
2083 cmd, result.StdOut);
2087 void cmQtAutoMocUicT::JobMocsCompilationT::Process()
2089 // Compose mocs compilation file content
2090 std::string content =
2091 "// This file is autogenerated. Changes will be overwritten.\n";
2093 if (MocEval().CompFiles.empty()) {
2094 // Placeholder content
2095 content += "// No files found that require moc or the moc files are "
2097 "enum some_compilers { need_more_than_nothing };\n";
2100 const bool mc = BaseConst().MultiConfig;
2101 cm::string_view const wrapFront = mc ? "#include <" : "#include \"";
2102 cm::string_view const wrapBack = mc ? ">\n" : "\"\n";
2103 content += cmWrap(wrapFront, MocEval().CompFiles, wrapBack, "");
2106 std::string const& compAbs = MocConst().CompFileAbs;
2107 if (cmQtAutoGenerator::FileDiffers(compAbs, content)) {
2108 // Actually write mocs compilation file
2109 if (Log().Verbose()) {
2110 Log().Info(GenT::MOC,
2111 "Generating MOC compilation " + MessagePath(compAbs));
2113 if (!FileWrite(compAbs, content)) {
2115 cmStrCat("Writing MOC compilation ", MessagePath(compAbs),
2118 } else if (MocEval().CompUpdated) {
2119 // Only touch mocs compilation file
2120 if (Log().Verbose()) {
2121 Log().Info(GenT::MOC,
2122 "Touching MOC compilation " + MessagePath(compAbs));
2124 if (!cmSystemTools::Touch(compAbs, false)) {
2126 cmStrCat("Touching MOC compilation ", MessagePath(compAbs),
2133 * Escapes paths for Ninja depfiles.
2134 * This is a re-implementation of what moc does when writing depfiles.
2136 std::string escapeDependencyPath(cm::string_view path)
2138 std::string escapedPath;
2139 escapedPath.reserve(path.size());
2140 const size_t s = path.size();
2141 int backslashCount = 0;
2142 for (size_t i = 0; i < s; ++i) {
2143 if (path[i] == '\\') {
2146 if (path[i] == '$') {
2147 escapedPath.push_back('$');
2148 } else if (path[i] == '#') {
2149 escapedPath.push_back('\\');
2150 } else if (path[i] == ' ') {
2151 // Double the amount of written backslashes,
2152 // and add one more to escape the space.
2153 while (backslashCount-- >= 0) {
2154 escapedPath.push_back('\\');
2159 escapedPath.push_back(path[i]);
2164 void cmQtAutoMocUicT::JobDepFilesMergeT::Process()
2166 if (Log().Verbose()) {
2167 Log().Info(GenT::MOC,
2168 cmStrCat("Merging MOC dependencies into ",
2169 MessagePath(BaseConst().DepFile.c_str())));
2171 auto processDepFile =
2172 [](const std::string& mocOutputFile) -> std::vector<std::string> {
2173 std::string f = mocOutputFile + ".d";
2174 if (!cmSystemTools::FileExists(f)) {
2177 return dependenciesFromDepFile(f.c_str());
2180 std::vector<std::string> dependencies = BaseConst().ListFiles;
2181 ParseCacheT& parseCache = BaseEval().ParseCache;
2182 auto processMappingEntry = [&](const MappingMapT::value_type& m) {
2183 auto cacheEntry = parseCache.GetOrInsert(m.first);
2184 if (cacheEntry.first->Moc.Depends.empty()) {
2185 cacheEntry.first->Moc.Depends = processDepFile(m.second->OutputFile);
2187 dependencies.insert(dependencies.end(),
2188 cacheEntry.first->Moc.Depends.begin(),
2189 cacheEntry.first->Moc.Depends.end());
2192 std::for_each(MocEval().HeaderMappings.begin(),
2193 MocEval().HeaderMappings.end(), processMappingEntry);
2194 std::for_each(MocEval().SourceMappings.begin(),
2195 MocEval().SourceMappings.end(), processMappingEntry);
2197 // Remove duplicates to make the depfile smaller
2198 std::sort(dependencies.begin(), dependencies.end());
2199 dependencies.erase(std::unique(dependencies.begin(), dependencies.end()),
2200 dependencies.end());
2203 for (const auto& uif : UicEval().UiFiles) {
2204 dependencies.push_back(uif.first);
2208 cmsys::ofstream ofs;
2209 ofs.open(BaseConst().DepFile.c_str(),
2210 (std::ios::out | std::ios::binary | std::ios::trunc));
2213 cmStrCat("Cannot open ", MessagePath(BaseConst().DepFile),
2217 ofs << BaseConst().DepFileRuleName << ": \\" << std::endl;
2218 for (const std::string& file : dependencies) {
2219 ofs << '\t' << escapeDependencyPath(file) << " \\" << std::endl;
2222 cmStrCat("Writing depfile", MessagePath(BaseConst().DepFile),
2228 // Add the CMake executable to re-new cache data if necessary.
2229 // Also, this is the last entry, so don't add a backslash.
2230 ofs << '\t' << escapeDependencyPath(BaseConst().CMakeExecutable)
2234 void cmQtAutoMocUicT::JobFinishT::Process()
2236 Gen()->AbortSuccess();
2239 cmQtAutoMocUicT::cmQtAutoMocUicT()
2240 : cmQtAutoGenerator(GenT::GEN)
2243 cmQtAutoMocUicT::~cmQtAutoMocUicT() = default;
2245 bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info)
2247 // -- Required settings
2248 if (!info.GetBool("MULTI_CONFIG", BaseConst_.MultiConfig, true) ||
2249 !info.GetUInt("QT_VERSION_MAJOR", BaseConst_.QtVersion.Major, true) ||
2250 !info.GetUInt("QT_VERSION_MINOR", BaseConst_.QtVersion.Minor, true) ||
2251 !info.GetUInt("PARALLEL", BaseConst_.ThreadCount, false) ||
2252 !info.GetString("BUILD_DIR", BaseConst_.AutogenBuildDir, true) ||
2253 !info.GetStringConfig("INCLUDE_DIR", BaseConst_.AutogenIncludeDir,
2255 !info.GetString("CMAKE_EXECUTABLE", BaseConst_.CMakeExecutable, true) ||
2256 !info.GetStringConfig("PARSE_CACHE_FILE", BaseConst_.ParseCacheFile,
2258 !info.GetString("DEP_FILE", BaseConst_.DepFile, false) ||
2259 !info.GetString("DEP_FILE_RULE_NAME", BaseConst_.DepFileRuleName,
2261 !info.GetStringConfig("SETTINGS_FILE", SettingsFile_, true) ||
2262 !info.GetArray("CMAKE_LIST_FILES", BaseConst_.ListFiles, true) ||
2263 !info.GetArray("HEADER_EXTENSIONS", BaseConst_.HeaderExtensions, true) ||
2264 !info.GetString("QT_MOC_EXECUTABLE", MocConst_.Executable, false) ||
2265 !info.GetString("QT_UIC_EXECUTABLE", UicConst_.Executable, false)) {
2270 if (!BaseConst_.CMakeExecutableTime.Load(BaseConst_.CMakeExecutable)) {
2271 return info.LogError(cmStrCat("The CMake executable ",
2272 MessagePath(BaseConst_.CMakeExecutable),
2273 " does not exist."));
2276 // -- Evaluate values
2277 BaseConst_.ThreadCount = std::min(BaseConst_.ThreadCount, ParallelMax);
2278 WorkerPool_.SetThreadCount(BaseConst_.ThreadCount);
2281 if (!MocConst_.Executable.empty()) {
2282 // -- Moc is enabled
2283 MocConst_.Enabled = true;
2285 // -- Temporary buffers
2288 std::vector<std::string> MacroNames;
2289 std::vector<std::string> DependFilters;
2292 // -- Required settings
2293 if (!info.GetBool("MOC_RELAXED_MODE", MocConst_.RelaxedMode, false) ||
2294 !info.GetBool("MOC_PATH_PREFIX", MocConst_.PathPrefix, true) ||
2295 !info.GetArray("MOC_SKIP", MocConst_.SkipList, false) ||
2296 !info.GetArrayConfig("MOC_DEFINITIONS", MocConst_.Definitions,
2298 !info.GetArrayConfig("MOC_INCLUDES", MocConst_.IncludePaths, false) ||
2299 !info.GetArray("MOC_OPTIONS", MocConst_.OptionsExtra, false) ||
2300 !info.GetStringConfig("MOC_COMPILATION_FILE", MocConst_.CompFileAbs,
2302 !info.GetArray("MOC_PREDEFS_CMD", MocConst_.PredefsCmd, false) ||
2303 !info.GetStringConfig("MOC_PREDEFS_FILE", MocConst_.PredefsFileAbs,
2304 !MocConst_.PredefsCmd.empty()) ||
2305 !info.GetArray("MOC_MACRO_NAMES", tmp.MacroNames, true) ||
2306 !info.GetArray("MOC_DEPEND_FILTERS", tmp.DependFilters, false)) {
2310 // -- Evaluate settings
2311 for (std::string const& item : tmp.MacroNames) {
2312 MocConst_.MacroFilters.emplace_back(
2313 item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
2315 // Can moc output dependencies or do we need to setup dependency filters?
2316 if (BaseConst_.QtVersion >= IntegerVersion(5, 15)) {
2317 MocConst_.CanOutputDependencies = true;
2319 Json::Value const& val = info.GetValue("MOC_DEPEND_FILTERS");
2320 if (!val.isArray()) {
2321 return info.LogError("MOC_DEPEND_FILTERS JSON value is not an array.");
2323 Json::ArrayIndex const arraySize = val.size();
2324 for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
2325 // Test entry closure
2326 auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
2329 cmStrCat("MOC_DEPEND_FILTERS filter ", ii, ": ", msg));
2334 Json::Value const& pairVal = val[ii];
2336 if (testEntry(pairVal.isArray(), "JSON value is not an array.") ||
2337 testEntry(pairVal.size() == 2, "JSON array size invalid.")) {
2341 Json::Value const& keyVal = pairVal[0u];
2342 Json::Value const& expVal = pairVal[1u];
2343 if (testEntry(keyVal.isString(),
2344 "JSON value for keyword is not a string.") ||
2345 testEntry(expVal.isString(),
2346 "JSON value for regular expression is not a string.")) {
2350 std::string const key = keyVal.asString();
2351 std::string const exp = expVal.asString();
2352 if (testEntry(!key.empty(), "Keyword is empty.") ||
2353 testEntry(!exp.empty(), "Regular expression is empty.")) {
2357 this->MocConst_.DependFilters.emplace_back(key, exp);
2359 this->MocConst_.DependFilters.back().Exp.is_valid(),
2360 cmStrCat("Regular expression compilation failed.\nKeyword: ",
2361 Quoted(key), "\nExpression: ", Quoted(exp)))) {
2366 // Check if moc executable exists (by reading the file time)
2367 if (!MocConst_.ExecutableTime.Load(MocConst_.Executable)) {
2368 return info.LogError(cmStrCat("The moc executable ",
2369 MessagePath(MocConst_.Executable),
2370 " does not exist."));
2375 if (!UicConst_.Executable.empty()) {
2377 UicConst_.Enabled = true;
2379 // -- Required settings
2380 if (!info.GetArray("UIC_SKIP", UicConst_.SkipList, false) ||
2381 !info.GetArray("UIC_SEARCH_PATHS", UicConst_.SearchPaths, false) ||
2382 !info.GetArrayConfig("UIC_OPTIONS", UicConst_.Options, false)) {
2387 Json::Value const& val = info.GetValue("UIC_UI_FILES");
2388 if (!val.isArray()) {
2389 return info.LogError("UIC_UI_FILES JSON value is not an array.");
2391 Json::ArrayIndex const arraySize = val.size();
2392 for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
2393 // Test entry closure
2394 auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
2396 info.LogError(cmStrCat("UIC_UI_FILES entry ", ii, ": ", msg));
2401 Json::Value const& entry = val[ii];
2402 if (testEntry(entry.isArray(), "JSON value is not an array.") ||
2403 testEntry(entry.size() == 2, "JSON array size invalid.")) {
2407 Json::Value const& entryName = entry[0u];
2408 Json::Value const& entryOptions = entry[1u];
2409 if (testEntry(entryName.isString(),
2410 "JSON value for name is not a string.") ||
2411 testEntry(entryOptions.isArray(),
2412 "JSON value for options is not an array.")) {
2416 auto& uiFile = UicConst_.UiFiles[entryName.asString()];
2417 InfoT::GetJsonArray(uiFile.Options, entryOptions);
2421 // -- Evaluate settings
2422 // Check if uic executable exists (by reading the file time)
2423 if (!UicConst_.ExecutableTime.Load(UicConst_.Executable)) {
2424 return info.LogError(cmStrCat("The uic executable ",
2425 MessagePath(UicConst_.Executable),
2426 " does not exist."));
2432 Json::Value const& val = info.GetValue("HEADERS");
2433 if (!val.isArray()) {
2434 return info.LogError("HEADERS JSON value is not an array.");
2436 Json::ArrayIndex const arraySize = val.size();
2437 for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
2438 // Test entry closure
2439 auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
2441 info.LogError(cmStrCat("HEADERS entry ", ii, ": ", msg));
2446 Json::Value const& entry = val[ii];
2447 if (testEntry(entry.isArray(), "JSON value is not an array.") ||
2448 testEntry(entry.size() == 3, "JSON array size invalid.")) {
2452 Json::Value const& entryName = entry[0u];
2453 Json::Value const& entryFlags = entry[1u];
2454 Json::Value const& entryBuild = entry[2u];
2455 if (testEntry(entryName.isString(),
2456 "JSON value for name is not a string.") ||
2457 testEntry(entryFlags.isString(),
2458 "JSON value for flags is not a string.") ||
2459 testEntry(entryBuild.isString(),
2460 "JSON value for build path is not a string.")) {
2464 std::string name = entryName.asString();
2465 std::string flags = entryFlags.asString();
2466 std::string build = entryBuild.asString();
2467 if (testEntry(flags.size() == 2, "Invalid flags string size")) {
2471 cmFileTime fileTime;
2472 if (!fileTime.Load(name)) {
2473 return info.LogError(cmStrCat(
2474 "The header file ", this->MessagePath(name), " does not exist."));
2477 SourceFileHandleT sourceHandle = std::make_shared<SourceFileT>(name);
2478 sourceHandle->FileTime = fileTime;
2479 sourceHandle->IsHeader = true;
2480 sourceHandle->Moc = (flags[0] == 'M');
2481 sourceHandle->Uic = (flags[1] == 'U');
2482 if (sourceHandle->Moc && MocConst().Enabled) {
2483 if (build.empty()) {
2484 return info.LogError(
2485 cmStrCat("Header file ", ii, " build path is empty"));
2487 sourceHandle->BuildPath = std::move(build);
2489 BaseEval().Headers.emplace(std::move(name), std::move(sourceHandle));
2495 Json::Value const& val = info.GetValue("SOURCES");
2496 if (!val.isArray()) {
2497 return info.LogError("SOURCES JSON value is not an array.");
2499 Json::ArrayIndex const arraySize = val.size();
2500 for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
2501 // Test entry closure
2502 auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
2504 info.LogError(cmStrCat("SOURCES entry ", ii, ": ", msg));
2509 Json::Value const& entry = val[ii];
2510 if (testEntry(entry.isArray(), "JSON value is not an array.") ||
2511 testEntry(entry.size() == 2, "JSON array size invalid.")) {
2515 Json::Value const& entryName = entry[0u];
2516 Json::Value const& entryFlags = entry[1u];
2517 if (testEntry(entryName.isString(),
2518 "JSON value for name is not a string.") ||
2519 testEntry(entryFlags.isString(),
2520 "JSON value for flags is not a string.")) {
2524 std::string name = entryName.asString();
2525 std::string flags = entryFlags.asString();
2526 if (testEntry(flags.size() == 2, "Invalid flags string size")) {
2530 cmFileTime fileTime;
2531 if (!fileTime.Load(name)) {
2532 return info.LogError(cmStrCat(
2533 "The source file ", this->MessagePath(name), " does not exist."));
2536 SourceFileHandleT sourceHandle = std::make_shared<SourceFileT>(name);
2537 sourceHandle->FileTime = fileTime;
2538 sourceHandle->IsHeader = false;
2539 sourceHandle->Moc = (flags[0] == 'M');
2540 sourceHandle->Uic = (flags[1] == 'U');
2541 BaseEval().Sources.emplace(std::move(name), std::move(sourceHandle));
2545 // -- Init derived information
2547 if (MocConst().Enabled) {
2548 // Compose moc includes list
2550 // Compute framework paths
2551 std::set<std::string> frameworkPaths;
2552 for (std::string const& path : MocConst().IncludePaths) {
2553 // Extract framework path
2554 if (cmHasLiteralSuffix(path, ".framework/Headers")) {
2555 // Go up twice to get to the framework root
2556 std::vector<std::string> pathComponents;
2557 cmSystemTools::SplitPath(path, pathComponents);
2558 frameworkPaths.emplace(cmSystemTools::JoinPath(
2559 pathComponents.begin(), pathComponents.end() - 2));
2563 MocConst_.OptionsIncludes.reserve(MocConst().IncludePaths.size() +
2564 frameworkPaths.size() * 2);
2566 for (std::string const& path : MocConst().IncludePaths) {
2567 MocConst_.OptionsIncludes.emplace_back("-I" + path);
2569 // Append framework includes
2570 for (std::string const& path : frameworkPaths) {
2571 MocConst_.OptionsIncludes.emplace_back("-F");
2572 MocConst_.OptionsIncludes.push_back(path);
2576 // Compose moc definitions list
2578 MocConst_.OptionsDefinitions.reserve(MocConst().Definitions.size());
2579 for (std::string const& def : MocConst().Definitions) {
2580 MocConst_.OptionsDefinitions.emplace_back("-D" + def);
2588 template <class JOBTYPE>
2589 void cmQtAutoMocUicT::CreateParseJobs(SourceFileMapT const& sourceMap)
2591 cmFileTime const parseCacheTime = BaseEval().ParseCacheTime;
2592 ParseCacheT& parseCache = BaseEval().ParseCache;
2593 for (auto& src : sourceMap) {
2594 // Get or create the file parse data reference
2595 ParseCacheT::GetOrInsertT cacheEntry = parseCache.GetOrInsert(src.first);
2596 src.second->ParseData = std::move(cacheEntry.first);
2597 // Create a parse job if the cache file was missing or is older
2598 if (cacheEntry.second || src.second->FileTime.Newer(parseCacheTime)) {
2599 BaseEval().ParseCacheChanged = true;
2600 WorkerPool().EmplaceJob<JOBTYPE>(src.second);
2605 /** Concurrently callable implementation of cmSystemTools::CollapseFullPath */
2606 std::string cmQtAutoMocUicT::CollapseFullPathTS(std::string const& path) const
2608 std::lock_guard<std::mutex> guard(CMakeLibMutex_);
2609 return cmSystemTools::CollapseFullPath(path, ProjectDirs().CurrentSource);
2612 void cmQtAutoMocUicT::InitJobs()
2614 // Add moc_predefs.h job
2615 if (MocConst().Enabled && !MocConst().PredefsCmd.empty()) {
2616 WorkerPool().EmplaceJob<JobMocPredefsT>();
2619 // Add header parse jobs
2620 CreateParseJobs<JobParseHeaderT>(BaseEval().Headers);
2621 // Add source parse jobs
2622 CreateParseJobs<JobParseSourceT>(BaseEval().Sources);
2624 // Add parse cache evaluations jobs
2626 // Add a fence job to ensure all parsing has finished
2627 WorkerPool().EmplaceJob<JobFenceT>();
2628 if (MocConst().Enabled) {
2629 WorkerPool().EmplaceJob<JobEvalCacheMocT>();
2631 if (UicConst().Enabled) {
2632 WorkerPool().EmplaceJob<JobEvalCacheUicT>();
2635 WorkerPool().EmplaceJob<JobEvalCacheFinishT>();
2639 bool cmQtAutoMocUicT::Process()
2643 if (!CreateDirectories()) {
2647 if (!WorkerPool_.Process(this)) {
2653 if (!ParseCacheWrite()) {
2656 if (!SettingsFileWrite()) {
2662 void cmQtAutoMocUicT::SettingsFileRead()
2664 // Compose current settings strings
2666 cmCryptoHash cryptoHash(cmCryptoHash::AlgoSHA256);
2667 auto cha = [&cryptoHash](cm::string_view value) {
2668 cryptoHash.Append(value);
2669 cryptoHash.Append(";");
2672 if (MocConst_.Enabled) {
2673 cryptoHash.Initialize();
2674 cha(MocConst().Executable);
2675 for (auto const& item : MocConst().OptionsDefinitions) {
2678 for (auto const& item : MocConst().OptionsIncludes) {
2681 for (auto const& item : MocConst().OptionsExtra) {
2684 for (auto const& item : MocConst().PredefsCmd) {
2687 for (auto const& filter : MocConst().DependFilters) {
2690 for (auto const& filter : MocConst().MacroFilters) {
2693 SettingsStringMoc_ = cryptoHash.FinalizeHex();
2696 if (UicConst().Enabled) {
2697 cryptoHash.Initialize();
2698 cha(UicConst().Executable);
2699 std::for_each(UicConst().Options.begin(), UicConst().Options.end(), cha);
2700 for (const auto& item : UicConst().UiFiles) {
2702 auto const& opts = item.second.Options;
2703 std::for_each(opts.begin(), opts.end(), cha);
2705 SettingsStringUic_ = cryptoHash.FinalizeHex();
2709 // Read old settings and compare
2711 std::string content;
2712 if (cmQtAutoGenerator::FileRead(content, SettingsFile_)) {
2713 if (MocConst().Enabled) {
2714 if (SettingsStringMoc_ != SettingsFind(content, "moc")) {
2715 MocConst_.SettingsChanged = true;
2718 if (UicConst().Enabled) {
2719 if (SettingsStringUic_ != SettingsFind(content, "uic")) {
2720 UicConst_.SettingsChanged = true;
2723 // In case any setting changed remove the old settings file.
2724 // This triggers a full rebuild on the next run if the current
2725 // build is aborted before writing the current settings in the end.
2726 if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
2727 cmSystemTools::RemoveFile(SettingsFile_);
2730 // Settings file read failed
2731 if (MocConst().Enabled) {
2732 MocConst_.SettingsChanged = true;
2734 if (UicConst().Enabled) {
2735 UicConst_.SettingsChanged = true;
2741 bool cmQtAutoMocUicT::SettingsFileWrite()
2743 // Only write if any setting changed
2744 if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
2745 if (Log().Verbose()) {
2748 cmStrCat("Writing the settings file ", MessagePath(SettingsFile_)));
2750 // Compose settings file content
2751 std::string content;
2753 auto SettingAppend = [&content](cm::string_view key,
2754 cm::string_view value) {
2755 if (!value.empty()) {
2756 content += cmStrCat(key, ':', value, '\n');
2759 SettingAppend("moc", SettingsStringMoc_);
2760 SettingAppend("uic", SettingsStringUic_);
2762 // Write settings file
2764 if (!cmQtAutoGenerator::FileWrite(SettingsFile_, content, &error)) {
2765 Log().Error(GenT::GEN,
2766 cmStrCat("Writing the settings file ",
2767 MessagePath(SettingsFile_), " failed.\n", error));
2768 // Remove old settings file to trigger a full rebuild on the next run
2769 cmSystemTools::RemoveFile(SettingsFile_);
2776 void cmQtAutoMocUicT::ParseCacheRead()
2778 cm::string_view reason;
2779 // Don't read the cache if it is invalid
2780 if (!BaseEval().ParseCacheTime.Load(BaseConst().ParseCacheFile)) {
2781 reason = "Refreshing parse cache because it doesn't exist.";
2782 } else if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
2783 reason = "Refreshing parse cache because the settings changed.";
2784 } else if (BaseEval().ParseCacheTime.Older(
2785 BaseConst().CMakeExecutableTime)) {
2787 "Refreshing parse cache because it is older than the CMake executable.";
2790 if (!reason.empty()) {
2791 // Don't read but refresh the complete parse cache
2792 if (Log().Verbose()) {
2793 Log().Info(GenT::GEN, reason);
2795 BaseEval().ParseCacheChanged = true;
2798 BaseEval().ParseCache.ReadFromFile(BaseConst().ParseCacheFile);
2802 bool cmQtAutoMocUicT::ParseCacheWrite()
2804 if (BaseEval().ParseCacheChanged) {
2805 if (Log().Verbose()) {
2806 Log().Info(GenT::GEN,
2807 cmStrCat("Writing the parse cache file ",
2808 MessagePath(BaseConst().ParseCacheFile)));
2810 if (!BaseEval().ParseCache.WriteToFile(BaseConst().ParseCacheFile)) {
2811 Log().Error(GenT::GEN,
2812 cmStrCat("Writing the parse cache file ",
2813 MessagePath(BaseConst().ParseCacheFile),
2821 bool cmQtAutoMocUicT::CreateDirectories()
2823 // Create AUTOGEN include directory
2824 if (!cmSystemTools::MakeDirectory(BaseConst().AutogenIncludeDir)) {
2825 Log().Error(GenT::GEN,
2826 cmStrCat("Creating the AUTOGEN include directory ",
2827 MessagePath(BaseConst().AutogenIncludeDir),
2834 std::vector<std::string> cmQtAutoMocUicT::dependenciesFromDepFile(
2835 const char* filePath)
2837 cmGccDepfileContent content = cmReadGccDepfile(filePath);
2838 if (content.empty()) {
2842 // Moc outputs a depfile with exactly one rule.
2843 // Discard the rule and return the dependencies.
2844 return content.front().paths;
2847 void cmQtAutoMocUicT::Abort(bool error)
2850 JobError_.store(true);
2852 WorkerPool_.Abort();
2855 std::string cmQtAutoMocUicT::AbsoluteBuildPath(
2856 cm::string_view relativePath) const
2858 return cmStrCat(BaseConst().AutogenBuildDir, '/', relativePath);
2861 std::string cmQtAutoMocUicT::AbsoluteIncludePath(
2862 cm::string_view relativePath) const
2864 return cmStrCat(BaseConst().AutogenIncludeDir, '/', relativePath);
2867 } // End of unnamed namespace
2869 bool cmQtAutoMocUic(cm::string_view infoFile, cm::string_view config)
2871 return cmQtAutoMocUicT().Run(infoFile, config);