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 "cmQtAutoGenGlobalInitializer.h"
4 
5 #include <set>
6 #include <utility>
7 
8 #include <cm/memory>
9 
10 #include "cmCustomCommandLines.h"
11 #include "cmDuration.h"
12 #include "cmGeneratorTarget.h"
13 #include "cmLocalGenerator.h"
14 #include "cmMakefile.h"
15 #include "cmMessageType.h"
16 #include "cmPolicies.h"
17 #include "cmProcessOutput.h"
18 #include "cmQtAutoGen.h"
19 #include "cmQtAutoGenInitializer.h"
20 #include "cmState.h"
21 #include "cmStateTypes.h"
22 #include "cmStringAlgorithms.h"
23 #include "cmSystemTools.h"
24 #include "cmTarget.h"
25 #include "cmValue.h"
26 
Keywords()27 cmQtAutoGenGlobalInitializer::Keywords::Keywords()
28   : AUTOMOC("AUTOMOC")
29   , AUTOUIC("AUTOUIC")
30   , AUTORCC("AUTORCC")
31   , AUTOMOC_EXECUTABLE("AUTOMOC_EXECUTABLE")
32   , AUTOUIC_EXECUTABLE("AUTOUIC_EXECUTABLE")
33   , AUTORCC_EXECUTABLE("AUTORCC_EXECUTABLE")
34   , SKIP_AUTOGEN("SKIP_AUTOGEN")
35   , SKIP_AUTOMOC("SKIP_AUTOMOC")
36   , SKIP_AUTOUIC("SKIP_AUTOUIC")
37   , SKIP_AUTORCC("SKIP_AUTORCC")
38   , AUTOUIC_OPTIONS("AUTOUIC_OPTIONS")
39   , AUTORCC_OPTIONS("AUTORCC_OPTIONS")
40   , qrc("qrc")
41   , ui("ui")
42 {
43 }
44 
cmQtAutoGenGlobalInitializer(std::vector<std::unique_ptr<cmLocalGenerator>> const & localGenerators)45 cmQtAutoGenGlobalInitializer::cmQtAutoGenGlobalInitializer(
46   std::vector<std::unique_ptr<cmLocalGenerator>> const& localGenerators)
47 {
48   for (const auto& localGen : localGenerators) {
49     // Detect global autogen and autorcc target names
50     bool globalAutoGenTarget = false;
51     bool globalAutoRccTarget = false;
52     {
53       cmMakefile* makefile = localGen->GetMakefile();
54       // Detect global autogen target name
55       if (makefile->IsOn("CMAKE_GLOBAL_AUTOGEN_TARGET")) {
56         std::string targetName =
57           makefile->GetSafeDefinition("CMAKE_GLOBAL_AUTOGEN_TARGET_NAME");
58         if (targetName.empty()) {
59           targetName = "autogen";
60         }
61         this->GlobalAutoGenTargets_.emplace(localGen.get(),
62                                             std::move(targetName));
63         globalAutoGenTarget = true;
64       }
65 
66       // Detect global autorcc target name
67       if (makefile->IsOn("CMAKE_GLOBAL_AUTORCC_TARGET")) {
68         std::string targetName =
69           makefile->GetSafeDefinition("CMAKE_GLOBAL_AUTORCC_TARGET_NAME");
70         if (targetName.empty()) {
71           targetName = "autorcc";
72         }
73         this->GlobalAutoRccTargets_.emplace(localGen.get(),
74                                             std::move(targetName));
75         globalAutoRccTarget = true;
76       }
77     }
78 
79     // Find targets that require AUTOMOC/UIC/RCC processing
80     for (const auto& target : localGen->GetGeneratorTargets()) {
81       // Process only certain target types
82       switch (target->GetType()) {
83         case cmStateEnums::EXECUTABLE:
84         case cmStateEnums::STATIC_LIBRARY:
85         case cmStateEnums::SHARED_LIBRARY:
86         case cmStateEnums::MODULE_LIBRARY:
87         case cmStateEnums::OBJECT_LIBRARY:
88           // Process target
89           break;
90         default:
91           // Don't process target
92           continue;
93       }
94       if (target->IsImported()) {
95         // Don't process target
96         continue;
97       }
98       std::set<std::string> const& languages =
99         target->GetAllConfigCompileLanguages();
100       // cmGeneratorTarget::GetAllConfigCompileLanguages caches the target's
101       // sources. Clear it so that OBJECT library targets that are AUTOGEN
102       // initialized after this target get their added mocs_compilation.cpp
103       // source acknowledged by this target.
104       target->ClearSourcesCache();
105       if (languages.count("CSharp")) {
106         // Don't process target if it's a CSharp target
107         continue;
108       }
109 
110       bool const moc = target->GetPropertyAsBool(this->kw().AUTOMOC);
111       bool const uic = target->GetPropertyAsBool(this->kw().AUTOUIC);
112       bool const rcc = target->GetPropertyAsBool(this->kw().AUTORCC);
113       if (moc || uic || rcc) {
114         std::string const& mocExec =
115           target->GetSafeProperty(this->kw().AUTOMOC_EXECUTABLE);
116         std::string const& uicExec =
117           target->GetSafeProperty(this->kw().AUTOUIC_EXECUTABLE);
118         std::string const& rccExec =
119           target->GetSafeProperty(this->kw().AUTORCC_EXECUTABLE);
120 
121         // We support Qt4, Qt5 and Qt6
122         auto qtVersion =
123           cmQtAutoGenInitializer::GetQtVersion(target.get(), mocExec);
124         bool const validQt = (qtVersion.first.Major == 4) ||
125           (qtVersion.first.Major == 5) || (qtVersion.first.Major == 6);
126 
127         bool const mocAvailable = (validQt || !mocExec.empty());
128         bool const uicAvailable = (validQt || !uicExec.empty());
129         bool const rccAvailable = (validQt || !rccExec.empty());
130         bool const mocIsValid = (moc && mocAvailable);
131         bool const uicIsValid = (uic && uicAvailable);
132         bool const rccIsValid = (rcc && rccAvailable);
133         // Disabled AUTOMOC/UIC/RCC warning
134         bool const mocDisabled = (moc && !mocAvailable);
135         bool const uicDisabled = (uic && !uicAvailable);
136         bool const rccDisabled = (rcc && !rccAvailable);
137         if (mocDisabled || uicDisabled || rccDisabled) {
138           cmAlphaNum version = (qtVersion.second == 0)
139             ? cmAlphaNum("<QTVERSION>")
140             : cmAlphaNum(qtVersion.second);
141           cmAlphaNum component = uicDisabled ? "Widgets" : "Core";
142 
143           std::string const msg = cmStrCat(
144             "AUTOGEN: No valid Qt version found for target ",
145             target->GetName(), ".  ",
146             cmQtAutoGen::Tools(mocDisabled, uicDisabled, rccDisabled),
147             " disabled.  Consider adding:\n", "  find_package(Qt", version,
148             " COMPONENTS ", component, ")\n", "to your CMakeLists.txt file.");
149           target->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, msg);
150         }
151         if (mocIsValid || uicIsValid || rccIsValid) {
152           // Create autogen target initializer
153           this->Initializers_.emplace_back(
154             cm::make_unique<cmQtAutoGenInitializer>(
155               this, target.get(), qtVersion.first, mocIsValid, uicIsValid,
156               rccIsValid, globalAutoGenTarget, globalAutoRccTarget));
157         }
158       }
159     }
160   }
161 }
162 
163 cmQtAutoGenGlobalInitializer::~cmQtAutoGenGlobalInitializer() = default;
164 
GetOrCreateGlobalTarget(cmLocalGenerator * localGen,std::string const & name,std::string const & comment)165 void cmQtAutoGenGlobalInitializer::GetOrCreateGlobalTarget(
166   cmLocalGenerator* localGen, std::string const& name,
167   std::string const& comment)
168 {
169   // Test if the target already exists
170   if (localGen->FindGeneratorTargetToUse(name) == nullptr) {
171     cmMakefile* makefile = localGen->GetMakefile();
172 
173     // Create utility target
174     std::vector<std::string> no_byproducts;
175     std::vector<std::string> no_depends;
176     cmCustomCommandLines no_commands;
177     const cmPolicies::PolicyStatus cmp0116_new = cmPolicies::NEW;
178     cmTarget* target = localGen->AddUtilityCommand(
179       name, true, makefile->GetHomeOutputDirectory().c_str(), no_byproducts,
180       no_depends, no_commands, cmp0116_new, false, comment.c_str());
181     localGen->AddGeneratorTarget(
182       cm::make_unique<cmGeneratorTarget>(target, localGen));
183 
184     // Set FOLDER property in the target
185     {
186       cmValue folder =
187         makefile->GetState()->GetGlobalProperty("AUTOGEN_TARGETS_FOLDER");
188       if (folder) {
189         target->SetProperty("FOLDER", folder);
190       }
191     }
192   }
193 }
194 
AddToGlobalAutoGen(cmLocalGenerator * localGen,std::string const & targetName)195 void cmQtAutoGenGlobalInitializer::AddToGlobalAutoGen(
196   cmLocalGenerator* localGen, std::string const& targetName)
197 {
198   auto it = this->GlobalAutoGenTargets_.find(localGen);
199   if (it != this->GlobalAutoGenTargets_.end()) {
200     cmGeneratorTarget* target = localGen->FindGeneratorTargetToUse(it->second);
201     if (target != nullptr) {
202       target->Target->AddUtility(targetName, false, localGen->GetMakefile());
203     }
204   }
205 }
206 
AddToGlobalAutoRcc(cmLocalGenerator * localGen,std::string const & targetName)207 void cmQtAutoGenGlobalInitializer::AddToGlobalAutoRcc(
208   cmLocalGenerator* localGen, std::string const& targetName)
209 {
210   auto it = this->GlobalAutoRccTargets_.find(localGen);
211   if (it != this->GlobalAutoRccTargets_.end()) {
212     cmGeneratorTarget* target = localGen->FindGeneratorTargetToUse(it->second);
213     if (target != nullptr) {
214       target->Target->AddUtility(targetName, false, localGen->GetMakefile());
215     }
216   }
217 }
218 
219 cmQtAutoGen::CompilerFeaturesHandle
GetCompilerFeatures(std::string const & generator,std::string const & executable,std::string & error)220 cmQtAutoGenGlobalInitializer::GetCompilerFeatures(
221   std::string const& generator, std::string const& executable,
222   std::string& error)
223 {
224   // Check if we have cached features
225   {
226     auto it = this->CompilerFeatures_.find(executable);
227     if (it != this->CompilerFeatures_.end()) {
228       return it->second;
229     }
230   }
231 
232   // Check if the executable exists
233   if (!cmSystemTools::FileExists(executable, true)) {
234     error = cmStrCat("The \"", generator, "\" executable ",
235                      cmQtAutoGen::Quoted(executable), " does not exist.");
236     return cmQtAutoGen::CompilerFeaturesHandle();
237   }
238 
239   // Test the executable
240   std::string stdOut;
241   {
242     std::string stdErr;
243     std::vector<std::string> command;
244     command.emplace_back(executable);
245     command.emplace_back("-h");
246     int retVal = 0;
247     const bool runResult = cmSystemTools::RunSingleCommand(
248       command, &stdOut, &stdErr, &retVal, nullptr, cmSystemTools::OUTPUT_NONE,
249       cmDuration::zero(), cmProcessOutput::Auto);
250     if (!runResult) {
251       error = cmStrCat("Test run of \"", generator, "\" executable ",
252                        cmQtAutoGen::Quoted(executable), " failed.\n",
253                        cmQtAutoGen::QuotedCommand(command), '\n', stdOut, '\n',
254                        stdErr);
255       return cmQtAutoGen::CompilerFeaturesHandle();
256     }
257   }
258 
259   // Create valid handle
260   cmQtAutoGen::CompilerFeaturesHandle res =
261     std::make_shared<cmQtAutoGen::CompilerFeatures>();
262   res->HelpOutput = std::move(stdOut);
263 
264   // Register compiler features
265   this->CompilerFeatures_.emplace(executable, res);
266 
267   return res;
268 }
269 
generate()270 bool cmQtAutoGenGlobalInitializer::generate()
271 {
272   return (this->InitializeCustomTargets() && this->SetupCustomTargets());
273 }
274 
InitializeCustomTargets()275 bool cmQtAutoGenGlobalInitializer::InitializeCustomTargets()
276 {
277   // Initialize global autogen targets
278   {
279     std::string const comment = "Global AUTOGEN target";
280     for (auto const& pair : this->GlobalAutoGenTargets_) {
281       this->GetOrCreateGlobalTarget(pair.first, pair.second, comment);
282     }
283   }
284   // Initialize global autorcc targets
285   {
286     std::string const comment = "Global AUTORCC target";
287     for (auto const& pair : this->GlobalAutoRccTargets_) {
288       this->GetOrCreateGlobalTarget(pair.first, pair.second, comment);
289     }
290   }
291   // Initialize per target autogen targets
292   for (auto& initializer : this->Initializers_) {
293     if (!initializer->InitCustomTargets()) {
294       return false;
295     }
296   }
297   return true;
298 }
299 
SetupCustomTargets()300 bool cmQtAutoGenGlobalInitializer::SetupCustomTargets()
301 {
302   for (auto& initializer : this->Initializers_) {
303     if (!initializer->SetupCustomTargets()) {
304       return false;
305     }
306   }
307   return true;
308 }
309