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 <cm3p/json/value.h>
23 #include "cmsys/FStream.hxx"
24 #include "cmsys/RegularExpression.hxx"
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;
189 /** Shared common variables. */
194 bool ParseCacheChanged = false;
195 cmFileTime ParseCacheTime;
196 ParseCacheT ParseCache;
199 SourceFileMapT Headers;
200 SourceFileMapT Sources;
211 MocSettingsT(MocSettingsT const&) = delete;
212 MocSettingsT& operator=(MocSettingsT const&) = delete;
215 bool skipped(std::string const& fileName) const;
216 std::string MacrosString() const;
219 bool Enabled = false;
220 bool SettingsChanged = false;
221 bool RelaxedMode = false;
222 bool PathPrefix = false;
223 bool CanOutputDependencies = false;
224 cmFileTime ExecutableTime;
225 std::string Executable;
226 std::string CompFileAbs;
227 std::string PredefsFileAbs;
228 std::unordered_set<std::string> SkipList;
229 std::vector<std::string> IncludePaths;
230 std::vector<std::string> Definitions;
231 std::vector<std::string> OptionsIncludes;
232 std::vector<std::string> OptionsDefinitions;
233 std::vector<std::string> OptionsExtra;
234 std::vector<std::string> PredefsCmd;
235 std::vector<KeyExpT> DependFilters;
236 std::vector<KeyExpT> MacroFilters;
237 cmsys::RegularExpression RegExpInclude;
240 /** Moc shared variables. */
244 // -- predefines file
245 cmFileTime PredefsTime;
247 MappingMapT HeaderMappings;
248 MappingMapT SourceMappings;
249 MappingMapT Includes;
250 // -- Discovered files
251 SourceFileMapT HeadersDiscovered;
252 // -- Output directories
253 std::unordered_set<std::string> OutputDirs;
254 // -- Mocs compilation
255 bool CompUpdated = false;
256 std::vector<std::string> CompFiles;
265 std::vector<std::string> Options;
272 UicSettingsT(UicSettingsT const&) = delete;
273 UicSettingsT& operator=(UicSettingsT const&) = delete;
276 bool skipped(std::string const& fileName) const;
279 bool Enabled = false;
280 bool SettingsChanged = false;
281 cmFileTime ExecutableTime;
282 std::string Executable;
283 std::unordered_set<std::string> SkipList;
284 std::vector<std::string> Options;
285 std::unordered_map<std::string, UiFile> UiFiles;
286 std::vector<std::string> SearchPaths;
287 cmsys::RegularExpression RegExpInclude;
290 /** Uic shared variables. */
294 // -- Discovered files
295 SourceFileMapT UiFiles;
297 MappingMapT Includes;
298 // -- Output directories
299 std::unordered_set<std::string> OutputDirs;
302 /** Abstract job class for concurrent job processing. */
303 class JobT : public cmWorkerPool::JobT
306 /** Protected default constructor. */
307 JobT(bool fence = false)
308 : cmWorkerPool::JobT(fence)
312 //! Get the generator. Only valid during Process() call!
313 cmQtAutoMocUicT* Gen() const
315 return static_cast<cmQtAutoMocUicT*>(UserData());
318 // -- Accessors. Only valid during Process() call!
319 Logger const& Log() const { return Gen()->Log(); }
320 BaseSettingsT const& BaseConst() const { return Gen()->BaseConst(); }
321 BaseEvalT& BaseEval() const { return Gen()->BaseEval(); }
322 MocSettingsT const& MocConst() const { return Gen()->MocConst(); }
323 MocEvalT& MocEval() const { return Gen()->MocEval(); }
324 UicSettingsT const& UicConst() const { return Gen()->UicConst(); }
325 UicEvalT& UicEval() const { return Gen()->UicEval(); }
328 std::string MessagePath(cm::string_view path) const
330 return Gen()->MessagePath(path);
332 // - Error logging with automatic abort
333 void LogError(GenT genType, cm::string_view message) const;
334 void LogCommandError(GenT genType, cm::string_view message,
335 std::vector<std::string> const& command,
336 std::string const& output) const;
338 /** @brief Run an external process. Use only during Process() call! */
339 bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result,
340 std::vector<std::string> const& command,
341 std::string* infoMessage = nullptr);
344 /** Fence job utility class. */
345 class JobFenceT : public JobT
352 void Process() override{};
355 /** Generate moc_predefs.h. */
356 class JobMocPredefsT : public JobFenceT
358 void Process() override;
359 bool Update(std::string* reason) const;
362 /** File parse job base class. */
363 class JobParseT : public JobT
366 JobParseT(SourceFileHandleT fileHandle)
367 : FileHandle(std::move(fileHandle))
373 void CreateKeys(std::vector<IncludeKeyT>& container,
374 std::set<std::string> const& source,
375 std::size_t basePrefixLength);
377 void MocDependecies();
382 SourceFileHandleT FileHandle;
386 /** Header file parse job. */
387 class JobParseHeaderT : public JobParseT
390 using JobParseT::JobParseT;
391 void Process() override;
394 /** Source file parse job. */
395 class JobParseSourceT : public JobParseT
398 using JobParseT::JobParseT;
399 void Process() override;
402 /** Evaluate cached file parse data - moc. */
403 class JobEvalCacheT : public JobT
406 std::string MessageSearchLocations() const;
407 std::vector<std::string> SearchLocations;
410 /** Evaluate cached file parse data - moc. */
411 class JobEvalCacheMocT : public JobEvalCacheT
413 void Process() override;
414 bool EvalHeader(SourceFileHandleT source);
415 bool EvalSource(SourceFileHandleT const& source);
416 bool FindIncludedHeader(SourceFileHandleT& headerHandle,
417 cm::string_view includerDir,
418 cm::string_view includeBase);
419 bool RegisterIncluded(std::string const& includeString,
420 SourceFileHandleT includerFileHandle,
421 SourceFileHandleT sourceFileHandle) const;
422 void RegisterMapping(MappingHandleT mappingHandle) const;
423 std::string MessageHeader(cm::string_view headerBase) const;
426 /** Evaluate cached file parse data - uic. */
427 class JobEvalCacheUicT : public JobEvalCacheT
429 void Process() override;
430 bool EvalFile(SourceFileHandleT const& sourceFileHandle);
431 bool FindIncludedUi(cm::string_view sourceDirPrefix,
432 cm::string_view includePrefix);
433 bool RegisterMapping(std::string const& includeString,
434 SourceFileHandleT includerFileHandle);
437 SourceFileHandleT UiFileHandle;
440 /** Evaluate cached file parse data - finish */
441 class JobEvalCacheFinishT : public JobFenceT
443 void Process() override;
446 /** Dependency probing base job. */
447 class JobProbeDepsT : public JobT
451 /** Probes file dependencies and generates moc compile jobs. */
452 class JobProbeDepsMocT : public JobProbeDepsT
454 void Process() override;
455 bool Generate(MappingHandleT const& mapping, bool compFile) const;
456 bool Probe(MappingT const& mapping, std::string* reason) const;
457 std::pair<std::string, cmFileTime> FindDependency(
458 std::string const& sourceDir, std::string const& includeString) const;
461 /** Probes file dependencies and generates uic compile jobs. */
462 class JobProbeDepsUicT : public JobProbeDepsT
464 void Process() override;
465 bool Probe(MappingT const& mapping, std::string* reason) const;
468 /** Dependency probing finish job. */
469 class JobProbeDepsFinishT : public JobFenceT
471 void Process() override;
474 /** Meta compiler base job. */
475 class JobCompileT : public JobT
478 JobCompileT(MappingHandleT uicMapping, std::unique_ptr<std::string> reason)
479 : Mapping(std::move(uicMapping))
480 , Reason(std::move(reason))
485 MappingHandleT Mapping;
486 std::unique_ptr<std::string> Reason;
489 /** moc compiles a file. */
490 class JobCompileMocT : public JobCompileT
493 JobCompileMocT(MappingHandleT uicMapping,
494 std::unique_ptr<std::string> reason,
495 ParseCacheT::FileHandleT cacheEntry)
496 : JobCompileT(std::move(uicMapping), std::move(reason))
497 , CacheEntry(std::move(cacheEntry))
500 void Process() override;
503 ParseCacheT::FileHandleT CacheEntry;
506 /** uic compiles a file. */
507 class JobCompileUicT : public JobCompileT
510 using JobCompileT::JobCompileT;
511 void Process() override;
514 /** Generate mocs_compilation.cpp. */
515 class JobMocsCompilationT : public JobFenceT
518 void Process() override;
521 class JobDepFilesMergeT : public JobFenceT
524 void Process() override;
527 /** @brief The last job. */
528 class JobFinishT : public JobFenceT
531 void Process() override;
534 // -- Const settings interface
535 BaseSettingsT const& BaseConst() const { return this->BaseConst_; }
536 BaseEvalT& BaseEval() { return this->BaseEval_; }
537 MocSettingsT const& MocConst() const { return this->MocConst_; }
538 MocEvalT& MocEval() { return this->MocEval_; }
539 UicSettingsT const& UicConst() const { return this->UicConst_; }
540 UicEvalT& UicEval() { return this->UicEval_; }
542 // -- Parallel job processing interface
543 cmWorkerPool& WorkerPool() { return WorkerPool_; }
544 void AbortError() { Abort(true); }
545 void AbortSuccess() { Abort(false); }
548 std::string AbsoluteBuildPath(cm::string_view relativePath) const;
549 std::string AbsoluteIncludePath(cm::string_view relativePath) const;
550 template <class JOBTYPE>
551 void CreateParseJobs(SourceFileMapT const& sourceMap);
552 std::string CollapseFullPathTS(std::string const& path) const;
555 // -- Abstract processing interface
556 bool InitFromInfo(InfoT const& info) override;
558 bool Process() override;
560 void SettingsFileRead();
561 bool SettingsFileWrite();
563 void ParseCacheRead();
564 bool ParseCacheWrite();
565 // -- Thread processing
566 void Abort(bool error);
568 bool CreateDirectories();
569 // -- Support for depfiles
570 static std::vector<std::string> dependenciesFromDepFile(
571 const char* filePath);
575 BaseSettingsT BaseConst_;
577 MocSettingsT MocConst_;
579 UicSettingsT UicConst_;
582 std::string SettingsFile_;
583 std::string SettingsStringMoc_;
584 std::string SettingsStringUic_;
585 // -- Worker thread pool
586 std::atomic<bool> JobError_ = ATOMIC_VAR_INIT(false);
587 cmWorkerPool WorkerPool_;
588 // -- Concurrent processing
589 mutable std::mutex CMakeLibMutex_;
592 cmQtAutoMocUicT::IncludeKeyT::IncludeKeyT(std::string const& key,
593 std::size_t basePrefixLength)
595 , Dir(SubDirPrefix(key))
596 , Base(cmSystemTools::GetFilenameWithoutLastExtension(key))
598 if (basePrefixLength != 0) {
599 Base = Base.substr(basePrefixLength);
603 void cmQtAutoMocUicT::ParseCacheT::FileT::Clear()
606 Moc.Include.Underscore.clear();
607 Moc.Include.Dot.clear();
614 cmQtAutoMocUicT::ParseCacheT::GetOrInsertT
615 cmQtAutoMocUicT::ParseCacheT::GetOrInsert(std::string const& fileName)
617 // Find existing entry
619 auto it = Map_.find(fileName);
620 if (it != Map_.end()) {
621 return GetOrInsertT{ it->second, false };
627 Map_.emplace(fileName, std::make_shared<FileT>()).first->second, true
631 cmQtAutoMocUicT::ParseCacheT::ParseCacheT() = default;
632 cmQtAutoMocUicT::ParseCacheT::~ParseCacheT() = default;
634 bool cmQtAutoMocUicT::ParseCacheT::ReadFromFile(std::string const& fileName)
636 cmsys::ifstream fin(fileName.c_str());
640 FileHandleT fileHandle;
643 while (std::getline(fin, line)) {
644 // Check if this an empty or a comment line
645 if (line.empty() || line.front() == '#') {
648 // Drop carriage return character at the end
649 if (line.back() == '\r') {
655 // Check if this a file name line
656 if (line.front() != ' ') {
657 fileHandle = GetOrInsert(line).first;
661 // Bad line or bad file handle
662 if (!fileHandle || (line.size() < 6)) {
666 constexpr std::size_t offset = 5;
667 if (cmHasLiteralPrefix(line, " mmc:")) {
668 fileHandle->Moc.Macro = line.substr(offset);
671 if (cmHasLiteralPrefix(line, " miu:")) {
672 fileHandle->Moc.Include.Underscore.emplace_back(line.substr(offset),
673 MocUnderscoreLength);
676 if (cmHasLiteralPrefix(line, " mid:")) {
677 fileHandle->Moc.Include.Dot.emplace_back(line.substr(offset), 0);
680 if (cmHasLiteralPrefix(line, " mdp:")) {
681 fileHandle->Moc.Depends.emplace_back(line.substr(offset));
684 if (cmHasLiteralPrefix(line, " uic:")) {
685 fileHandle->Uic.Include.emplace_back(line.substr(offset),
689 if (cmHasLiteralPrefix(line, " udp:")) {
690 fileHandle->Uic.Depends.emplace_back(line.substr(offset));
697 bool cmQtAutoMocUicT::ParseCacheT::WriteToFile(std::string const& fileName)
699 cmGeneratedFileStream ofs(fileName);
703 ofs << "# Generated by CMake. Changes will be overwritten.\n";
704 for (auto const& pair : Map_) {
705 ofs << pair.first << '\n';
706 FileT const& file = *pair.second;
707 if (!file.Moc.Macro.empty()) {
708 ofs << " mmc:" << file.Moc.Macro << '\n';
710 for (IncludeKeyT const& item : file.Moc.Include.Underscore) {
711 ofs << " miu:" << item.Key << '\n';
713 for (IncludeKeyT const& item : file.Moc.Include.Dot) {
714 ofs << " mid:" << item.Key << '\n';
716 for (std::string const& item : file.Moc.Depends) {
717 ofs << " mdp:" << item << '\n';
719 for (IncludeKeyT const& item : file.Uic.Include) {
720 ofs << " uic:" << item.Key << '\n';
722 for (std::string const& item : file.Uic.Depends) {
723 ofs << " udp:" << item << '\n';
729 cmQtAutoMocUicT::BaseSettingsT::BaseSettingsT() = default;
730 cmQtAutoMocUicT::BaseSettingsT::~BaseSettingsT() = default;
732 cmQtAutoMocUicT::MocSettingsT::MocSettingsT()
734 RegExpInclude.compile(
735 "(^|\n)[ \t]*#[ \t]*include[ \t]+"
736 "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
739 cmQtAutoMocUicT::MocSettingsT::~MocSettingsT() = default;
741 bool cmQtAutoMocUicT::MocSettingsT::skipped(std::string const& fileName) const
743 return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
746 std::string cmQtAutoMocUicT::MocSettingsT::MacrosString() const
749 const auto itB = MacroFilters.cbegin();
750 const auto itE = MacroFilters.cend();
751 const auto itL = itE - 1;
753 for (; itC != itE; ++itC) {
768 cmQtAutoMocUicT::UicSettingsT::UicSettingsT()
770 RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+"
771 "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
774 cmQtAutoMocUicT::UicSettingsT::~UicSettingsT() = default;
776 bool cmQtAutoMocUicT::UicSettingsT::skipped(std::string const& fileName) const
778 return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
781 void cmQtAutoMocUicT::JobT::LogError(GenT genType,
782 cm::string_view message) const
785 Gen()->Log().Error(genType, message);
788 void cmQtAutoMocUicT::JobT::LogCommandError(
789 GenT genType, cm::string_view message,
790 std::vector<std::string> const& command, std::string const& output) const
793 Gen()->Log().ErrorCommand(genType, message, command, output);
796 bool cmQtAutoMocUicT::JobT::RunProcess(GenT genType,
797 cmWorkerPool::ProcessResultT& result,
798 std::vector<std::string> const& command,
799 std::string* infoMessage)
802 if (Log().Verbose()) {
803 cm::string_view info;
804 if (infoMessage != nullptr) {
809 info.empty() || cmHasSuffix(info, '\n') ? "" : "\n",
810 QuotedCommand(command), '\n'));
813 return cmWorkerPool::JobT::RunProcess(result, command,
814 BaseConst().AutogenBuildDir);
817 void cmQtAutoMocUicT::JobMocPredefsT::Process()
819 // (Re)generate moc_predefs.h on demand
820 std::unique_ptr<std::string> reason;
821 if (Log().Verbose()) {
822 reason = cm::make_unique<std::string>();
824 if (!Update(reason.get())) {
827 std::string const& predefsFileAbs = MocConst().PredefsFileAbs;
829 cmWorkerPool::ProcessResultT result;
832 std::vector<std::string> cmd = MocConst().PredefsCmd;
834 cm::append(cmd, MocConst().OptionsDefinitions);
836 cm::append(cmd, MocConst().OptionsIncludes);
838 if (!RunProcess(GenT::MOC, result, cmd, reason.get())) {
839 LogCommandError(GenT::MOC,
840 cmStrCat("The content generation command for ",
841 MessagePath(predefsFileAbs), " failed.\n",
842 result.ErrorMessage),
848 // (Re)write predefs file only on demand
849 if (cmQtAutoGenerator::FileDiffers(predefsFileAbs, result.StdOut)) {
850 if (!cmQtAutoGenerator::FileWrite(predefsFileAbs, result.StdOut)) {
853 cmStrCat("Writing ", MessagePath(predefsFileAbs), " failed."));
857 // Touch to update the time stamp
858 if (Log().Verbose()) {
859 Log().Info(GenT::MOC, "Touching " + MessagePath(predefsFileAbs));
861 if (!cmSystemTools::Touch(predefsFileAbs, false)) {
864 cmStrCat("Touching ", MessagePath(predefsFileAbs), " failed."));
870 // Read file time afterwards
871 if (!MocEval().PredefsTime.Load(predefsFileAbs)) {
873 cmStrCat("Reading the file time of ", MessagePath(predefsFileAbs),
879 bool cmQtAutoMocUicT::JobMocPredefsT::Update(std::string* reason) const
881 // Test if the file exists
882 if (!MocEval().PredefsTime.Load(MocConst().PredefsFileAbs)) {
883 if (reason != nullptr) {
884 *reason = cmStrCat("Generating ", MessagePath(MocConst().PredefsFileAbs),
885 ", because it doesn't exist.");
890 // Test if the settings changed
891 if (MocConst().SettingsChanged) {
892 if (reason != nullptr) {
893 *reason = cmStrCat("Generating ", MessagePath(MocConst().PredefsFileAbs),
894 ", because the moc settings changed.");
899 // Test if the executable is newer
901 std::string const& exec = MocConst().PredefsCmd.at(0);
903 if (execTime.Load(exec)) {
904 if (MocEval().PredefsTime.Older(execTime)) {
905 if (reason != nullptr) {
907 cmStrCat("Generating ", MessagePath(MocConst().PredefsFileAbs),
908 " because it is older than ", MessagePath(exec), '.');
918 bool cmQtAutoMocUicT::JobParseT::ReadFile()
920 // Clear old parse information
921 FileHandle->ParseData->Clear();
922 std::string const& fileName = FileHandle->FileName;
924 if (Log().Verbose()) {
925 Log().Info(GenT::GEN, cmStrCat("Parsing ", MessagePath(fileName)));
930 if (!cmQtAutoGenerator::FileRead(Content, fileName, &error)) {
933 cmStrCat("Could not read ", MessagePath(fileName), ".\n", error));
938 if (Content.empty()) {
939 Log().Warning(GenT::GEN, cmStrCat(MessagePath(fileName), " is empty."));
945 void cmQtAutoMocUicT::JobParseT::CreateKeys(
946 std::vector<IncludeKeyT>& container, std::set<std::string> const& source,
947 std::size_t basePrefixLength)
949 if (source.empty()) {
952 container.reserve(source.size());
953 for (std::string const& src : source) {
954 container.emplace_back(src, basePrefixLength);
958 void cmQtAutoMocUicT::JobParseT::MocMacro()
960 for (KeyExpT const& filter : MocConst().MacroFilters) {
961 // Run a simple find string check
962 if (Content.find(filter.Key) == std::string::npos) {
965 // Run the expensive regular expression check loop
966 cmsys::RegularExpressionMatch match;
967 if (filter.Exp.find(Content.c_str(), match)) {
968 // Keep detected macro name
969 FileHandle->ParseData->Moc.Macro = filter.Key;
975 void cmQtAutoMocUicT::JobParseT::MocDependecies()
977 if (MocConst().DependFilters.empty() || MocConst().CanOutputDependencies) {
981 // Find dependency strings
982 std::set<std::string> parseDepends;
983 for (KeyExpT const& filter : MocConst().DependFilters) {
984 // Run a simple find string check
985 if (Content.find(filter.Key) == std::string::npos) {
988 // Run the expensive regular expression check loop
989 const char* contentChars = Content.c_str();
990 cmsys::RegularExpressionMatch match;
991 while (filter.Exp.find(contentChars, match)) {
993 std::string dep = match.match(1);
995 parseDepends.emplace(std::move(dep));
998 contentChars += match.end();
1002 // Store dependency strings
1004 auto& Depends = FileHandle->ParseData->Moc.Depends;
1005 Depends.reserve(parseDepends.size());
1006 for (std::string const& item : parseDepends) {
1007 Depends.emplace_back(item);
1008 // Replace end of line characters in filenames
1009 std::string& path = Depends.back();
1010 std::replace(path.begin(), path.end(), '\n', ' ');
1011 std::replace(path.begin(), path.end(), '\r', ' ');
1016 void cmQtAutoMocUicT::JobParseT::MocIncludes()
1018 if (Content.find("moc") == std::string::npos) {
1022 std::set<std::string> underscore;
1023 std::set<std::string> dot;
1025 const char* contentChars = Content.c_str();
1026 cmsys::RegularExpression const& regExp = MocConst().RegExpInclude;
1027 cmsys::RegularExpressionMatch match;
1028 while (regExp.find(contentChars, match)) {
1029 std::string incString = match.match(2);
1030 std::string const incBase =
1031 cmSystemTools::GetFilenameWithoutLastExtension(incString);
1032 if (cmHasLiteralPrefix(incBase, "moc_")) {
1034 // Remove the moc_ part from the base name
1035 underscore.emplace(std::move(incString));
1038 dot.emplace(std::move(incString));
1040 // Forward content pointer
1041 contentChars += match.end();
1044 auto& Include = FileHandle->ParseData->Moc.Include;
1045 CreateKeys(Include.Underscore, underscore, MocUnderscoreLength);
1046 CreateKeys(Include.Dot, dot, 0);
1049 void cmQtAutoMocUicT::JobParseT::UicIncludes()
1051 if (Content.find("ui_") == std::string::npos) {
1055 std::set<std::string> includes;
1057 const char* contentChars = Content.c_str();
1058 cmsys::RegularExpression const& regExp = UicConst().RegExpInclude;
1059 cmsys::RegularExpressionMatch match;
1060 while (regExp.find(contentChars, match)) {
1061 includes.emplace(match.match(2));
1062 // Forward content pointer
1063 contentChars += match.end();
1066 CreateKeys(FileHandle->ParseData->Uic.Include, includes, UiUnderscoreLength);
1069 void cmQtAutoMocUicT::JobParseHeaderT::Process()
1075 if (FileHandle->Moc) {
1080 if (FileHandle->Uic) {
1085 void cmQtAutoMocUicT::JobParseSourceT::Process()
1091 if (FileHandle->Moc) {
1097 if (FileHandle->Uic) {
1102 std::string cmQtAutoMocUicT::JobEvalCacheT::MessageSearchLocations() const
1106 for (std::string const& path : SearchLocations) {
1108 res += MessagePath(path);
1114 void cmQtAutoMocUicT::JobEvalCacheMocT::Process()
1117 for (auto const& pair : BaseEval().Headers) {
1118 if (!EvalHeader(pair.second)) {
1123 for (auto const& pair : BaseEval().Sources) {
1124 if (!EvalSource(pair.second)) {
1130 bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalHeader(SourceFileHandleT source)
1132 SourceFileT const& sourceFile = *source;
1133 auto const& parseData = sourceFile.ParseData->Moc;
1138 if (!parseData.Macro.empty()) {
1139 // Create a new mapping
1140 MappingHandleT handle = std::make_shared<MappingT>();
1141 handle->SourceFile = std::move(source);
1143 // Absolute build path
1144 if (BaseConst().MultiConfig) {
1145 handle->OutputFile = Gen()->AbsoluteIncludePath(sourceFile.BuildPath);
1147 handle->OutputFile = Gen()->AbsoluteBuildPath(sourceFile.BuildPath);
1150 // Register mapping in headers map
1151 RegisterMapping(handle);
1157 bool cmQtAutoMocUicT::JobEvalCacheMocT::EvalSource(
1158 SourceFileHandleT const& source)
1160 SourceFileT const& sourceFile = *source;
1161 auto const& parseData = sourceFile.ParseData->Moc;
1162 if (!sourceFile.Moc ||
1163 (parseData.Macro.empty() && parseData.Include.Underscore.empty() &&
1164 parseData.Include.Dot.empty())) {
1168 std::string const sourceDirPrefix = SubDirPrefix(sourceFile.FileName);
1169 std::string const sourceBase =
1170 cmSystemTools::GetFilenameWithoutLastExtension(sourceFile.FileName);
1172 // For relaxed mode check if the own "moc_" or ".moc" file is included
1173 bool const relaxedMode = MocConst().RelaxedMode;
1174 bool sourceIncludesMocUnderscore = false;
1175 bool sourceIncludesDotMoc = false;
1176 // Check if the sources own "moc_" or ".moc" file is included
1178 for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
1179 if (incKey.Base == sourceBase) {
1180 sourceIncludesMocUnderscore = true;
1185 for (IncludeKeyT const& incKey : parseData.Include.Dot) {
1186 if (incKey.Base == sourceBase) {
1187 sourceIncludesDotMoc = true;
1192 // Check if this source needs to be moc processed but doesn't.
1193 if (!sourceIncludesDotMoc && !parseData.Macro.empty() &&
1194 !(relaxedMode && sourceIncludesMocUnderscore)) {
1196 cmStrCat(MessagePath(sourceFile.FileName), "\ncontains a ",
1197 Quoted(parseData.Macro), " macro, but does not include ",
1198 MessagePath(sourceBase + ".moc"),
1199 "!\nConsider to\n - add #include \"", sourceBase,
1200 ".moc\"\n - enable SKIP_AUTOMOC for this file"));
1204 // Evaluate "moc_" includes
1205 for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
1206 SourceFileHandleT headerHandle;
1208 std::string const headerBase = cmStrCat(incKey.Dir, incKey.Base);
1209 if (!FindIncludedHeader(headerHandle, sourceDirPrefix, headerBase)) {
1211 cmStrCat(MessagePath(sourceFile.FileName),
1212 "\nincludes the moc file ", MessagePath(incKey.Key),
1213 ",\nbut a header ", MessageHeader(headerBase),
1214 "\ncould not be found "
1215 "in the following directories\n",
1216 MessageSearchLocations()));
1220 // The include might be handled differently in relaxed mode
1221 if (relaxedMode && !sourceIncludesDotMoc && !parseData.Macro.empty() &&
1222 (incKey.Base == sourceBase)) {
1223 // The <BASE>.cpp file includes a Qt macro but does not include the
1224 // <BASE>.moc file. In this case, the moc_<BASE>.cpp should probably
1225 // be generated from <BASE>.cpp instead of <BASE>.h, because otherwise
1226 // it won't build. But warn, since this is not how it is supposed to be
1227 // used. This is for KDE4 compatibility.
1232 cmStrCat(MessagePath(sourceFile.FileName), "\ncontains a ",
1233 Quoted(parseData.Macro), " macro, but does not include ",
1234 MessagePath(sourceBase + ".moc"), ".\nInstead it includes ",
1235 MessagePath(incKey.Key), ".\nRunning moc on the source\n ",
1236 MessagePath(sourceFile.FileName), "!\nBetter include ",
1237 MessagePath(sourceBase + ".moc"),
1238 " for compatibility with regular mode.\n",
1239 "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
1242 if (!RegisterIncluded(incKey.Key, source, source)) {
1248 // Check if header is skipped
1249 if (MocConst().skipped(headerHandle->FileName)) {
1253 if (!RegisterIncluded(incKey.Key, source, std::move(headerHandle))) {
1258 // Evaluate ".moc" includes
1261 for (IncludeKeyT const& incKey : parseData.Include.Dot) {
1262 // Check if this is the sources own .moc file
1263 bool const ownMoc = (incKey.Base == sourceBase);
1264 if (ownMoc && !parseData.Macro.empty()) {
1265 // Create mapping for the regular use case
1266 if (!RegisterIncluded(incKey.Key, source, source)) {
1271 // Try to find a header instead but issue a warning.
1272 // This is for KDE4 compatibility.
1273 SourceFileHandleT headerHandle;
1275 std::string const headerBase = cmStrCat(incKey.Dir, incKey.Base);
1276 if (!FindIncludedHeader(headerHandle, sourceDirPrefix, headerBase)) {
1280 MessagePath(sourceFile.FileName), "\nincludes the moc file ",
1281 MessagePath(incKey.Key),
1282 ",\nwhich seems to be the moc file from a different source "
1283 "file.\nCMAKE_AUTOMOC_RELAXED_MODE:\nAlso a matching header ",
1284 MessageHeader(headerBase),
1285 "\ncould not be found in the following directories\n",
1286 MessageSearchLocations()));
1290 // Check if header is skipped
1291 if (MocConst().skipped(headerHandle->FileName)) {
1295 if (ownMoc && parseData.Macro.empty()) {
1298 cmStrCat(MessagePath(sourceFile.FileName),
1299 "\nincludes the moc file ", MessagePath(incKey.Key),
1300 ", but does not contain a\n", MocConst().MacrosString(),
1301 " macro.\nRunning moc on the header\n ",
1302 MessagePath(headerHandle->FileName), "!\nBetter include ",
1303 MessagePath("moc_" + incKey.Base + ".cpp"),
1304 " for a compatibility with regular mode.\n",
1305 "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
1309 cmStrCat(MessagePath(sourceFile.FileName),
1310 "\nincludes the moc file ", MessagePath(incKey.Key),
1311 " instead of ", MessagePath("moc_" + incKey.Base + ".cpp"),
1312 ".\nRunning moc on the header\n ",
1313 MessagePath(headerHandle->FileName), "!\nBetter include ",
1314 MessagePath("moc_" + incKey.Base + ".cpp"),
1315 " for compatibility with regular mode.\n",
1316 "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n"));
1319 if (!RegisterIncluded(incKey.Key, source, std::move(headerHandle))) {
1325 for (IncludeKeyT const& incKey : parseData.Include.Dot) {
1326 // Check if this is the sources own .moc file
1327 bool const ownMoc = (incKey.Base == sourceBase);
1329 // Don't allow <BASE>.moc include other than own in regular mode
1331 cmStrCat(MessagePath(sourceFile.FileName),
1332 "\nincludes the moc file ", MessagePath(incKey.Key),
1333 ",\nwhich seems to be the moc file from a different "
1334 "source file.\nThis is not supported. Include ",
1335 MessagePath(sourceBase + ".moc"),
1336 " to run moc on this source file."));
1339 // Accept but issue a warning if moc isn't required
1340 if (parseData.Macro.empty()) {
1341 Log().Warning(GenT::MOC,
1342 cmStrCat(MessagePath(sourceFile.FileName),
1343 "\nincludes the moc file ",
1344 MessagePath(incKey.Key),
1345 ", but does not contain a ",
1346 MocConst().MacrosString(), " macro."));
1349 if (!RegisterIncluded(incKey.Key, source, source)) {
1358 bool cmQtAutoMocUicT::JobEvalCacheMocT::FindIncludedHeader(
1359 SourceFileHandleT& headerHandle, cm::string_view includerDir,
1360 cm::string_view includeBase)
1362 // Clear search locations
1363 SearchLocations.clear();
1365 auto findHeader = [this,
1366 &headerHandle](std::string const& basePath) -> bool {
1368 for (std::string const& ext : this->BaseConst().HeaderExtensions) {
1369 std::string const testPath =
1370 this->Gen()->CollapseFullPathTS(cmStrCat(basePath, '.', ext));
1371 cmFileTime fileTime;
1372 if (!fileTime.Load(testPath)) {
1377 // Return a known file if it exists already
1379 auto it = BaseEval().Headers.find(testPath);
1380 if (it != BaseEval().Headers.end()) {
1381 headerHandle = it->second;
1387 // Created and return discovered file entry
1389 SourceFileHandleT& handle = MocEval().HeadersDiscovered[testPath];
1391 handle = std::make_shared<SourceFileT>(testPath);
1392 handle->FileTime = fileTime;
1393 handle->IsHeader = true;
1396 headerHandle = handle;
1402 this->SearchLocations.emplace_back(cmQtAutoGen::ParentDir(basePath));
1407 // Search in vicinity of the source
1408 if (findHeader(cmStrCat(includerDir, includeBase))) {
1411 // Search in include directories
1412 for (std::string const& path : MocConst().IncludePaths) {
1413 if (findHeader(cmStrCat(path, '/', includeBase))) {
1417 // Return without success
1421 bool cmQtAutoMocUicT::JobEvalCacheMocT::RegisterIncluded(
1422 std::string const& includeString, SourceFileHandleT includerFileHandle,
1423 SourceFileHandleT sourceFileHandle) const
1425 // Check if this file is already included
1426 MappingHandleT& handle = MocEval().Includes[includeString];
1428 // Check if the output file would be generated from different source files
1429 if (handle->SourceFile != sourceFileHandle) {
1431 cmStrCat(" ", MessagePath(includerFileHandle->FileName), '\n');
1432 for (auto const& item : handle->IncluderFiles) {
1433 files += cmStrCat(" ", MessagePath(item->FileName), '\n');
1437 cmStrCat("The source files\n", files,
1438 "contain the same include string ",
1439 MessagePath(includeString),
1440 ", but\nthe moc file would be generated from different "
1442 MessagePath(sourceFileHandle->FileName), " and\n ",
1443 MessagePath(handle->SourceFile->FileName),
1445 " - not include the \"moc_<NAME>.cpp\" file\n"
1446 " - add a directory prefix to a \"<NAME>.moc\" include "
1447 "(e.g \"sub/<NAME>.moc\")\n"
1448 " - rename the source file(s)\n"));
1452 // The same mapping already exists. Just add to the includers list.
1453 handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
1457 // Create a new mapping
1458 handle = std::make_shared<MappingT>();
1459 handle->IncludeString = includeString;
1460 handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
1461 handle->SourceFile = std::move(sourceFileHandle);
1462 handle->OutputFile = Gen()->AbsoluteIncludePath(includeString);
1464 // Register mapping in sources/headers map
1465 RegisterMapping(handle);
1469 void cmQtAutoMocUicT::JobEvalCacheMocT::RegisterMapping(
1470 MappingHandleT mappingHandle) const
1472 auto& regMap = mappingHandle->SourceFile->IsHeader
1473 ? MocEval().HeaderMappings
1474 : MocEval().SourceMappings;
1475 // Check if source file already gets mapped
1476 auto& regHandle = regMap[mappingHandle->SourceFile->FileName];
1478 // Yet unknown mapping
1479 regHandle = std::move(mappingHandle);
1481 // Mappings with include string override those without
1482 if (!mappingHandle->IncludeString.empty()) {
1483 regHandle = std::move(mappingHandle);
1488 std::string cmQtAutoMocUicT::JobEvalCacheMocT::MessageHeader(
1489 cm::string_view headerBase) const
1491 return MessagePath(cmStrCat(
1492 headerBase, ".{", cmJoin(this->BaseConst().HeaderExtensions, ","), '}'));
1495 void cmQtAutoMocUicT::JobEvalCacheUicT::Process()
1498 SearchLocations.reserve((UicConst().SearchPaths.size() + 1) * 2);
1501 for (auto const& pair : BaseEval().Headers) {
1502 if (!EvalFile(pair.second)) {
1507 for (auto const& pair : BaseEval().Sources) {
1508 if (!EvalFile(pair.second)) {
1514 bool cmQtAutoMocUicT::JobEvalCacheUicT::EvalFile(
1515 SourceFileHandleT const& sourceFileHandle)
1517 SourceFileT const& sourceFile = *sourceFileHandle;
1518 auto const& Include = sourceFile.ParseData->Uic.Include;
1519 if (!sourceFile.Uic || Include.empty()) {
1523 std::string const sourceDirPrefix = SubDirPrefix(sourceFile.FileName);
1524 for (IncludeKeyT const& incKey : Include) {
1526 UiName = cmStrCat(incKey.Base, ".ui");
1527 if (!FindIncludedUi(sourceDirPrefix, incKey.Dir)) {
1529 cmStrCat(MessagePath(sourceFile.FileName),
1530 "\nincludes the uic file ", MessagePath(incKey.Key),
1531 ",\nbut the user interface file ", MessagePath(UiName),
1532 "\ncould not be found in the following directories\n",
1533 MessageSearchLocations()));
1536 // Check if the file is skipped
1537 if (UicConst().skipped(UiFileHandle->FileName)) {
1541 if (!RegisterMapping(incKey.Key, sourceFileHandle)) {
1549 bool cmQtAutoMocUicT::JobEvalCacheUicT::FindIncludedUi(
1550 cm::string_view sourceDirPrefix, cm::string_view includePrefix)
1552 // Clear locations buffer
1553 SearchLocations.clear();
1555 auto findUi = [this](std::string const& testPath) -> bool {
1556 std::string const fullPath = this->Gen()->CollapseFullPathTS(testPath);
1557 cmFileTime fileTime;
1558 if (!fileTime.Load(fullPath)) {
1559 this->SearchLocations.emplace_back(cmQtAutoGen::ParentDir(fullPath));
1562 // .ui file found in files system!
1563 // Get or create .ui file handle
1564 SourceFileHandleT& handle = this->UicEval().UiFiles[fullPath];
1566 // The file wasn't registered, yet
1567 handle = std::make_shared<SourceFileT>(fullPath);
1568 handle->FileTime = fileTime;
1570 this->UiFileHandle = handle;
1574 // Vicinity of the source
1575 if (findUi(cmStrCat(sourceDirPrefix, UiName))) {
1578 if (!includePrefix.empty()) {
1579 if (findUi(cmStrCat(sourceDirPrefix, includePrefix, UiName))) {
1583 // Additional AUTOUIC search paths
1584 auto const& searchPaths = UicConst().SearchPaths;
1585 if (!searchPaths.empty()) {
1586 for (std::string const& sPath : searchPaths) {
1587 if (findUi(cmStrCat(sPath, '/', UiName))) {
1591 if (!includePrefix.empty()) {
1592 for (std::string const& sPath : searchPaths) {
1593 if (findUi(cmStrCat(sPath, '/', includePrefix, UiName))) {
1603 bool cmQtAutoMocUicT::JobEvalCacheUicT::RegisterMapping(
1604 std::string const& includeString, SourceFileHandleT includerFileHandle)
1606 auto& Includes = Gen()->UicEval().Includes;
1607 auto it = Includes.find(includeString);
1608 if (it != Includes.end()) {
1609 MappingHandleT const& handle = it->second;
1610 if (handle->SourceFile != UiFileHandle) {
1611 // The output file already gets generated - from a different .ui file!
1613 cmStrCat(" ", MessagePath(includerFileHandle->FileName), '\n');
1614 for (auto const& item : handle->IncluderFiles) {
1615 files += cmStrCat(" ", MessagePath(item->FileName), '\n');
1620 "The source files\n", files, "contain the same include string ",
1621 Quoted(includeString),
1622 ", but\nthe uic file would be generated from different "
1623 "user interface files\n ",
1624 MessagePath(UiFileHandle->FileName), " and\n ",
1625 MessagePath(handle->SourceFile->FileName),
1627 " - add a directory prefix to a \"ui_<NAME>.h\" include "
1628 "(e.g \"sub/ui_<NAME>.h\")\n"
1629 " - rename the <NAME>.ui file(s) and adjust the \"ui_<NAME>.h\" "
1633 // Add includer file to existing mapping
1634 handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
1636 // New mapping handle
1637 MappingHandleT handle = std::make_shared<MappingT>();
1638 handle->IncludeString = includeString;
1639 handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
1640 handle->SourceFile = UiFileHandle;
1641 handle->OutputFile = Gen()->AbsoluteIncludePath(includeString);
1643 Includes.emplace(includeString, std::move(handle));
1648 void cmQtAutoMocUicT::JobEvalCacheFinishT::Process()
1650 // Add discovered header parse jobs
1651 Gen()->CreateParseJobs<JobParseHeaderT>(MocEval().HeadersDiscovered);
1653 // Add dependency probing jobs
1655 // Add fence job to ensure all parsing has finished
1656 Gen()->WorkerPool().EmplaceJob<JobFenceT>();
1657 if (MocConst().Enabled) {
1658 Gen()->WorkerPool().EmplaceJob<JobProbeDepsMocT>();
1660 if (UicConst().Enabled) {
1661 Gen()->WorkerPool().EmplaceJob<JobProbeDepsUicT>();
1663 // Add probe finish job
1664 Gen()->WorkerPool().EmplaceJob<JobProbeDepsFinishT>();
1668 void cmQtAutoMocUicT::JobProbeDepsMocT::Process()
1670 // Create moc header jobs
1671 for (auto const& pair : MocEval().HeaderMappings) {
1672 // Register if this mapping is a candidate for mocs_compilation.cpp
1673 bool const compFile = pair.second->IncludeString.empty();
1675 MocEval().CompFiles.emplace_back(pair.second->SourceFile->BuildPath);
1677 if (!Generate(pair.second, compFile)) {
1682 // Create moc source jobs
1683 for (auto const& pair : MocEval().SourceMappings) {
1684 if (!Generate(pair.second, false)) {
1690 bool cmQtAutoMocUicT::JobProbeDepsMocT::Generate(MappingHandleT const& mapping,
1691 bool compFile) const
1693 std::unique_ptr<std::string> reason;
1694 if (Log().Verbose()) {
1695 reason = cm::make_unique<std::string>();
1697 if (Probe(*mapping, reason.get())) {
1698 // Register the parent directory for creation
1699 MocEval().OutputDirs.emplace(cmQtAutoGen::ParentDir(mapping->OutputFile));
1700 // Fetch the cache entry for the source file
1701 std::string const& sourceFile = mapping->SourceFile->FileName;
1702 ParseCacheT::GetOrInsertT cacheEntry =
1703 BaseEval().ParseCache.GetOrInsert(sourceFile);
1705 Gen()->WorkerPool().EmplaceJob<JobCompileMocT>(
1706 mapping, std::move(reason), std::move(cacheEntry.first));
1707 // Check if a moc job for a mocs_compilation.cpp entry was generated
1709 MocEval().CompUpdated = true;
1715 bool cmQtAutoMocUicT::JobProbeDepsMocT::Probe(MappingT const& mapping,
1716 std::string* reason) const
1718 std::string const& sourceFile = mapping.SourceFile->FileName;
1719 std::string const& outputFile = mapping.OutputFile;
1721 // Test if the output file exists
1722 cmFileTime outputFileTime;
1723 if (!outputFileTime.Load(outputFile)) {
1724 if (reason != nullptr) {
1726 cmStrCat("Generating ", MessagePath(outputFile),
1727 ", because it doesn't exist, from ", MessagePath(sourceFile));
1732 // Test if any setting changed
1733 if (MocConst().SettingsChanged) {
1734 if (reason != nullptr) {
1735 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1736 ", because the uic settings changed, from ",
1737 MessagePath(sourceFile));
1742 // Test if the source file is newer
1743 if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
1744 if (reason != nullptr) {
1745 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1746 ", because it's older than its source file, from ",
1747 MessagePath(sourceFile));
1752 // Test if the moc_predefs file is newer
1753 if (!MocConst().PredefsFileAbs.empty()) {
1754 if (outputFileTime.Older(MocEval().PredefsTime)) {
1755 if (reason != nullptr) {
1756 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1757 ", because it's older than ",
1758 MessagePath(MocConst().PredefsFileAbs), ", from ",
1759 MessagePath(sourceFile));
1765 // Test if the moc executable is newer
1766 if (outputFileTime.Older(MocConst().ExecutableTime)) {
1767 if (reason != nullptr) {
1768 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1769 ", because it's older than the moc executable, from ",
1770 MessagePath(sourceFile));
1775 // Test if a dependency file is newer
1777 // Check dependency timestamps
1778 std::string const sourceDir = SubDirPrefix(sourceFile);
1779 for (std::string const& dep : mapping.SourceFile->ParseData->Moc.Depends) {
1780 // Find dependency file
1781 auto const depMatch = FindDependency(sourceDir, dep);
1782 if (depMatch.first.empty()) {
1783 Log().Warning(GenT::MOC,
1784 cmStrCat(MessagePath(sourceFile), " depends on ",
1786 " but the file does not exist."));
1789 // Test if dependency file is older
1790 if (outputFileTime.Older(depMatch.second)) {
1791 if (reason != nullptr) {
1792 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1793 ", because it's older than its dependency file ",
1794 MessagePath(depMatch.first), ", from ",
1795 MessagePath(sourceFile));
1805 std::pair<std::string, cmFileTime>
1806 cmQtAutoMocUicT::JobProbeDepsMocT::FindDependency(
1807 std::string const& sourceDir, std::string const& includeString) const
1809 using ResPair = std::pair<std::string, cmFileTime>;
1810 // moc's dependency file contains absolute paths
1811 if (MocConst().CanOutputDependencies) {
1812 ResPair res{ includeString, {} };
1813 if (res.second.Load(res.first)) {
1818 // Search in vicinity of the source
1820 ResPair res{ sourceDir + includeString, {} };
1821 if (res.second.Load(res.first)) {
1825 // Search in include directories
1826 for (std::string const& includePath : MocConst().IncludePaths) {
1827 ResPair res{ cmStrCat(includePath, '/', includeString), {} };
1828 if (res.second.Load(res.first)) {
1836 void cmQtAutoMocUicT::JobProbeDepsUicT::Process()
1838 for (auto const& pair : Gen()->UicEval().Includes) {
1839 MappingHandleT const& mapping = pair.second;
1840 std::unique_ptr<std::string> reason;
1841 if (Log().Verbose()) {
1842 reason = cm::make_unique<std::string>();
1844 if (!Probe(*mapping, reason.get())) {
1848 // Register the parent directory for creation
1849 UicEval().OutputDirs.emplace(cmQtAutoGen::ParentDir(mapping->OutputFile));
1851 Gen()->WorkerPool().EmplaceJob<JobCompileUicT>(mapping, std::move(reason));
1855 bool cmQtAutoMocUicT::JobProbeDepsUicT::Probe(MappingT const& mapping,
1856 std::string* reason) const
1858 std::string const& sourceFile = mapping.SourceFile->FileName;
1859 std::string const& outputFile = mapping.OutputFile;
1861 // Test if the build file exists
1862 cmFileTime outputFileTime;
1863 if (!outputFileTime.Load(outputFile)) {
1864 if (reason != nullptr) {
1866 cmStrCat("Generating ", MessagePath(outputFile),
1867 ", because it doesn't exist, from ", MessagePath(sourceFile));
1872 // Test if the uic settings changed
1873 if (UicConst().SettingsChanged) {
1874 if (reason != nullptr) {
1875 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1876 ", because the uic settings changed, from ",
1877 MessagePath(sourceFile));
1882 // Test if the source file is newer
1883 if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
1884 if (reason != nullptr) {
1885 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1886 " because it's older than the source file ",
1887 MessagePath(sourceFile));
1892 // Test if the uic executable is newer
1893 if (outputFileTime.Older(UicConst().ExecutableTime)) {
1894 if (reason != nullptr) {
1895 *reason = cmStrCat("Generating ", MessagePath(outputFile),
1896 ", because it's older than the uic executable, from ",
1897 MessagePath(sourceFile));
1905 void cmQtAutoMocUicT::JobProbeDepsFinishT::Process()
1907 // Create output directories
1909 using StringSet = std::unordered_set<std::string>;
1910 auto createDirs = [this](GenT genType, StringSet const& dirSet) {
1911 for (std::string const& dirName : dirSet) {
1912 if (!cmSystemTools::MakeDirectory(dirName)) {
1915 cmStrCat("Creating directory ", MessagePath(dirName), " failed."));
1920 if (MocConst().Enabled && UicConst().Enabled) {
1921 StringSet outputDirs = MocEval().OutputDirs;
1922 outputDirs.insert(UicEval().OutputDirs.begin(),
1923 UicEval().OutputDirs.end());
1924 createDirs(GenT::GEN, outputDirs);
1925 } else if (MocConst().Enabled) {
1926 createDirs(GenT::MOC, MocEval().OutputDirs);
1927 } else if (UicConst().Enabled) {
1928 createDirs(GenT::UIC, UicEval().OutputDirs);
1932 if (MocConst().Enabled) {
1933 // Add mocs compilations job
1934 Gen()->WorkerPool().EmplaceJob<JobMocsCompilationT>();
1937 if (!BaseConst().DepFile.empty()) {
1938 // Add job to merge dep files
1939 Gen()->WorkerPool().EmplaceJob<JobDepFilesMergeT>();
1943 Gen()->WorkerPool().EmplaceJob<JobFinishT>();
1946 void cmQtAutoMocUicT::JobCompileMocT::Process()
1948 std::string const& sourceFile = Mapping->SourceFile->FileName;
1949 std::string const& outputFile = Mapping->OutputFile;
1951 // Compose moc command
1952 std::vector<std::string> cmd;
1954 // Reserve large enough
1955 cmd.reserve(MocConst().OptionsDefinitions.size() +
1956 MocConst().OptionsIncludes.size() +
1957 MocConst().OptionsExtra.size() + 16);
1958 cmd.push_back(MocConst().Executable);
1960 cm::append(cmd, MocConst().OptionsDefinitions);
1962 cm::append(cmd, MocConst().OptionsIncludes);
1963 // Add predefs include
1964 if (!MocConst().PredefsFileAbs.empty()) {
1965 cmd.emplace_back("--include");
1966 cmd.push_back(MocConst().PredefsFileAbs);
1968 // Add path prefix on demand
1969 if (MocConst().PathPrefix && Mapping->SourceFile->IsHeader) {
1970 for (std::string const& dir : MocConst().IncludePaths) {
1971 cm::string_view prefix = sourceFile;
1972 if (cmHasPrefix(prefix, dir)) {
1973 prefix.remove_prefix(dir.size());
1974 if (cmHasPrefix(prefix, '/')) {
1975 prefix.remove_prefix(1);
1976 auto slashPos = prefix.rfind('/');
1977 if (slashPos != cm::string_view::npos) {
1978 cmd.emplace_back("-p");
1979 cmd.emplace_back(prefix.substr(0, slashPos));
1981 cmd.emplace_back("-p");
1982 cmd.emplace_back("./");
1989 // Add extra options
1990 cm::append(cmd, MocConst().OptionsExtra);
1991 if (MocConst().CanOutputDependencies) {
1992 cmd.emplace_back("--output-dep-file");
1995 cmd.emplace_back("-o");
1996 cmd.push_back(outputFile);
1998 cmd.push_back(sourceFile);
2001 // Execute moc command
2002 cmWorkerPool::ProcessResultT result;
2003 if (!RunProcess(GenT::MOC, result, cmd, Reason.get())) {
2004 // Moc command failed
2005 std::string includers;
2006 if (!Mapping->IncluderFiles.empty()) {
2007 includers = "included by\n";
2008 for (auto const& item : Mapping->IncluderFiles) {
2009 includers += cmStrCat(" ", MessagePath(item->FileName), '\n');
2012 LogCommandError(GenT::MOC,
2013 cmStrCat("The moc process failed to compile\n ",
2014 MessagePath(sourceFile), "\ninto\n ",
2015 MessagePath(outputFile), '\n', includers,
2016 result.ErrorMessage),
2017 cmd, result.StdOut);
2021 // Moc command success. Print moc output.
2022 if (!result.StdOut.empty()) {
2023 Log().Info(GenT::MOC, result.StdOut);
2026 // Extract dependencies from the dep file moc generated for us
2027 if (MocConst().CanOutputDependencies) {
2028 const std::string depfile = outputFile + ".d";
2029 if (Log().Verbose()) {
2030 Log().Info(GenT::MOC,
2031 "Reading dependencies from " + MessagePath(depfile));
2033 if (!cmSystemTools::FileExists(depfile)) {
2034 Log().Warning(GenT::MOC,
2035 "Dependency file " + MessagePath(depfile) +
2036 " does not exist.");
2039 CacheEntry->Moc.Depends = dependenciesFromDepFile(depfile.c_str());
2043 void cmQtAutoMocUicT::JobCompileUicT::Process()
2045 std::string const& sourceFile = Mapping->SourceFile->FileName;
2046 std::string const& outputFile = Mapping->OutputFile;
2048 // Compose uic command
2049 std::vector<std::string> cmd;
2050 cmd.push_back(UicConst().Executable);
2052 std::vector<std::string> allOpts = UicConst().Options;
2053 auto optionIt = UicConst().UiFiles.find(sourceFile);
2054 if (optionIt != UicConst().UiFiles.end()) {
2055 UicMergeOptions(allOpts, optionIt->second.Options,
2056 (BaseConst().QtVersion.Major == 5));
2058 cm::append(cmd, allOpts);
2060 cmd.emplace_back("-o");
2061 cmd.emplace_back(outputFile);
2062 cmd.emplace_back(sourceFile);
2064 cmWorkerPool::ProcessResultT result;
2065 if (RunProcess(GenT::UIC, result, cmd, Reason.get())) {
2066 // Uic command success
2068 if (!result.StdOut.empty()) {
2069 Log().Info(GenT::UIC, result.StdOut);
2072 // Uic command failed
2073 std::string includers;
2074 for (auto const& item : Mapping->IncluderFiles) {
2075 includers += cmStrCat(" ", MessagePath(item->FileName), '\n');
2077 LogCommandError(GenT::UIC,
2078 cmStrCat("The uic process failed to compile\n ",
2079 MessagePath(sourceFile), "\ninto\n ",
2080 MessagePath(outputFile), "\nincluded by\n",
2081 includers, result.ErrorMessage),
2082 cmd, result.StdOut);
2086 void cmQtAutoMocUicT::JobMocsCompilationT::Process()
2088 // Compose mocs compilation file content
2089 std::string content =
2090 "// This file is autogenerated. Changes will be overwritten.\n";
2092 if (MocEval().CompFiles.empty()) {
2093 // Placeholder content
2094 content += "// No files found that require moc or the moc files are "
2096 "enum some_compilers { need_more_than_nothing };\n";
2099 const bool mc = BaseConst().MultiConfig;
2100 cm::string_view const wrapFront = mc ? "#include <" : "#include \"";
2101 cm::string_view const wrapBack = mc ? ">\n" : "\"\n";
2102 content += cmWrap(wrapFront, MocEval().CompFiles, wrapBack, "");
2105 std::string const& compAbs = MocConst().CompFileAbs;
2106 if (cmQtAutoGenerator::FileDiffers(compAbs, content)) {
2107 // Actually write mocs compilation file
2108 if (Log().Verbose()) {
2109 Log().Info(GenT::MOC,
2110 "Generating MOC compilation " + MessagePath(compAbs));
2112 if (!FileWrite(compAbs, content)) {
2114 cmStrCat("Writing MOC compilation ", MessagePath(compAbs),
2117 } else if (MocEval().CompUpdated) {
2118 // Only touch mocs compilation file
2119 if (Log().Verbose()) {
2120 Log().Info(GenT::MOC,
2121 "Touching MOC compilation " + MessagePath(compAbs));
2123 if (!cmSystemTools::Touch(compAbs, false)) {
2125 cmStrCat("Touching MOC compilation ", MessagePath(compAbs),
2132 * Escapes paths for Ninja depfiles.
2133 * This is a re-implementation of what moc does when writing depfiles.
2135 std::string escapeDependencyPath(cm::string_view path)
2137 std::string escapedPath;
2138 escapedPath.reserve(path.size());
2139 const size_t s = path.size();
2140 int backslashCount = 0;
2141 for (size_t i = 0; i < s; ++i) {
2142 if (path[i] == '\\') {
2145 if (path[i] == '$') {
2146 escapedPath.push_back('$');
2147 } else if (path[i] == '#') {
2148 escapedPath.push_back('\\');
2149 } else if (path[i] == ' ') {
2150 // Double the amount of written backslashes,
2151 // and add one more to escape the space.
2152 while (backslashCount-- >= 0) {
2153 escapedPath.push_back('\\');
2158 escapedPath.push_back(path[i]);
2163 void cmQtAutoMocUicT::JobDepFilesMergeT::Process()
2165 if (Log().Verbose()) {
2166 Log().Info(GenT::MOC, "Merging MOC dependencies");
2168 auto processDepFile =
2169 [](const std::string& mocOutputFile) -> std::vector<std::string> {
2170 std::string f = mocOutputFile + ".d";
2171 if (!cmSystemTools::FileExists(f)) {
2174 return dependenciesFromDepFile(f.c_str());
2177 std::vector<std::string> dependencies;
2178 ParseCacheT& parseCache = BaseEval().ParseCache;
2179 auto processMappingEntry = [&](const MappingMapT::value_type& m) {
2180 auto cacheEntry = parseCache.GetOrInsert(m.first);
2181 if (cacheEntry.first->Moc.Depends.empty()) {
2182 cacheEntry.first->Moc.Depends = processDepFile(m.second->OutputFile);
2184 dependencies.insert(dependencies.end(),
2185 cacheEntry.first->Moc.Depends.begin(),
2186 cacheEntry.first->Moc.Depends.end());
2189 std::for_each(MocEval().HeaderMappings.begin(),
2190 MocEval().HeaderMappings.end(), processMappingEntry);
2191 std::for_each(MocEval().SourceMappings.begin(),
2192 MocEval().SourceMappings.end(), processMappingEntry);
2194 // Remove duplicates to make the depfile smaller
2195 std::sort(dependencies.begin(), dependencies.end());
2196 dependencies.erase(std::unique(dependencies.begin(), dependencies.end()),
2197 dependencies.end());
2200 for (const auto& uif : UicEval().UiFiles) {
2201 dependencies.push_back(uif.first);
2205 cmsys::ofstream ofs;
2206 ofs.open(BaseConst().DepFile.c_str(),
2207 (std::ios::out | std::ios::binary | std::ios::trunc));
2210 cmStrCat("Cannot open ", MessagePath(BaseConst().DepFile),
2214 ofs << BaseConst().DepFileRuleName << ": \\\n";
2215 for (const std::string& file : dependencies) {
2216 ofs << '\t' << escapeDependencyPath(file) << " \\\n";
2219 cmStrCat("Writing depfile", MessagePath(BaseConst().DepFile),
2225 // Add the CMake executable to re-new cache data if necessary.
2226 // Also, this is the last entry, so don't add a backslash.
2227 ofs << '\t' << escapeDependencyPath(BaseConst().CMakeExecutable) << '\n';
2230 void cmQtAutoMocUicT::JobFinishT::Process()
2232 Gen()->AbortSuccess();
2235 cmQtAutoMocUicT::cmQtAutoMocUicT()
2236 : cmQtAutoGenerator(GenT::GEN)
2239 cmQtAutoMocUicT::~cmQtAutoMocUicT() = default;
2241 bool cmQtAutoMocUicT::InitFromInfo(InfoT const& info)
2243 // -- Required settings
2244 if (!info.GetBool("MULTI_CONFIG", BaseConst_.MultiConfig, true) ||
2245 !info.GetUInt("QT_VERSION_MAJOR", BaseConst_.QtVersion.Major, true) ||
2246 !info.GetUInt("QT_VERSION_MINOR", BaseConst_.QtVersion.Minor, true) ||
2247 !info.GetUInt("PARALLEL", BaseConst_.ThreadCount, false) ||
2248 !info.GetString("BUILD_DIR", BaseConst_.AutogenBuildDir, true) ||
2249 !info.GetStringConfig("INCLUDE_DIR", BaseConst_.AutogenIncludeDir,
2251 !info.GetString("CMAKE_EXECUTABLE", BaseConst_.CMakeExecutable, true) ||
2252 !info.GetStringConfig("PARSE_CACHE_FILE", BaseConst_.ParseCacheFile,
2254 !info.GetString("DEP_FILE", BaseConst_.DepFile, false) ||
2255 !info.GetString("DEP_FILE_RULE_NAME", BaseConst_.DepFileRuleName,
2257 !info.GetStringConfig("SETTINGS_FILE", SettingsFile_, true) ||
2258 !info.GetArray("HEADER_EXTENSIONS", BaseConst_.HeaderExtensions, true) ||
2259 !info.GetString("QT_MOC_EXECUTABLE", MocConst_.Executable, false) ||
2260 !info.GetString("QT_UIC_EXECUTABLE", UicConst_.Executable, false)) {
2265 if (!BaseConst_.CMakeExecutableTime.Load(BaseConst_.CMakeExecutable)) {
2266 return info.LogError(cmStrCat("The CMake executable ",
2267 MessagePath(BaseConst_.CMakeExecutable),
2268 " does not exist."));
2271 // -- Evaluate values
2272 BaseConst_.ThreadCount = std::min(BaseConst_.ThreadCount, ParallelMax);
2273 WorkerPool_.SetThreadCount(BaseConst_.ThreadCount);
2276 if (!MocConst_.Executable.empty()) {
2277 // -- Moc is enabled
2278 MocConst_.Enabled = true;
2280 // -- Temporary buffers
2283 std::vector<std::string> MacroNames;
2284 std::vector<std::string> DependFilters;
2287 // -- Required settings
2288 if (!info.GetBool("MOC_RELAXED_MODE", MocConst_.RelaxedMode, false) ||
2289 !info.GetBool("MOC_PATH_PREFIX", MocConst_.PathPrefix, true) ||
2290 !info.GetArray("MOC_SKIP", MocConst_.SkipList, false) ||
2291 !info.GetArrayConfig("MOC_DEFINITIONS", MocConst_.Definitions,
2293 !info.GetArrayConfig("MOC_INCLUDES", MocConst_.IncludePaths, false) ||
2294 !info.GetArray("MOC_OPTIONS", MocConst_.OptionsExtra, false) ||
2295 !info.GetStringConfig("MOC_COMPILATION_FILE", MocConst_.CompFileAbs,
2297 !info.GetArray("MOC_PREDEFS_CMD", MocConst_.PredefsCmd, false) ||
2298 !info.GetStringConfig("MOC_PREDEFS_FILE", MocConst_.PredefsFileAbs,
2299 !MocConst_.PredefsCmd.empty()) ||
2300 !info.GetArray("MOC_MACRO_NAMES", tmp.MacroNames, true) ||
2301 !info.GetArray("MOC_DEPEND_FILTERS", tmp.DependFilters, false)) {
2305 // -- Evaluate settings
2306 for (std::string const& item : tmp.MacroNames) {
2307 MocConst_.MacroFilters.emplace_back(
2308 item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
2310 // Can moc output dependencies or do we need to setup dependency filters?
2311 if (BaseConst_.QtVersion >= IntegerVersion(5, 15)) {
2312 MocConst_.CanOutputDependencies = true;
2314 Json::Value const& val = info.GetValue("MOC_DEPEND_FILTERS");
2315 if (!val.isArray()) {
2316 return info.LogError("MOC_DEPEND_FILTERS JSON value is not an array.");
2318 Json::ArrayIndex const arraySize = val.size();
2319 for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
2320 // Test entry closure
2321 auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
2324 cmStrCat("MOC_DEPEND_FILTERS filter ", ii, ": ", msg));
2329 Json::Value const& pairVal = val[ii];
2331 if (testEntry(pairVal.isArray(), "JSON value is not an array.") ||
2332 testEntry(pairVal.size() == 2, "JSON array size invalid.")) {
2336 Json::Value const& keyVal = pairVal[0u];
2337 Json::Value const& expVal = pairVal[1u];
2338 if (testEntry(keyVal.isString(),
2339 "JSON value for keyword is not a string.") ||
2340 testEntry(expVal.isString(),
2341 "JSON value for regular expression is not a string.")) {
2345 std::string const key = keyVal.asString();
2346 std::string const exp = expVal.asString();
2347 if (testEntry(!key.empty(), "Keyword is empty.") ||
2348 testEntry(!exp.empty(), "Regular expression is empty.")) {
2352 this->MocConst_.DependFilters.emplace_back(key, exp);
2354 this->MocConst_.DependFilters.back().Exp.is_valid(),
2355 cmStrCat("Regular expression compilation failed.\nKeyword: ",
2356 Quoted(key), "\nExpression: ", Quoted(exp)))) {
2361 // Check if moc executable exists (by reading the file time)
2362 if (!MocConst_.ExecutableTime.Load(MocConst_.Executable)) {
2363 return info.LogError(cmStrCat("The moc executable ",
2364 MessagePath(MocConst_.Executable),
2365 " does not exist."));
2370 if (!UicConst_.Executable.empty()) {
2372 UicConst_.Enabled = true;
2374 // -- Required settings
2375 if (!info.GetArray("UIC_SKIP", UicConst_.SkipList, false) ||
2376 !info.GetArray("UIC_SEARCH_PATHS", UicConst_.SearchPaths, false) ||
2377 !info.GetArrayConfig("UIC_OPTIONS", UicConst_.Options, false)) {
2382 Json::Value const& val = info.GetValue("UIC_UI_FILES");
2383 if (!val.isArray()) {
2384 return info.LogError("UIC_UI_FILES JSON value is not an array.");
2386 Json::ArrayIndex const arraySize = val.size();
2387 for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
2388 // Test entry closure
2389 auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
2391 info.LogError(cmStrCat("UIC_UI_FILES entry ", ii, ": ", msg));
2396 Json::Value const& entry = val[ii];
2397 if (testEntry(entry.isArray(), "JSON value is not an array.") ||
2398 testEntry(entry.size() == 2, "JSON array size invalid.")) {
2402 Json::Value const& entryName = entry[0u];
2403 Json::Value const& entryOptions = entry[1u];
2404 if (testEntry(entryName.isString(),
2405 "JSON value for name is not a string.") ||
2406 testEntry(entryOptions.isArray(),
2407 "JSON value for options is not an array.")) {
2411 auto& uiFile = UicConst_.UiFiles[entryName.asString()];
2412 InfoT::GetJsonArray(uiFile.Options, entryOptions);
2416 // -- Evaluate settings
2417 // Check if uic executable exists (by reading the file time)
2418 if (!UicConst_.ExecutableTime.Load(UicConst_.Executable)) {
2419 return info.LogError(cmStrCat("The uic executable ",
2420 MessagePath(UicConst_.Executable),
2421 " does not exist."));
2427 Json::Value const& val = info.GetValue("HEADERS");
2428 if (!val.isArray()) {
2429 return info.LogError("HEADERS JSON value is not an array.");
2431 Json::ArrayIndex const arraySize = val.size();
2432 for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
2433 // Test entry closure
2434 auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
2436 info.LogError(cmStrCat("HEADERS entry ", ii, ": ", msg));
2441 Json::Value const& entry = val[ii];
2442 if (testEntry(entry.isArray(), "JSON value is not an array.") ||
2443 testEntry(entry.size() == 3, "JSON array size invalid.")) {
2447 Json::Value const& entryName = entry[0u];
2448 Json::Value const& entryFlags = entry[1u];
2449 Json::Value const& entryBuild = entry[2u];
2450 if (testEntry(entryName.isString(),
2451 "JSON value for name is not a string.") ||
2452 testEntry(entryFlags.isString(),
2453 "JSON value for flags is not a string.") ||
2454 testEntry(entryBuild.isString(),
2455 "JSON value for build path is not a string.")) {
2459 std::string name = entryName.asString();
2460 std::string flags = entryFlags.asString();
2461 std::string build = entryBuild.asString();
2462 if (testEntry(flags.size() == 2, "Invalid flags string size")) {
2466 cmFileTime fileTime;
2467 if (!fileTime.Load(name)) {
2468 return info.LogError(cmStrCat(
2469 "The header file ", this->MessagePath(name), " does not exist."));
2472 SourceFileHandleT sourceHandle = std::make_shared<SourceFileT>(name);
2473 sourceHandle->FileTime = fileTime;
2474 sourceHandle->IsHeader = true;
2475 sourceHandle->Moc = (flags[0] == 'M');
2476 sourceHandle->Uic = (flags[1] == 'U');
2477 if (sourceHandle->Moc && MocConst().Enabled) {
2478 if (build.empty()) {
2479 return info.LogError(
2480 cmStrCat("Header file ", ii, " build path is empty"));
2482 sourceHandle->BuildPath = std::move(build);
2484 BaseEval().Headers.emplace(std::move(name), std::move(sourceHandle));
2490 Json::Value const& val = info.GetValue("SOURCES");
2491 if (!val.isArray()) {
2492 return info.LogError("SOURCES JSON value is not an array.");
2494 Json::ArrayIndex const arraySize = val.size();
2495 for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
2496 // Test entry closure
2497 auto testEntry = [&info, ii](bool test, cm::string_view msg) -> bool {
2499 info.LogError(cmStrCat("SOURCES entry ", ii, ": ", msg));
2504 Json::Value const& entry = val[ii];
2505 if (testEntry(entry.isArray(), "JSON value is not an array.") ||
2506 testEntry(entry.size() == 2, "JSON array size invalid.")) {
2510 Json::Value const& entryName = entry[0u];
2511 Json::Value const& entryFlags = entry[1u];
2512 if (testEntry(entryName.isString(),
2513 "JSON value for name is not a string.") ||
2514 testEntry(entryFlags.isString(),
2515 "JSON value for flags is not a string.")) {
2519 std::string name = entryName.asString();
2520 std::string flags = entryFlags.asString();
2521 if (testEntry(flags.size() == 2, "Invalid flags string size")) {
2525 cmFileTime fileTime;
2526 if (!fileTime.Load(name)) {
2527 return info.LogError(cmStrCat(
2528 "The source file ", this->MessagePath(name), " does not exist."));
2531 SourceFileHandleT sourceHandle = std::make_shared<SourceFileT>(name);
2532 sourceHandle->FileTime = fileTime;
2533 sourceHandle->IsHeader = false;
2534 sourceHandle->Moc = (flags[0] == 'M');
2535 sourceHandle->Uic = (flags[1] == 'U');
2536 BaseEval().Sources.emplace(std::move(name), std::move(sourceHandle));
2540 // -- Init derived information
2542 if (MocConst().Enabled) {
2543 // Compose moc includes list
2545 // Compute framework paths
2546 std::set<std::string> frameworkPaths;
2547 for (std::string const& path : MocConst().IncludePaths) {
2548 // Extract framework path
2549 if (cmHasLiteralSuffix(path, ".framework/Headers")) {
2550 // Go up twice to get to the framework root
2551 std::vector<std::string> pathComponents;
2552 cmSystemTools::SplitPath(path, pathComponents);
2553 frameworkPaths.emplace(cmSystemTools::JoinPath(
2554 pathComponents.begin(), pathComponents.end() - 2));
2558 MocConst_.OptionsIncludes.reserve(MocConst().IncludePaths.size() +
2559 frameworkPaths.size() * 2);
2561 for (std::string const& path : MocConst().IncludePaths) {
2562 MocConst_.OptionsIncludes.emplace_back("-I" + path);
2564 // Append framework includes
2565 for (std::string const& path : frameworkPaths) {
2566 MocConst_.OptionsIncludes.emplace_back("-F");
2567 MocConst_.OptionsIncludes.push_back(path);
2571 // Compose moc definitions list
2573 MocConst_.OptionsDefinitions.reserve(MocConst().Definitions.size());
2574 for (std::string const& def : MocConst().Definitions) {
2575 MocConst_.OptionsDefinitions.emplace_back("-D" + def);
2583 template <class JOBTYPE>
2584 void cmQtAutoMocUicT::CreateParseJobs(SourceFileMapT const& sourceMap)
2586 cmFileTime const parseCacheTime = BaseEval().ParseCacheTime;
2587 ParseCacheT& parseCache = BaseEval().ParseCache;
2588 for (auto& src : sourceMap) {
2589 // Get or create the file parse data reference
2590 ParseCacheT::GetOrInsertT cacheEntry = parseCache.GetOrInsert(src.first);
2591 src.second->ParseData = std::move(cacheEntry.first);
2592 // Create a parse job if the cache file was missing or is older
2593 if (cacheEntry.second || src.second->FileTime.Newer(parseCacheTime)) {
2594 BaseEval().ParseCacheChanged = true;
2595 WorkerPool().EmplaceJob<JOBTYPE>(src.second);
2600 /** Concurrently callable implementation of cmSystemTools::CollapseFullPath */
2601 std::string cmQtAutoMocUicT::CollapseFullPathTS(std::string const& path) const
2603 std::lock_guard<std::mutex> guard(CMakeLibMutex_);
2604 return cmSystemTools::CollapseFullPath(path, ProjectDirs().CurrentSource);
2607 void cmQtAutoMocUicT::InitJobs()
2609 // Add moc_predefs.h job
2610 if (MocConst().Enabled && !MocConst().PredefsCmd.empty()) {
2611 WorkerPool().EmplaceJob<JobMocPredefsT>();
2614 // Add header parse jobs
2615 CreateParseJobs<JobParseHeaderT>(BaseEval().Headers);
2616 // Add source parse jobs
2617 CreateParseJobs<JobParseSourceT>(BaseEval().Sources);
2619 // Add parse cache evaluations jobs
2621 // Add a fence job to ensure all parsing has finished
2622 WorkerPool().EmplaceJob<JobFenceT>();
2623 if (MocConst().Enabled) {
2624 WorkerPool().EmplaceJob<JobEvalCacheMocT>();
2626 if (UicConst().Enabled) {
2627 WorkerPool().EmplaceJob<JobEvalCacheUicT>();
2630 WorkerPool().EmplaceJob<JobEvalCacheFinishT>();
2634 bool cmQtAutoMocUicT::Process()
2638 if (!CreateDirectories()) {
2642 if (!WorkerPool_.Process(this)) {
2648 if (!ParseCacheWrite()) {
2651 if (!SettingsFileWrite()) {
2657 void cmQtAutoMocUicT::SettingsFileRead()
2659 // Compose current settings strings
2661 cmCryptoHash cryptoHash(cmCryptoHash::AlgoSHA256);
2662 auto cha = [&cryptoHash](cm::string_view value) {
2663 cryptoHash.Append(value);
2664 cryptoHash.Append(";");
2667 if (MocConst_.Enabled) {
2668 cryptoHash.Initialize();
2669 cha(MocConst().Executable);
2670 for (auto const& item : MocConst().OptionsDefinitions) {
2673 for (auto const& item : MocConst().OptionsIncludes) {
2676 for (auto const& item : MocConst().OptionsExtra) {
2679 for (auto const& item : MocConst().PredefsCmd) {
2682 for (auto const& filter : MocConst().DependFilters) {
2685 for (auto const& filter : MocConst().MacroFilters) {
2688 SettingsStringMoc_ = cryptoHash.FinalizeHex();
2691 if (UicConst().Enabled) {
2692 cryptoHash.Initialize();
2693 cha(UicConst().Executable);
2694 std::for_each(UicConst().Options.begin(), UicConst().Options.end(), cha);
2695 for (const auto& item : UicConst().UiFiles) {
2697 auto const& opts = item.second.Options;
2698 std::for_each(opts.begin(), opts.end(), cha);
2700 SettingsStringUic_ = cryptoHash.FinalizeHex();
2704 // Read old settings and compare
2706 std::string content;
2707 if (cmQtAutoGenerator::FileRead(content, SettingsFile_)) {
2708 if (MocConst().Enabled) {
2709 if (SettingsStringMoc_ != SettingsFind(content, "moc")) {
2710 MocConst_.SettingsChanged = true;
2713 if (UicConst().Enabled) {
2714 if (SettingsStringUic_ != SettingsFind(content, "uic")) {
2715 UicConst_.SettingsChanged = true;
2718 // In case any setting changed remove the old settings file.
2719 // This triggers a full rebuild on the next run if the current
2720 // build is aborted before writing the current settings in the end.
2721 if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
2722 cmSystemTools::RemoveFile(SettingsFile_);
2725 // Settings file read failed
2726 if (MocConst().Enabled) {
2727 MocConst_.SettingsChanged = true;
2729 if (UicConst().Enabled) {
2730 UicConst_.SettingsChanged = true;
2736 bool cmQtAutoMocUicT::SettingsFileWrite()
2738 // Only write if any setting changed
2739 if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
2740 if (Log().Verbose()) {
2743 cmStrCat("Writing the settings file ", MessagePath(SettingsFile_)));
2745 // Compose settings file content
2746 std::string content;
2748 auto SettingAppend = [&content](cm::string_view key,
2749 cm::string_view value) {
2750 if (!value.empty()) {
2751 content += cmStrCat(key, ':', value, '\n');
2754 SettingAppend("moc", SettingsStringMoc_);
2755 SettingAppend("uic", SettingsStringUic_);
2757 // Write settings file
2759 if (!cmQtAutoGenerator::FileWrite(SettingsFile_, content, &error)) {
2760 Log().Error(GenT::GEN,
2761 cmStrCat("Writing the settings file ",
2762 MessagePath(SettingsFile_), " failed.\n", error));
2763 // Remove old settings file to trigger a full rebuild on the next run
2764 cmSystemTools::RemoveFile(SettingsFile_);
2771 void cmQtAutoMocUicT::ParseCacheRead()
2773 cm::string_view reason;
2774 // Don't read the cache if it is invalid
2775 if (!BaseEval().ParseCacheTime.Load(BaseConst().ParseCacheFile)) {
2776 reason = "Refreshing parse cache because it doesn't exist.";
2777 } else if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
2778 reason = "Refreshing parse cache because the settings changed.";
2779 } else if (BaseEval().ParseCacheTime.Older(
2780 BaseConst().CMakeExecutableTime)) {
2782 "Refreshing parse cache because it is older than the CMake executable.";
2785 if (!reason.empty()) {
2786 // Don't read but refresh the complete parse cache
2787 if (Log().Verbose()) {
2788 Log().Info(GenT::GEN, reason);
2790 BaseEval().ParseCacheChanged = true;
2793 BaseEval().ParseCache.ReadFromFile(BaseConst().ParseCacheFile);
2797 bool cmQtAutoMocUicT::ParseCacheWrite()
2799 if (BaseEval().ParseCacheChanged) {
2800 if (Log().Verbose()) {
2801 Log().Info(GenT::GEN,
2802 cmStrCat("Writing the parse cache file ",
2803 MessagePath(BaseConst().ParseCacheFile)));
2805 if (!BaseEval().ParseCache.WriteToFile(BaseConst().ParseCacheFile)) {
2806 Log().Error(GenT::GEN,
2807 cmStrCat("Writing the parse cache file ",
2808 MessagePath(BaseConst().ParseCacheFile),
2816 bool cmQtAutoMocUicT::CreateDirectories()
2818 // Create AUTOGEN include directory
2819 if (!cmSystemTools::MakeDirectory(BaseConst().AutogenIncludeDir)) {
2820 Log().Error(GenT::GEN,
2821 cmStrCat("Creating the AUTOGEN include directory ",
2822 MessagePath(BaseConst().AutogenIncludeDir),
2829 std::vector<std::string> cmQtAutoMocUicT::dependenciesFromDepFile(
2830 const char* filePath)
2832 cmGccDepfileContent content = cmReadGccDepfile(filePath);
2833 if (content.empty()) {
2837 // Moc outputs a depfile with exactly one rule.
2838 // Discard the rule and return the dependencies.
2839 return content.front().paths;
2842 void cmQtAutoMocUicT::Abort(bool error)
2845 JobError_.store(true);
2847 WorkerPool_.Abort();
2850 std::string cmQtAutoMocUicT::AbsoluteBuildPath(
2851 cm::string_view relativePath) const
2853 return cmStrCat(BaseConst().AutogenBuildDir, '/', relativePath);
2856 std::string cmQtAutoMocUicT::AbsoluteIncludePath(
2857 cm::string_view relativePath) const
2859 return cmStrCat(BaseConst().AutogenIncludeDir, '/', relativePath);
2862 } // End of unnamed namespace
2864 bool cmQtAutoMocUic(cm::string_view infoFile, cm::string_view config)
2866 return cmQtAutoMocUicT().Run(infoFile, config);