1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "metamakefile.h" |
5 | #include "qdir.h" |
6 | #include "qdebug.h" |
7 | #include "makefile.h" |
8 | #include "project.h" |
9 | #include "cachekeys.h" |
10 | |
11 | #include <algorithm> |
12 | #include <iterator> |
13 | #include <utility> |
14 | |
15 | #define BUILDSMETATYPE 1 |
16 | #define SUBDIRSMETATYPE 2 |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | MetaMakefileGenerator::~MetaMakefileGenerator() |
21 | { |
22 | if(own_project) |
23 | delete project; |
24 | } |
25 | |
26 | #ifndef QT_QMAKE_PARSER_ONLY |
27 | |
28 | class BuildsMetaMakefileGenerator : public MetaMakefileGenerator |
29 | { |
30 | private: |
31 | bool init_flag; |
32 | struct Build { |
33 | QString name, build; |
34 | MakefileGenerator *makefile; |
35 | }; |
36 | QList<Build *> makefiles; |
37 | void clearBuilds(); |
38 | MakefileGenerator *processBuild(const ProString &); |
39 | void accumulateVariableFromBuilds(const ProKey &name, Build *build) const; |
40 | void checkForConflictingTargets() const; |
41 | |
42 | public: |
43 | |
44 | BuildsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { } |
45 | ~BuildsMetaMakefileGenerator() { clearBuilds(); } |
46 | |
47 | bool init() override; |
48 | int type() const override { return BUILDSMETATYPE; } |
49 | bool write() override; |
50 | }; |
51 | |
52 | void |
53 | BuildsMetaMakefileGenerator::clearBuilds() |
54 | { |
55 | for(int i = 0; i < makefiles.size(); i++) { |
56 | Build *build = makefiles[i]; |
57 | if(QMakeProject *p = build->makefile->projectFile()) { |
58 | if(p != project) |
59 | delete p; |
60 | } |
61 | delete build->makefile; |
62 | delete build; |
63 | } |
64 | makefiles.clear(); |
65 | } |
66 | |
67 | bool |
68 | BuildsMetaMakefileGenerator::init() |
69 | { |
70 | if(init_flag) |
71 | return false; |
72 | init_flag = true; |
73 | |
74 | const ProStringList &builds = project->values(v: "BUILDS" ); |
75 | bool use_single_build = builds.isEmpty(); |
76 | if(builds.size() > 1 && Option::output.fileName() == "-" ) { |
77 | use_single_build = true; |
78 | warn_msg(t: WarnLogic, fmt: "Cannot direct to stdout when using multiple BUILDS." ); |
79 | } |
80 | if(!use_single_build) { |
81 | for(int i = 0; i < builds.size(); i++) { |
82 | ProString build = builds[i]; |
83 | MakefileGenerator *makefile = processBuild(build); |
84 | if(!makefile) |
85 | return false; |
86 | if(!makefile->supportsMetaBuild()) { |
87 | warn_msg(t: WarnLogic, fmt: "QMAKESPEC does not support multiple BUILDS." ); |
88 | clearBuilds(); |
89 | use_single_build = true; |
90 | break; |
91 | } else { |
92 | Build *b = new Build; |
93 | b->name = name; |
94 | if(builds.size() != 1) |
95 | b->build = build.toQString(); |
96 | b->makefile = makefile; |
97 | makefiles += b; |
98 | } |
99 | } |
100 | } |
101 | if(use_single_build) { |
102 | Build *build = new Build; |
103 | build->name = name; |
104 | build->makefile = createMakefileGenerator(proj: project, noIO: false); |
105 | if (build->makefile){ |
106 | makefiles += build; |
107 | }else { |
108 | delete build; |
109 | return false; |
110 | } |
111 | } |
112 | return true; |
113 | } |
114 | |
115 | bool |
116 | BuildsMetaMakefileGenerator::write() |
117 | { |
118 | Build *glue = nullptr; |
119 | if(!makefiles.isEmpty() && !makefiles.first()->build.isNull() |
120 | && Option::qmake_mode != Option::QMAKE_GENERATE_PRL) { |
121 | glue = new Build; |
122 | glue->name = name; |
123 | glue->makefile = createMakefileGenerator(proj: project, noIO: true); |
124 | makefiles += glue; |
125 | } |
126 | |
127 | bool ret = true; |
128 | const QString &output_name = Option::output.fileName(); |
129 | for(int i = 0; ret && i < makefiles.size(); i++) { |
130 | Option::output.setFileName(output_name); |
131 | Build *build = makefiles[i]; |
132 | |
133 | bool using_stdout = false; |
134 | if(build->makefile && (Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE || |
135 | Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) |
136 | && (!build->makefile->supportsMergedBuilds() |
137 | || (build->makefile->supportsMergedBuilds() && (!glue || build == glue)))) { |
138 | //open output |
139 | if(!(Option::output.isOpen())) { |
140 | if(Option::output.fileName() == "-" ) { |
141 | Option::output.setFileName("" ); |
142 | Option::output_dir = qmake_getpwd(); |
143 | Option::output.open(stdout, ioFlags: QIODevice::WriteOnly | QIODevice::Text); |
144 | using_stdout = true; |
145 | } else { |
146 | if(Option::output.fileName().isEmpty() && |
147 | Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE) |
148 | Option::output.setFileName(project->first(variableName: "QMAKE_MAKEFILE" ).toQString()); |
149 | QString build_name = build->name; |
150 | if(!build->build.isEmpty()) { |
151 | if(!build_name.isEmpty()) |
152 | build_name += "." ; |
153 | build_name += build->build; |
154 | } |
155 | if(!build->makefile->openOutput(Option::output, build: build_name)) { |
156 | fprintf(stderr, format: "Failure to open file: %s\n" , |
157 | Option::output.fileName().isEmpty() ? "(stdout)" : |
158 | Option::output.fileName().toLatin1().constData()); |
159 | return false; |
160 | } |
161 | } |
162 | } |
163 | } else { |
164 | using_stdout = true; //kind of.. |
165 | } |
166 | |
167 | if(!build->makefile) { |
168 | ret = false; |
169 | } else if(build == glue) { |
170 | checkForConflictingTargets(); |
171 | accumulateVariableFromBuilds(name: "QMAKE_INTERNAL_INCLUDED_FILES" , build); |
172 | ret = build->makefile->writeProjectMakefile(); |
173 | } else { |
174 | ret = build->makefile->write(); |
175 | if (glue && glue->makefile->supportsMergedBuilds()) |
176 | ret = glue->makefile->mergeBuildProject(build->makefile); |
177 | } |
178 | if(!using_stdout) { |
179 | Option::output.close(); |
180 | if(!ret) |
181 | Option::output.remove(); |
182 | } |
183 | } |
184 | return ret; |
185 | } |
186 | |
187 | MakefileGenerator |
188 | *BuildsMetaMakefileGenerator::processBuild(const ProString &build) |
189 | { |
190 | if(project) { |
191 | debug_msg(level: 1, fmt: "Meta Generator: Parsing '%s' for build [%s]." , |
192 | project->projectFile().toLatin1().constData(),build.toLatin1().constData()); |
193 | |
194 | //initialize the base |
195 | ProValueMap basevars; |
196 | ProStringList basecfgs = project->values(v: ProKey(build + ".CONFIG" )); |
197 | basecfgs += build; |
198 | basecfgs += "build_pass" ; |
199 | basevars["BUILD_PASS" ] = ProStringList(build); |
200 | ProStringList buildname = project->values(v: ProKey(build + ".name" )); |
201 | basevars["BUILD_NAME" ] = (buildname.isEmpty() ? ProStringList(build) : buildname); |
202 | |
203 | //create project |
204 | QMakeProject *build_proj = new QMakeProject; |
205 | build_proj->setExtraVars(basevars); |
206 | build_proj->setExtraConfigs(basecfgs); |
207 | |
208 | if (build_proj->read(project: project->projectFile())) |
209 | return createMakefileGenerator(proj: build_proj); |
210 | } |
211 | return nullptr; |
212 | } |
213 | |
214 | void BuildsMetaMakefileGenerator::accumulateVariableFromBuilds(const ProKey &name, Build *dst) const |
215 | { |
216 | ProStringList &values = dst->makefile->projectFile()->values(v: name); |
217 | for (auto build : makefiles) { |
218 | if (build != dst) |
219 | values += build->makefile->projectFile()->values(v: name); |
220 | } |
221 | values.removeDuplicates(); |
222 | } |
223 | |
224 | void BuildsMetaMakefileGenerator::checkForConflictingTargets() const |
225 | { |
226 | if (makefiles.size() < 3) { |
227 | // Checking for conflicts only makes sense if we have more than one BUILD, |
228 | // and the last entry in makefiles is the "glue" Build. |
229 | return; |
230 | } |
231 | if (!project->isActiveConfig(config: "build_all" )) { |
232 | // Only complain if we're about to build all configurations. |
233 | return; |
234 | } |
235 | using TargetInfo = std::pair<Build *, ProString>; |
236 | QList<TargetInfo> targets; |
237 | const int last = makefiles.size() - 1; |
238 | targets.resize(size: last); |
239 | for (int i = 0; i < last; ++i) { |
240 | Build *b = makefiles.at(i); |
241 | auto mkf = b->makefile; |
242 | auto prj = mkf->projectFile(); |
243 | targets[i] = std::make_pair(x&: b, y: prj->first(variableName: mkf->fullTargetVariable())); |
244 | } |
245 | std::stable_sort(first: targets.begin(), last: targets.end(), |
246 | comp: [](const TargetInfo &lhs, const TargetInfo &rhs) |
247 | { |
248 | return lhs.second < rhs.second; |
249 | }); |
250 | for (auto prev = targets.begin(), it = std::next(x: prev); it != targets.end(); ++prev, ++it) { |
251 | if (prev->second == it->second) { |
252 | warn_msg(t: WarnLogic, fmt: "Targets of builds '%s' and '%s' conflict: %s." , |
253 | qPrintable(prev->first->build), |
254 | qPrintable(it->first->build), |
255 | qPrintable(prev->second.toQString())); |
256 | break; |
257 | } |
258 | } |
259 | } |
260 | |
261 | class SubdirsMetaMakefileGenerator : public MetaMakefileGenerator |
262 | { |
263 | protected: |
264 | bool init_flag; |
265 | struct Subdir { |
266 | Subdir() : makefile(nullptr), indent(0) { } |
267 | ~Subdir() { delete makefile; } |
268 | QString input_dir; |
269 | QString output_dir, output_file; |
270 | MetaMakefileGenerator *makefile; |
271 | int indent; |
272 | }; |
273 | QList<Subdir *> subs; |
274 | MakefileGenerator *processBuild(const QString &); |
275 | |
276 | public: |
277 | SubdirsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { } |
278 | ~SubdirsMetaMakefileGenerator(); |
279 | |
280 | bool init() override; |
281 | int type() const override { return SUBDIRSMETATYPE; } |
282 | bool write() override; |
283 | }; |
284 | |
285 | bool |
286 | SubdirsMetaMakefileGenerator::init() |
287 | { |
288 | if(init_flag) |
289 | return false; |
290 | init_flag = true; |
291 | bool hasError = false; |
292 | |
293 | bool recurse = Option::recursive; |
294 | if (recurse && project->isActiveConfig(config: "dont_recurse" )) |
295 | recurse = false; |
296 | if(recurse) { |
297 | QString old_output_dir = Option::output_dir; |
298 | QString old_output = Option::output.fileName(); |
299 | QString oldpwd = qmake_getpwd(); |
300 | QString thispwd = oldpwd; |
301 | if(!thispwd.endsWith(c: '/')) |
302 | thispwd += '/'; |
303 | const ProStringList &subdirs = project->values(v: "SUBDIRS" ); |
304 | Q_CONSTINIT static int recurseDepth = -1; |
305 | ++recurseDepth; |
306 | for(int i = 0; i < subdirs.size(); ++i) { |
307 | Subdir *sub = new Subdir; |
308 | sub->indent = recurseDepth; |
309 | |
310 | QFileInfo subdir(subdirs.at(i).toQString()); |
311 | const ProKey fkey(subdirs.at(i) + ".file" ); |
312 | if (!project->isEmpty(v: fkey)) { |
313 | subdir = QFileInfo(project->first(variableName: fkey).toQString()); |
314 | } else { |
315 | const ProKey skey(subdirs.at(i) + ".subdir" ); |
316 | if (!project->isEmpty(v: skey)) |
317 | subdir = QFileInfo(project->first(variableName: skey).toQString()); |
318 | } |
319 | QString sub_name; |
320 | if(subdir.isDir()) |
321 | subdir = QFileInfo(subdir.filePath() + "/" + subdir.fileName() + Option::pro_ext); |
322 | else |
323 | sub_name = subdir.baseName(); |
324 | if(!subdir.isRelative()) { //we can try to make it relative |
325 | QString subdir_path = subdir.filePath(); |
326 | if(subdir_path.startsWith(s: thispwd)) |
327 | subdir = QFileInfo(subdir_path.mid(position: thispwd.size())); |
328 | } |
329 | |
330 | //handle sub project |
331 | QMakeProject *sub_proj = new QMakeProject; |
332 | for (int ind = 0; ind < sub->indent; ++ind) |
333 | printf(format: " " ); |
334 | sub->input_dir = subdir.absolutePath(); |
335 | if(subdir.isRelative() && old_output_dir != oldpwd) { |
336 | sub->output_dir = old_output_dir + (subdir.path() != "." ? "/" + subdir.path() : QString()); |
337 | printf(format: "Reading %s [%s]\n" , subdir.absoluteFilePath().toLatin1().constData(), sub->output_dir.toLatin1().constData()); |
338 | } else { //what about shadow builds? |
339 | sub->output_dir = sub->input_dir; |
340 | printf(format: "Reading %s\n" , subdir.absoluteFilePath().toLatin1().constData()); |
341 | } |
342 | qmake_setpwd(p: sub->input_dir); |
343 | Option::output_dir = sub->output_dir; |
344 | bool tmpError = !sub_proj->read(project: subdir.fileName()); |
345 | if (!sub_proj->isEmpty(v: "QMAKE_FAILED_REQUIREMENTS" )) { |
346 | fprintf(stderr, format: "Project file(%s) not recursed because all requirements not met:\n\t%s\n" , |
347 | subdir.fileName().toLatin1().constData(), |
348 | sub_proj->values(v: "QMAKE_FAILED_REQUIREMENTS" ).join(sep: ' ').toLatin1().constData()); |
349 | delete sub; |
350 | delete sub_proj; |
351 | Option::output_dir = old_output_dir; |
352 | qmake_setpwd(p: oldpwd); |
353 | continue; |
354 | } else { |
355 | hasError |= tmpError; |
356 | } |
357 | sub->makefile = MetaMakefileGenerator::createMetaGenerator(proj: sub_proj, name: sub_name); |
358 | const QString output_name = Option::output.fileName(); |
359 | Option::output.setFileName(sub->output_file); |
360 | hasError |= !sub->makefile->write(); |
361 | delete sub; |
362 | qmakeClearCaches(); |
363 | sub = nullptr; |
364 | Option::output.setFileName(output_name); |
365 | Option::output_dir = old_output_dir; |
366 | qmake_setpwd(p: oldpwd); |
367 | |
368 | } |
369 | --recurseDepth; |
370 | Option::output.setFileName(old_output); |
371 | Option::output_dir = old_output_dir; |
372 | qmake_setpwd(p: oldpwd); |
373 | } |
374 | |
375 | Subdir *self = new Subdir; |
376 | self->input_dir = qmake_getpwd(); |
377 | self->output_dir = Option::output_dir; |
378 | if(!recurse || (!Option::output.fileName().endsWith(s: Option::dir_sep) && !QFileInfo(Option::output).isDir())) |
379 | self->output_file = Option::output.fileName(); |
380 | self->makefile = new BuildsMetaMakefileGenerator(project, name, false); |
381 | self->makefile->init(); |
382 | subs.append(t: self); |
383 | |
384 | return !hasError; |
385 | } |
386 | |
387 | bool |
388 | SubdirsMetaMakefileGenerator::write() |
389 | { |
390 | bool ret = true; |
391 | const QString &pwd = qmake_getpwd(); |
392 | const QString &output_dir = Option::output_dir; |
393 | const QString &output_name = Option::output.fileName(); |
394 | for(int i = 0; ret && i < subs.size(); i++) { |
395 | const Subdir *sub = subs.at(i); |
396 | qmake_setpwd(p: sub->input_dir); |
397 | Option::output_dir = QFileInfo(sub->output_dir).absoluteFilePath(); |
398 | Option::output.setFileName(sub->output_file); |
399 | if(i != subs.size()-1) { |
400 | for (int ind = 0; ind < sub->indent; ++ind) |
401 | printf(format: " " ); |
402 | printf(format: "Writing %s\n" , QDir::cleanPath(path: Option::output_dir+"/" + |
403 | Option::output.fileName()).toLatin1().constData()); |
404 | } |
405 | if (!(ret = sub->makefile->write())) |
406 | break; |
407 | //restore because I'm paranoid |
408 | qmake_setpwd(p: pwd); |
409 | Option::output.setFileName(output_name); |
410 | Option::output_dir = output_dir; |
411 | } |
412 | return ret; |
413 | } |
414 | |
415 | SubdirsMetaMakefileGenerator::~SubdirsMetaMakefileGenerator() |
416 | { |
417 | for(int i = 0; i < subs.size(); i++) |
418 | delete subs[i]; |
419 | subs.clear(); |
420 | } |
421 | |
422 | //Factory things |
423 | QT_BEGIN_INCLUDE_NAMESPACE |
424 | #include "unixmake.h" |
425 | #include "mingw_make.h" |
426 | #include "projectgenerator.h" |
427 | #include "pbuilder_pbx.h" |
428 | #include "msvc_nmake.h" |
429 | #include "msvc_vcproj.h" |
430 | #include "msvc_vcxproj.h" |
431 | QT_END_INCLUDE_NAMESPACE |
432 | |
433 | MakefileGenerator * |
434 | MetaMakefileGenerator::createMakefileGenerator(QMakeProject *proj, bool noIO) |
435 | { |
436 | Option::postProcessProject(proj); |
437 | |
438 | MakefileGenerator *mkfile = nullptr; |
439 | if(Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) { |
440 | mkfile = new ProjectGenerator; |
441 | mkfile->setProjectFile(proj); |
442 | return mkfile; |
443 | } |
444 | |
445 | ProString gen = proj->first(variableName: "MAKEFILE_GENERATOR" ); |
446 | if(gen.isEmpty()) { |
447 | fprintf(stderr, format: "MAKEFILE_GENERATOR variable not set as a result of parsing : %s. Possibly qmake was not able to find files included using \"include(..)\" - enable qmake debugging to investigate more.\n" , |
448 | proj->projectFile().toLatin1().constData()); |
449 | } else if(gen == "UNIX" ) { |
450 | mkfile = new UnixMakefileGenerator; |
451 | } else if(gen == "MINGW" ) { |
452 | mkfile = new MingwMakefileGenerator; |
453 | } else if(gen == "PROJECTBUILDER" || gen == "XCODE" ) { |
454 | #ifdef Q_CC_MSVC |
455 | fprintf(stderr, "Generating Xcode projects is not supported with an MSVC build of Qt.\n" ); |
456 | #else |
457 | mkfile = new ProjectBuilderMakefileGenerator; |
458 | #endif |
459 | } else if(gen == "MSVC.NET" ) { |
460 | if (proj->first(variableName: "TEMPLATE" ).startsWith(sub: "vc" )) |
461 | mkfile = new VcprojGenerator; |
462 | else |
463 | mkfile = new NmakeMakefileGenerator; |
464 | } else if(gen == "MSBUILD" ) { |
465 | // Visual Studio >= v11.0 |
466 | if (proj->first(variableName: "TEMPLATE" ).startsWith(sub: "vc" )) |
467 | mkfile = new VcxprojGenerator; |
468 | else |
469 | mkfile = new NmakeMakefileGenerator; |
470 | } else { |
471 | fprintf(stderr, format: "Unknown generator specified: %s\n" , gen.toLatin1().constData()); |
472 | } |
473 | if (mkfile) { |
474 | mkfile->setNoIO(noIO); |
475 | mkfile->setProjectFile(proj); |
476 | } |
477 | return mkfile; |
478 | } |
479 | |
480 | MetaMakefileGenerator * |
481 | MetaMakefileGenerator::createMetaGenerator(QMakeProject *proj, const QString &name, bool op, bool *success) |
482 | { |
483 | Option::postProcessProject(proj); |
484 | |
485 | MetaMakefileGenerator *ret = nullptr; |
486 | if ((Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE || |
487 | Option::qmake_mode == Option::QMAKE_GENERATE_PRL)) { |
488 | if (proj->first(variableName: "TEMPLATE" ).endsWith(sub: "subdirs" )) |
489 | ret = new SubdirsMetaMakefileGenerator(proj, name, op); |
490 | } |
491 | if (!ret) |
492 | ret = new BuildsMetaMakefileGenerator(proj, name, op); |
493 | bool res = ret->init(); |
494 | if (success) |
495 | *success = res; |
496 | return ret; |
497 | } |
498 | |
499 | #endif // QT_QMAKE_PARSER_ONLY |
500 | |
501 | QT_END_NAMESPACE |
502 | |