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 "makefile.h"
5#include "option.h"
6#include "cachekeys.h"
7#include "meta.h"
8
9#include <ioutils.h>
10
11#include <qdir.h>
12#include <qfile.h>
13#include <qtextstream.h>
14#include <qregularexpression.h>
15#include <qhash.h>
16#include <qdebug.h>
17#include <qbuffer.h>
18#include <qdatetime.h>
19#include <qtversion.h>
20
21#if defined(Q_OS_UNIX)
22#include <unistd.h>
23#else
24#include <io.h>
25#endif
26#include <stdio.h>
27#include <stdlib.h>
28#include <time.h>
29#include <fcntl.h>
30#include <sys/types.h>
31#include <sys/stat.h>
32
33#include <algorithm>
34
35QT_BEGIN_NAMESPACE
36
37using namespace QMakeInternal;
38
39bool MakefileGenerator::canExecute(const QStringList &cmdline, int *a) const
40{
41 int argv0 = -1;
42 for(int i = 0; i < cmdline.size(); ++i) {
43 if(!cmdline.at(i).contains(c: '=')) {
44 argv0 = i;
45 break;
46 }
47 }
48 if(a)
49 *a = argv0;
50 if(argv0 != -1) {
51 const QString c = Option::normalizePath(in: cmdline.at(i: argv0));
52 if(exists(file: c))
53 return true;
54 }
55 return false;
56}
57
58QString MakefileGenerator::mkdir_p_asstring(const QString &dir, bool escape) const
59{
60 return "@" + makedir.arg(
61 a: escape ? escapeFilePath(path: Option::fixPathToTargetOS(in: dir, fix_env: false, canonical: false)) : dir);
62}
63
64bool MakefileGenerator::mkdir(const QString &in_path) const
65{
66 QString path = Option::normalizePath(in: in_path);
67 if(QFile::exists(fileName: path))
68 return true;
69
70 return QDir().mkpath(dirPath: path);
71}
72
73void
74MakefileGenerator::verifyCompilers()
75{
76 ProValueMap &v = project->variables();
77 ProStringList &quc = v["QMAKE_EXTRA_COMPILERS"];
78 for(int i = 0; i < quc.size(); ) {
79 bool error = false;
80 const ProString &comp = quc.at(i);
81 const ProKey okey(comp + ".output");
82 if (v[okey].isEmpty()) {
83 const ProKey ofkey(comp + ".output_function");
84 if (!v[ofkey].isEmpty()) {
85 v[okey].append(t: "${QMAKE_FUNC_FILE_IN_" + v[ofkey].first() + "}");
86 } else {
87 error = true;
88 warn_msg(t: WarnLogic, fmt: "Compiler: %s: No output file specified", comp.toLatin1().constData());
89 }
90 } else if (v[ProKey(comp + ".input")].isEmpty()) {
91 error = true;
92 warn_msg(t: WarnLogic, fmt: "Compiler: %s: No input variable specified", comp.toLatin1().constData());
93 }
94 if(error)
95 quc.removeAt(idx: i);
96 else
97 ++i;
98 }
99}
100
101void
102MakefileGenerator::initOutPaths()
103{
104 ProValueMap &v = project->variables();
105 //for shadow builds
106 if(!v.contains(key: "QMAKE_ABSOLUTE_SOURCE_PATH")) {
107 if (Option::globals->do_cache && !project->cacheFile().isEmpty() &&
108 v.contains(key: "QMAKE_ABSOLUTE_SOURCE_ROOT")) {
109 QString root = v["QMAKE_ABSOLUTE_SOURCE_ROOT"].first().toQString();
110 root = QDir::fromNativeSeparators(pathName: root);
111 if(!root.isEmpty()) {
112 QFileInfo fi = fileInfo(file: project->cacheFile());
113 if(!fi.makeAbsolute()) {
114 QString cache_r = fi.path(), pwd = Option::output_dir;
115 if(pwd.startsWith(s: cache_r) && !pwd.startsWith(s: root)) {
116 pwd = root + pwd.mid(position: cache_r.size());
117 if(exists(file: pwd))
118 v.insert(key: "QMAKE_ABSOLUTE_SOURCE_PATH", value: ProStringList(pwd));
119 }
120 }
121 }
122 }
123 }
124 if(!v["QMAKE_ABSOLUTE_SOURCE_PATH"].isEmpty()) {
125 ProString &asp = v["QMAKE_ABSOLUTE_SOURCE_PATH"].first();
126 asp = QDir::fromNativeSeparators(pathName: asp.toQString());
127 if(asp.isEmpty() || asp == Option::output_dir) //if they're the same, why bother?
128 v["QMAKE_ABSOLUTE_SOURCE_PATH"].clear();
129 }
130
131 //some builtin directories
132 if(project->isEmpty(v: "PRECOMPILED_DIR") && !project->isEmpty(v: "OBJECTS_DIR"))
133 v["PRECOMPILED_DIR"] = v["OBJECTS_DIR"];
134 static const char * const dirs[] = { "OBJECTS_DIR", "DESTDIR",
135 "SUBLIBS_DIR", "DLLDESTDIR",
136 "PRECOMPILED_DIR", nullptr };
137 for (int x = 0; dirs[x]; x++) {
138 const ProKey dkey(dirs[x]);
139 if (v[dkey].isEmpty())
140 continue;
141 const ProString orig_path = v[dkey].first();
142
143 ProString &pathRef = v[dkey].first();
144 pathRef = fileFixify(file: pathRef.toQString(), fix: FileFixifyFromOutdir);
145
146 if (!pathRef.endsWith(sub: Option::dir_sep))
147 pathRef += Option::dir_sep;
148
149 if (noIO() || (project->first(variableName: "TEMPLATE") == "subdirs"))
150 continue;
151
152 if (inhibitMakeDirOutPath(path: dkey))
153 continue;
154
155 QString path = project->first(variableName: dkey).toQString(); //not to be changed any further
156 path = fileFixify(file: path, fix: FileFixifyBackwards);
157 debug_msg(level: 3, fmt: "Fixed output_dir %s (%s) into %s", dirs[x],
158 orig_path.toLatin1().constData(), path.toLatin1().constData());
159 if(!mkdir(in_path: path))
160 warn_msg(t: WarnLogic, fmt: "%s: Cannot access directory '%s'", dirs[x],
161 path.toLatin1().constData());
162 }
163
164 //out paths from the extra compilers
165 const ProStringList &quc = project->values(v: "QMAKE_EXTRA_COMPILERS");
166 for (ProStringList::ConstIterator it = quc.begin(); it != quc.end(); ++it) {
167 QString tmp_out = project->first(variableName: ProKey(*it + ".output")).toQString();
168 if(tmp_out.isEmpty())
169 continue;
170 const ProStringList &tmp = project->values(v: ProKey(*it + ".input"));
171 for (ProStringList::ConstIterator it2 = tmp.begin(); it2 != tmp.end(); ++it2) {
172 ProStringList &inputs = project->values(v: (*it2).toKey());
173 for (ProStringList::Iterator input = inputs.begin(); input != inputs.end(); ++input) {
174 QString finp = fileFixify(file: (*input).toQString(), fix: FileFixifyFromOutdir);
175 QString path = replaceExtraCompilerVariables(val: tmp_out, in: finp, out: QString(), forShell: NoShell);
176 path = Option::normalizePath(in: path);
177 int slash = path.lastIndexOf(c: '/');
178 if(slash != -1) {
179 path = path.left(n: slash);
180 // Make out path only if it does not contain makefile variables
181 if(!path.contains(s: "${"))
182 if(path != "." &&
183 !mkdir(in_path: fileFixify(file: path, fix: FileFixifyBackwards)))
184 warn_msg(t: WarnLogic, fmt: "%s: Cannot access directory '%s'",
185 (*it).toLatin1().constData(), path.toLatin1().constData());
186 }
187 }
188 }
189 }
190
191 if(!v["DESTDIR"].isEmpty()) {
192 QDir d(v["DESTDIR"].first().toQString());
193 if (Option::normalizePath(in: d.absolutePath()) == Option::normalizePath(in: Option::output_dir))
194 v.remove(key: "DESTDIR");
195 }
196}
197
198/*
199 * For the given directory path, return true if MakefileGenerator::initOutPaths() should inhibit the
200 * creation of the directory. Overload this in subclasses.
201 */
202bool
203MakefileGenerator::inhibitMakeDirOutPath(const ProKey &path) const
204{
205 Q_UNUSED(path);
206 return false;
207}
208
209
210QMakeProject
211*MakefileGenerator::projectFile() const
212{
213 return project;
214}
215
216void
217MakefileGenerator::setProjectFile(QMakeProject *p)
218{
219 if(project)
220 return;
221 project = p;
222 if (project->isActiveConfig(config: "win32"))
223 target_mode = TARG_WIN_MODE;
224 else if (project->isActiveConfig(config: "mac"))
225 target_mode = TARG_MAC_MODE;
226 else
227 target_mode = TARG_UNIX_MODE;
228 init();
229 bool linkPrl = (Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE)
230 && project->isActiveConfig(config: "link_prl");
231 bool mergeLflags = !project->isActiveConfig(config: "no_smart_library_merge")
232 && !project->isActiveConfig(config: "no_lflags_merge");
233 findLibraries(linkPrl, mergeLflags);
234}
235
236ProStringList
237MakefileGenerator::findFilesInVPATH(ProStringList l, uchar flags, const QString &vpath_var)
238{
239 ProStringList vpath;
240 const ProValueMap &v = project->variables();
241 for(int val_it = 0; val_it < l.size(); ) {
242 bool remove_file = false;
243 ProString &val = l[val_it];
244 if(!val.isEmpty()) {
245 QString qval = val.toQString();
246 QString file = fixEnvVariables(x: qval);
247 if (file.isEmpty()) {
248 ++val_it;
249 continue;
250 }
251 if(!(flags & VPATH_NoFixify))
252 file = fileFixify(file, fix: FileFixifyBackwards);
253
254 if(exists(file)) {
255 ++val_it;
256 continue;
257 }
258 bool found = false;
259 if (QDir::isRelativePath(path: qval)) {
260 if(vpath.isEmpty()) {
261 if(!vpath_var.isEmpty())
262 vpath = v[ProKey(vpath_var)];
263 vpath += v["VPATH"] + v["QMAKE_ABSOLUTE_SOURCE_PATH"];
264 if(Option::output_dir != qmake_getpwd())
265 vpath << Option::output_dir;
266 }
267 for (ProStringList::Iterator vpath_it = vpath.begin();
268 vpath_it != vpath.end(); ++vpath_it) {
269 QString real_dir = Option::normalizePath(in: (*vpath_it).toQString());
270 if (exists(file: real_dir + '/' + val)) {
271 ProString dir = (*vpath_it);
272 if(!dir.endsWith(sub: Option::dir_sep))
273 dir += Option::dir_sep;
274 val = dir + val;
275 if(!(flags & VPATH_NoFixify))
276 val = fileFixify(file: val.toQString());
277 found = true;
278 debug_msg(level: 1, fmt: "Found file through vpath %s -> %s",
279 file.toLatin1().constData(), val.toLatin1().constData());
280 break;
281 }
282 }
283 }
284 if(!found) {
285 QString dir, regex = val.toQString(), real_dir;
286 if(regex.lastIndexOf(s: Option::dir_sep) != -1) {
287 dir = regex.left(n: regex.lastIndexOf(s: Option::dir_sep) + 1);
288 real_dir = dir;
289 if(!(flags & VPATH_NoFixify))
290 real_dir = fileFixify(file: real_dir, fix: FileFixifyBackwards) + '/';
291 regex.remove(i: 0, len: dir.size());
292 }
293 if(real_dir.isEmpty() || exists(file: real_dir)) {
294 QStringList files = QDir(real_dir).entryList(nameFilters: QStringList(regex),
295 filters: QDir::NoDotAndDotDot | QDir::AllEntries);
296 if(files.isEmpty()) {
297 debug_msg(level: 1, fmt: "%s:%d Failure to find %s in vpath (%s)",
298 __FILE__, __LINE__, val.toLatin1().constData(),
299 vpath.join(sep: QString("::")).toLatin1().constData());
300 if (flags & VPATH_RemoveMissingFiles)
301 remove_file = true;
302 if (flags & VPATH_WarnMissingFiles)
303 warn_msg(t: WarnLogic, fmt: "Failure to find: %s", val.toLatin1().constData());
304 } else {
305 l.removeAt(idx: val_it);
306 QString a;
307 for(int i = (int)files.size()-1; i >= 0; i--) {
308 a = real_dir + files[i];
309 if(!(flags & VPATH_NoFixify))
310 a = fileFixify(file: a);
311 l.insert(i: val_it, t: a);
312 }
313 }
314 } else {
315 debug_msg(level: 1, fmt: "%s:%d Cannot match %s%s, as %s does not exist.",
316 __FILE__, __LINE__, real_dir.toLatin1().constData(),
317 regex.toLatin1().constData(), real_dir.toLatin1().constData());
318 if (flags & VPATH_RemoveMissingFiles)
319 remove_file = true;
320 if (flags & VPATH_WarnMissingFiles)
321 warn_msg(t: WarnLogic, fmt: "Failure to find: %s", val.toLatin1().constData());
322 }
323 }
324 }
325 if(remove_file)
326 l.removeAt(idx: val_it);
327 else
328 ++val_it;
329 }
330 return l;
331}
332
333void
334MakefileGenerator::initCompiler(const MakefileGenerator::Compiler &comp)
335{
336 ProValueMap &v = project->variables();
337 ProStringList &l = v[ProKey(comp.variable_in)];
338 // find all the relevant file inputs
339 if(!init_compiler_already.contains(key: comp.variable_in)) {
340 init_compiler_already.insert(key: comp.variable_in, value: true);
341 if(!noIO()) {
342 uchar flags = 0;
343 if (comp.flags & Compiler::CompilerRemoveNoExist)
344 flags |= VPATH_RemoveMissingFiles;
345 if (comp.flags & Compiler::CompilerWarnNoExist)
346 flags |= VPATH_WarnMissingFiles;
347 l = findFilesInVPATH(l, flags, vpath_var: "VPATH_" + comp.variable_in);
348 }
349 }
350}
351
352void
353MakefileGenerator::init()
354{
355 verifyCompilers();
356 initOutPaths();
357
358 ProValueMap &v = project->variables();
359
360 v["QMAKE_BUILTIN_COMPILERS"] = ProStringList() << "C" << "CXX";
361
362 v["QMAKE_LANGUAGE_C"] = ProString("c");
363 v["QMAKE_LANGUAGE_CXX"] = ProString("c++");
364 v["QMAKE_LANGUAGE_OBJC"] = ProString("objective-c");
365 v["QMAKE_LANGUAGE_OBJCXX"] = ProString("objective-c++");
366
367 if (v["TARGET"].isEmpty())
368 warn_msg(t: WarnLogic, fmt: "TARGET is empty");
369
370 makedir = v["QMAKE_MKDIR_CMD"].join(sep: ' ');
371 chkexists = v["QMAKE_CHK_EXISTS"].join(sep: ' ');
372 if (makedir.isEmpty()) { // Backwards compat with Qt < 5.0.2 specs
373 if (isWindowsShell()) {
374 makedir = "if not exist %1 mkdir %1 & if not exist %1 exit 1";
375 chkexists = "if not exist %1";
376 } else {
377 makedir = "test -d %1 || mkdir -p %1";
378 chkexists = "test -e %1 ||";
379 }
380 }
381
382 if (v["QMAKE_CC_O_FLAG"].isEmpty())
383 v["QMAKE_CC_O_FLAG"].append(t: "-o ");
384
385 if (v["QMAKE_LINK_O_FLAG"].isEmpty())
386 v["QMAKE_LINK_O_FLAG"].append(t: "-o ");
387
388 setSystemIncludes(v["QMAKE_DEFAULT_INCDIRS"]);
389
390 ProStringList &incs = project->values(v: "INCLUDEPATH");
391 if (!project->isActiveConfig(config: "no_include_pwd")) {
392 if (Option::output_dir != qmake_getpwd()) {
393 // Pretend that the build dir is the source dir for #include purposes,
394 // consistently with the "transparent shadow builds" strategy. This is
395 // also consistent with #include "foo.h" falling back to #include <foo.h>
396 // behavior if it doesn't find the file in the source dir.
397 incs.prepend(t: Option::output_dir);
398 }
399 // This makes #include <foo.h> work if the header lives in the source dir.
400 // The benefit of that is questionable, as generally the user should use the
401 // correct include style, and extra compilers that put stuff in the source dir
402 // should add the dir themselves.
403 // More importantly, it makes #include "foo.h" work with MSVC when shadow-building,
404 // as this compiler looks files up relative to %CD%, not the source file's parent.
405 incs.prepend(t: qmake_getpwd());
406 }
407 incs.append(t: project->specDir());
408
409 const auto platform = v["QMAKE_PLATFORM"];
410 resolveDependenciesInFrameworks = platform.contains(str: "darwin");
411
412 const char * const cacheKeys[] = { "_QMAKE_STASH_", "_QMAKE_SUPER_CACHE_", nullptr };
413 for (int i = 0; cacheKeys[i]; ++i) {
414 if (v[cacheKeys[i]].isEmpty())
415 continue;
416 const ProString &file = v[cacheKeys[i]].first();
417 if (file.isEmpty())
418 continue;
419
420 QFileInfo fi(fileInfo(file: file.toQString()));
421
422 // If the file lives in the output dir we treat it as 'owned' by
423 // the current project, so it should be distcleaned by it as well.
424 if (fi.path() == Option::output_dir)
425 v["QMAKE_DISTCLEAN"].append(t: fi.fileName());
426 }
427
428 ProStringList &quc = v["QMAKE_EXTRA_COMPILERS"];
429
430 //make sure the COMPILERS are in the correct input/output chain order
431 for(int comp_out = 0, jump_count = 0; comp_out < quc.size(); ++comp_out) {
432 continue_compiler_chain:
433 if(jump_count > quc.size()) //just to avoid an infinite loop here
434 break;
435 const ProKey vokey(quc.at(i: comp_out) + ".variable_out");
436 if (v.contains(key: vokey)) {
437 const ProStringList &outputs = v.value(key: vokey);
438 for(int out = 0; out < outputs.size(); ++out) {
439 for(int comp_in = 0; comp_in < quc.size(); ++comp_in) {
440 if(comp_in == comp_out)
441 continue;
442 const ProKey ikey(quc.at(i: comp_in) + ".input");
443 if (v.contains(key: ikey)) {
444 const ProStringList &inputs = v.value(key: ikey);
445 for(int in = 0; in < inputs.size(); ++in) {
446 if(inputs.at(i: in) == outputs.at(i: out) && comp_out > comp_in) {
447 ++jump_count;
448 //move comp_out to comp_in and continue the compiler chain
449 // quc.move(comp_out, comp_in);
450 quc.insert(i: comp_in, t: quc.value(i: comp_out));
451 // comp_out > comp_in, so the insertion did move everything up
452 quc.remove(i: comp_out + 1);
453 comp_out = comp_in;
454 goto continue_compiler_chain;
455 }
456 }
457 }
458 }
459 }
460 }
461 }
462
463 if(!project->isEmpty(v: "QMAKE_SUBSTITUTES")) {
464 const ProStringList &subs = v["QMAKE_SUBSTITUTES"];
465 for(int i = 0; i < subs.size(); ++i) {
466 QString sub = subs.at(i).toQString();
467 QString inn = sub + ".input", outn = sub + ".output";
468 const ProKey innkey(inn), outnkey(outn);
469 if (v.contains(key: innkey) || v.contains(key: outnkey)) {
470 if (!v.contains(key: innkey) || !v.contains(key: outnkey)) {
471 warn_msg(t: WarnLogic, fmt: "Substitute '%s' has only one of .input and .output",
472 sub.toLatin1().constData());
473 continue;
474 }
475 const ProStringList &tinn = v[innkey], &toutn = v[outnkey];
476 if (tinn.size() != 1) {
477 warn_msg(t: WarnLogic, fmt: "Substitute '%s.input' does not have exactly one value",
478 sub.toLatin1().constData());
479 continue;
480 }
481 if (toutn.size() != 1) {
482 warn_msg(t: WarnLogic, fmt: "Substitute '%s.output' does not have exactly one value",
483 sub.toLatin1().constData());
484 continue;
485 }
486 inn = fileFixify(file: tinn.first().toQString(), fix: FileFixifyToIndir);
487 outn = fileFixify(file: toutn.first().toQString(), fix: FileFixifyBackwards);
488 } else {
489 inn = fileFixify(file: sub, fix: FileFixifyToIndir);
490 if (!QFile::exists(fileName: inn)) {
491 // random insanity for backwards compat: .in file specified with absolute out dir
492 inn = fileFixify(file: sub);
493 }
494 if(!inn.endsWith(s: ".in")) {
495 warn_msg(t: WarnLogic, fmt: "Substitute '%s' does not end with '.in'",
496 inn.toLatin1().constData());
497 continue;
498 }
499 outn = fileFixify(file: inn.left(n: inn.size() - 3), fix: FileFixifyBackwards);
500 }
501
502 const ProKey confign(sub + ".CONFIG");
503 bool verbatim = false;
504 if (v.contains(key: confign))
505 verbatim = v[confign].contains(str: QLatin1String("verbatim"));
506
507 QFile in(inn);
508 if (in.open(flags: QFile::ReadOnly)) {
509 QByteArray contentBytes;
510 if (verbatim) {
511 contentBytes = in.readAll();
512 } else {
513 QString contents;
514 QStack<int> state;
515 enum { IN_CONDITION, MET_CONDITION, PENDING_CONDITION };
516 for (int count = 1; !in.atEnd(); ++count) {
517 QString line = QString::fromLatin1(ba: in.readLine());
518 if (line.startsWith(s: "!!IF ")) {
519 if (state.isEmpty() || state.top() == IN_CONDITION) {
520 QString test = line.mid(position: 5, n: line.size()-(5+1));
521 if (project->test(v: test, file: inn, line: count))
522 state.push(t: IN_CONDITION);
523 else
524 state.push(t: PENDING_CONDITION);
525 } else {
526 state.push(t: MET_CONDITION);
527 }
528 } else if (line.startsWith(s: "!!ELIF ")) {
529 if (state.isEmpty()) {
530 warn_msg(t: WarnLogic, fmt: "(%s:%d): Unexpected else condition",
531 in.fileName().toLatin1().constData(), count);
532 } else if (state.top() == PENDING_CONDITION) {
533 QString test = line.mid(position: 7, n: line.size()-(7+1));
534 if (project->test(v: test, file: inn, line: count)) {
535 state.pop();
536 state.push(t: IN_CONDITION);
537 }
538 } else if (state.top() == IN_CONDITION) {
539 state.pop();
540 state.push(t: MET_CONDITION);
541 }
542 } else if (line.startsWith(s: "!!ELSE")) {
543 if (state.isEmpty()) {
544 warn_msg(t: WarnLogic, fmt: "(%s:%d): Unexpected else condition",
545 in.fileName().toLatin1().constData(), count);
546 } else if (state.top() == PENDING_CONDITION) {
547 state.pop();
548 state.push(t: IN_CONDITION);
549 } else if (state.top() == IN_CONDITION) {
550 state.pop();
551 state.push(t: MET_CONDITION);
552 }
553 } else if (line.startsWith(s: "!!ENDIF")) {
554 if (state.isEmpty())
555 warn_msg(t: WarnLogic, fmt: "(%s:%d): Unexpected endif",
556 in.fileName().toLatin1().constData(), count);
557 else
558 state.pop();
559 } else if (state.isEmpty() || state.top() == IN_CONDITION) {
560 contents += project->expand(v: line, file: in.fileName(), line: count);
561 }
562 }
563 contentBytes = contents.toLatin1();
564 }
565 QFile out(outn);
566 QFileInfo outfi(out);
567 if (out.exists() && out.open(flags: QFile::ReadOnly)) {
568 QByteArray old = out.readAll();
569 if (contentBytes == old) {
570 v["QMAKE_INTERNAL_INCLUDED_FILES"].append(t: in.fileName());
571 v["QMAKE_DISTCLEAN"].append(t: outfi.absoluteFilePath());
572 continue;
573 }
574 out.close();
575 if(!out.remove()) {
576 warn_msg(t: WarnLogic, fmt: "Cannot clear substitute '%s'",
577 out.fileName().toLatin1().constData());
578 continue;
579 }
580 }
581 mkdir(in_path: outfi.absolutePath());
582 if(out.open(flags: QFile::WriteOnly)) {
583 v["QMAKE_INTERNAL_INCLUDED_FILES"].append(t: in.fileName());
584 v["QMAKE_DISTCLEAN"].append(t: outfi.absoluteFilePath());
585 out.write(data: contentBytes);
586 } else {
587 warn_msg(t: WarnLogic, fmt: "Cannot open substitute for output '%s'",
588 out.fileName().toLatin1().constData());
589 }
590 } else {
591 warn_msg(t: WarnLogic, fmt: "Cannot open substitute for input '%s'",
592 in.fileName().toLatin1().constData());
593 }
594 }
595 }
596
597 int x;
598
599 //build up a list of compilers
600 QList<Compiler> compilers;
601 {
602 const char *builtins[] = { "OBJECTS", "SOURCES", "PRECOMPILED_HEADER", nullptr };
603 for(x = 0; builtins[x]; ++x) {
604 Compiler compiler;
605 compiler.variable_in = builtins[x];
606 compiler.flags = Compiler::CompilerBuiltin;
607 compiler.type = QMakeSourceFileInfo::TYPE_C;
608 if(!strcmp(s1: builtins[x], s2: "OBJECTS"))
609 compiler.flags |= Compiler::CompilerNoCheckDeps;
610 compilers.append(t: compiler);
611 }
612 for (ProStringList::ConstIterator it = quc.cbegin(); it != quc.cend(); ++it) {
613 const ProStringList &inputs = v[ProKey(*it + ".input")];
614 for(x = 0; x < inputs.size(); ++x) {
615 Compiler compiler;
616 compiler.variable_in = inputs.at(i: x).toQString();
617 compiler.flags = Compiler::CompilerNoFlags;
618 const ProStringList &config = v[ProKey(*it + ".CONFIG")];
619 if (config.indexOf(t: "ignore_no_exist") != -1)
620 compiler.flags |= Compiler::CompilerRemoveNoExist;
621 else
622 compiler.flags |= Compiler::CompilerWarnNoExist;
623 if (config.indexOf(t: "remove_no_exist") != -1)
624 compiler.flags |= Compiler::CompilerRemoveNoExist;
625 if (config.indexOf(t: "no_dependencies") != -1)
626 compiler.flags |= Compiler::CompilerNoCheckDeps;
627 if (config.indexOf(t: "add_inputs_as_makefile_deps") != -1)
628 compiler.flags |= Compiler::CompilerAddInputsAsMakefileDeps;
629
630 const ProKey dkey(*it + ".dependency_type");
631 ProString dep_type;
632 if (!project->isEmpty(v: dkey))
633 dep_type = project->first(variableName: dkey);
634 if (dep_type.isEmpty())
635 compiler.type = QMakeSourceFileInfo::TYPE_UNKNOWN;
636 else if(dep_type == "TYPE_UI")
637 compiler.type = QMakeSourceFileInfo::TYPE_UI;
638 else
639 compiler.type = QMakeSourceFileInfo::TYPE_C;
640 compilers.append(t: compiler);
641 }
642 }
643 }
644 { //do the path fixifying
645 ProStringList paths;
646 for(x = 0; x < compilers.size(); ++x) {
647 if(!paths.contains(str: compilers.at(i: x).variable_in))
648 paths << compilers.at(i: x).variable_in;
649 }
650 paths << "INCLUDEPATH" << "QMAKE_INTERNAL_INCLUDED_FILES" << "PRECOMPILED_HEADER";
651 for(int y = 0; y < paths.size(); y++) {
652 ProStringList &l = v[paths[y].toKey()];
653 for (ProStringList::Iterator it = l.begin(); it != l.end(); ++it) {
654 if((*it).isEmpty())
655 continue;
656 QString fn = (*it).toQString();
657 if (exists(file: fn))
658 (*it) = fileFixify(file: fn);
659 }
660 }
661 }
662
663 if(noIO() || !doDepends() || project->isActiveConfig(config: "GNUmake"))
664 QMakeSourceFileInfo::setDependencyMode(QMakeSourceFileInfo::NonRecursive);
665 for(x = 0; x < compilers.size(); ++x)
666 initCompiler(comp: compilers.at(i: x));
667
668 //merge actual compiler outputs into their variable_out. This is done last so that
669 //files are already properly fixified.
670 for (ProStringList::Iterator it = quc.begin(); it != quc.end(); ++it) {
671 const ProKey ikey(*it + ".input");
672 const ProKey vokey(*it + ".variable_out");
673 const ProStringList &config = project->values(v: ProKey(*it + ".CONFIG"));
674 const ProString &tmp_out = project->first(variableName: ProKey(*it + ".output"));
675 if(tmp_out.isEmpty())
676 continue;
677 if (config.indexOf(t: "combine") != -1) {
678 const ProStringList &compilerInputs = project->values(v: ikey);
679 // Don't generate compiler output if it doesn't have input.
680 if (compilerInputs.isEmpty() || project->values(v: compilerInputs.first().toKey()).isEmpty())
681 continue;
682 if(tmp_out.indexOf(s: "$") == -1) {
683 if(!verifyExtraCompiler(c: (*it), f: QString())) //verify
684 continue;
685 QString out = fileFixify(file: tmp_out.toQString(), fix: FileFixifyFromOutdir);
686 bool pre_dep = (config.indexOf(t: "target_predeps") != -1);
687 if (v.contains(key: vokey)) {
688 const ProStringList &var_out = v.value(key: vokey);
689 for(int i = 0; i < var_out.size(); ++i) {
690 ProKey v = var_out.at(i).toKey();
691 if(v == QLatin1String("SOURCES"))
692 v = "GENERATED_SOURCES";
693 else if(v == QLatin1String("OBJECTS"))
694 pre_dep = false;
695 ProStringList &list = project->values(v);
696 if(!list.contains(str: out))
697 list.append(t: out);
698 }
699 } else if (config.indexOf(t: "no_link") == -1) {
700 ProStringList &list = project->values(v: "OBJECTS");
701 pre_dep = false;
702 if(!list.contains(str: out))
703 list.append(t: out);
704 } else {
705 ProStringList &list = project->values(v: "UNUSED_SOURCES");
706 if(!list.contains(str: out))
707 list.append(t: out);
708 }
709 if(pre_dep) {
710 ProStringList &list = project->values(v: "PRE_TARGETDEPS");
711 if(!list.contains(str: out))
712 list.append(t: out);
713 }
714 }
715 } else {
716 const ProStringList &tmp = project->values(v: ikey);
717 for (ProStringList::ConstIterator it2 = tmp.begin(); it2 != tmp.end(); ++it2) {
718 const ProStringList &inputs = project->values(v: (*it2).toKey());
719 for (ProStringList::ConstIterator input = inputs.constBegin(); input != inputs.constEnd(); ++input) {
720 if((*input).isEmpty())
721 continue;
722 QString inpf = (*input).toQString();
723 if (!verifyExtraCompiler(c: (*it).toQString(), f: inpf)) //verify
724 continue;
725 QString out = replaceExtraCompilerVariables(val: tmp_out.toQString(), in: inpf, out: QString(), forShell: NoShell);
726 out = fileFixify(file: out, fix: FileFixifyFromOutdir);
727 bool pre_dep = (config.indexOf(t: "target_predeps") != -1);
728 if (v.contains(key: vokey)) {
729 const ProStringList &var_out = project->values(v: vokey);
730 for(int i = 0; i < var_out.size(); ++i) {
731 ProKey v = var_out.at(i).toKey();
732 if(v == QLatin1String("SOURCES"))
733 v = "GENERATED_SOURCES";
734 else if(v == QLatin1String("OBJECTS"))
735 pre_dep = false;
736 ProStringList &list = project->values(v);
737 if(!list.contains(str: out))
738 list.append(t: out);
739 }
740 } else if (config.indexOf(t: "no_link") == -1) {
741 pre_dep = false;
742 ProStringList &list = project->values(v: "OBJECTS");
743 if(!list.contains(str: out))
744 list.append(t: out);
745 } else {
746 ProStringList &list = project->values(v: "UNUSED_SOURCES");
747 if(!list.contains(str: out))
748 list.append(t: out);
749 }
750 if(pre_dep) {
751 ProStringList &list = project->values(v: "PRE_TARGETDEPS");
752 if(!list.contains(str: out))
753 list.append(t: out);
754 }
755 }
756 }
757 }
758 }
759
760 //handle dependencies
761 depHeuristicsCache.clear();
762 if(!noIO()) {
763 // dependency paths
764 ProStringList incDirs = v["DEPENDPATH"] + v["QMAKE_ABSOLUTE_SOURCE_PATH"];
765 if(project->isActiveConfig(config: "depend_includepath"))
766 incDirs += v["INCLUDEPATH"];
767 QList<QMakeLocalFileName> deplist;
768 deplist.reserve(asize: incDirs.size());
769 for (ProStringList::Iterator it = incDirs.begin(); it != incDirs.end(); ++it)
770 deplist.append(t: QMakeLocalFileName((*it).toQString()));
771 QMakeSourceFileInfo::setDependencyPaths(deplist);
772 debug_msg(level: 1, fmt: "Dependency Directories: %s",
773 incDirs.join(sep: QString(" :: ")).toLatin1().constData());
774
775 //add to dependency engine
776 for(x = 0; x < compilers.size(); ++x) {
777 const MakefileGenerator::Compiler &comp = compilers.at(i: x);
778 if(!(comp.flags & Compiler::CompilerNoCheckDeps)) {
779 const ProKey ikey(comp.variable_in);
780 addSourceFiles(v[ikey], seek: QMakeSourceFileInfo::SEEK_DEPS,
781 type: (QMakeSourceFileInfo::SourceFileType)comp.type);
782
783 if (comp.flags & Compiler::CompilerAddInputsAsMakefileDeps) {
784 ProStringList &l = v[ikey];
785 for (int i=0; i < l.size(); ++i) {
786 if(v["QMAKE_INTERNAL_INCLUDED_FILES"].indexOf(t: l.at(i)) == -1)
787 v["QMAKE_INTERNAL_INCLUDED_FILES"].append(t: l.at(i));
788 }
789 }
790 }
791 }
792 }
793
794 processSources(); //remove anything in SOURCES which is included (thus it need not be linked in)
795
796 //all sources and generated sources must be turned into objects at some point (the one builtin compiler)
797 v["OBJECTS"] += createObjectList(sources: v["SOURCES"]) + createObjectList(sources: v["GENERATED_SOURCES"]);
798
799 //Translation files
800 if(!project->isEmpty(v: "TRANSLATIONS")) {
801 ProStringList &trf = project->values(v: "TRANSLATIONS");
802 for (ProStringList::Iterator it = trf.begin(); it != trf.end(); ++it)
803 (*it) = Option::fixPathToTargetOS(in: (*it).toQString());
804 }
805
806 //fix up the target deps
807 static const char * const fixpaths[] = { "PRE_TARGETDEPS", "POST_TARGETDEPS", nullptr };
808 for (int path = 0; fixpaths[path]; path++) {
809 ProStringList &l = v[fixpaths[path]];
810 for (ProStringList::Iterator val_it = l.begin(); val_it != l.end(); ++val_it) {
811 if(!(*val_it).isEmpty())
812 (*val_it) = Option::fixPathToTargetOS(in: (*val_it).toQString(), fix_env: false, canonical: false);
813 }
814 }
815
816 //extra depends
817 if(!project->isEmpty(v: "DEPENDS")) {
818 ProStringList &l = v["DEPENDS"];
819 for (ProStringList::Iterator it = l.begin(); it != l.end(); ++it) {
820 const ProStringList &files = v[ProKey(*it + ".file")] + v[ProKey(*it + ".files")]; //why do I support such evil things?
821 for (ProStringList::ConstIterator file_it = files.begin(); file_it != files.end(); ++file_it) {
822 QStringList &out_deps = findDependencies(file: (*file_it).toQString());
823 const ProStringList &in_deps = v[ProKey(*it + ".depends")]; //even more evilness..
824 for (ProStringList::ConstIterator dep_it = in_deps.begin(); dep_it != in_deps.end(); ++dep_it) {
825 QString dep = (*dep_it).toQString();
826 if (exists(file: dep)) {
827 out_deps.append(t: dep);
828 } else {
829 QString dir, regex = Option::normalizePath(in: dep);
830 if (regex.lastIndexOf(c: '/') != -1) {
831 dir = regex.left(n: regex.lastIndexOf(c: '/') + 1);
832 regex.remove(i: 0, len: dir.size());
833 }
834 QStringList files = QDir(dir).entryList(nameFilters: QStringList(regex));
835 if(files.isEmpty()) {
836 warn_msg(t: WarnLogic, fmt: "Dependency for [%s]: Not found %s", (*file_it).toLatin1().constData(),
837 dep.toLatin1().constData());
838 } else {
839 for(int i = 0; i < files.size(); i++)
840 out_deps.append(t: dir + files[i]);
841 }
842 }
843 }
844 }
845 }
846 }
847
848 // escape qmake command
849 project->values(v: "QMAKE_QMAKE") =
850 ProStringList(escapeFilePath(path: Option::fixPathToTargetOS(in: Option::globals->qmake_abslocation, fix_env: false)));
851}
852
853bool
854MakefileGenerator::processPrlFile(QString &file, bool baseOnly)
855{
856 QString f = fileFixify(file, fix: FileFixifyBackwards);
857 // Explicitly given full .prl name
858 if (!baseOnly && f.endsWith(s: Option::prl_ext))
859 return processPrlFileCore(origFile&: file, origName: QStringView(), fixedFile: f);
860 // Explicitly given or derived (from -l) base name
861 if (processPrlFileCore(origFile&: file, origName: QStringView(), fixedFile: f + Option::prl_ext))
862 return true;
863 if (!baseOnly) {
864 // Explicitly given full library name
865 int off = qMax(a: f.lastIndexOf(c: '/'), b: f.lastIndexOf(c: '\\')) + 1;
866 int ext = QStringView(f).mid(pos: off).lastIndexOf(c: '.');
867 if (ext != -1)
868 return processPrlFileBase(origFile&: file, origName: QStringView(f).mid(pos: off), fixedBase: QStringView{f}.left(n: off + ext), slashOff: off);
869 }
870 return false;
871}
872
873bool
874MakefileGenerator::processPrlFileBase(QString &origFile, QStringView origName,
875 QStringView fixedBase, int /*slashOff*/)
876{
877 return processPrlFileCore(origFile, origName, fixedFile: fixedBase + Option::prl_ext);
878}
879
880bool
881MakefileGenerator::processPrlFileCore(QString &origFile, QStringView origName,
882 const QString &fixedFile)
883{
884 const QString meta_file = QMakeMetaInfo::checkLib(lib: fixedFile);
885 if (meta_file.isEmpty())
886 return false;
887 QMakeMetaInfo libinfo;
888 debug_msg(level: 1, fmt: "Processing PRL file: %s", meta_file.toLatin1().constData());
889 if (!libinfo.readLib(meta_file)) {
890 fprintf(stderr, format: "Error processing meta file %s\n", meta_file.toLatin1().constData());
891 return false;
892 }
893 if (project->isActiveConfig(config: "no_read_prl_qmake")) {
894 debug_msg(level: 2, fmt: "Ignored meta file %s", meta_file.toLatin1().constData());
895 return false;
896 }
897 ProString tgt = libinfo.first(v: "QMAKE_PRL_TARGET");
898 if (tgt.isEmpty()) {
899 fprintf(stderr, format: "Error: %s does not define QMAKE_PRL_TARGET\n",
900 meta_file.toLatin1().constData());
901 return false;
902 }
903 if (!tgt.contains(c: '.') && !libinfo.values(v: "QMAKE_PRL_CONFIG").contains(str: "lib_bundle")) {
904 fprintf(stderr, format: "Error: %s defines QMAKE_PRL_TARGET without extension\n",
905 meta_file.toLatin1().constData());
906 return false;
907 }
908 if (origName.isEmpty()) {
909 // We got a .prl file as input, replace it with an actual library.
910 int off = qMax(a: origFile.lastIndexOf(c: '/'), b: origFile.lastIndexOf(c: '\\')) + 1;
911 debug_msg(level: 1, fmt: " Replacing library reference %s with %s",
912 origFile.mid(position: off).toLatin1().constData(),
913 tgt.toQString().toLatin1().constData());
914 origFile.replace(i: off, len: 1000, after: tgt.toQString());
915 } else if (tgt != origName) {
916 // We got an actual library as input, and found the wrong .prl for it.
917 debug_msg(level: 2, fmt: "Mismatched meta file %s (want %s, got %s)",
918 meta_file.toLatin1().constData(),
919 origName.toLatin1().constData(), tgt.toLatin1().constData());
920 return false;
921 }
922 project->values(v: "QMAKE_CURRENT_PRL_LIBS") = libinfo.values(v: "QMAKE_PRL_LIBS");
923 ProStringList &defs = project->values(v: "DEFINES");
924 const ProStringList &prl_defs = project->values(v: "PRL_EXPORT_DEFINES");
925 for (const ProString &def : libinfo.values(v: "QMAKE_PRL_DEFINES"))
926 if (!defs.contains(str: def) && prl_defs.contains(str: def))
927 defs.append(t: def);
928 QString mf = fileFixify(file: meta_file);
929 if (!project->values(v: "QMAKE_PRL_INTERNAL_FILES").contains(str: mf))
930 project->values(v: "QMAKE_PRL_INTERNAL_FILES").append(t: mf);
931 if (!project->values(v: "QMAKE_INTERNAL_INCLUDED_FILES").contains(str: mf))
932 project->values(v: "QMAKE_INTERNAL_INCLUDED_FILES").append(t: mf);
933 return true;
934}
935
936void
937MakefileGenerator::filterIncludedFiles(const char *var)
938{
939 ProStringList &inputs = project->values(v: var);
940 auto isIncluded = [this](const ProString &input) {
941 return QMakeSourceFileInfo::included(file: input.toQString()) > 0;
942 };
943 inputs.removeIf(pred: isIncluded);
944}
945
946void MakefileGenerator::processSources()
947{
948 if (project->isActiveConfig(config: "compile_included_sources"))
949 return;
950
951 filterIncludedFiles(var: "SOURCES");
952 filterIncludedFiles(var: "GENERATED_SOURCES");
953}
954
955static QString
956qv(const ProString &val)
957{
958 return ' ' + QMakeEvaluator::quoteValue(val);
959}
960
961static QString
962qv(const ProStringList &val)
963{
964 QString ret;
965 for (const ProString &v : val)
966 ret += qv(val: v);
967 return ret;
968}
969
970void
971MakefileGenerator::writePrlFile(QTextStream &t)
972{
973 QString bdir = Option::output_dir;
974 if(bdir.isEmpty())
975 bdir = qmake_getpwd();
976 t << "QMAKE_PRL_BUILD_DIR =" << qv(val: bdir) << Qt::endl;
977
978 t << "QMAKE_PRO_INPUT =" << qv(val: project->projectFile().section(asep: '/', astart: -1)) << Qt::endl;
979
980 if(!project->isEmpty(v: "QMAKE_ABSOLUTE_SOURCE_PATH"))
981 t << "QMAKE_PRL_SOURCE_DIR =" << qv(val: project->first(variableName: "QMAKE_ABSOLUTE_SOURCE_PATH")) << Qt::endl;
982 t << "QMAKE_PRL_TARGET =" << qv(val: project->first(variableName: "LIB_TARGET")) << Qt::endl;
983 if(!project->isEmpty(v: "PRL_EXPORT_DEFINES"))
984 t << "QMAKE_PRL_DEFINES =" << qv(val: project->values(v: "PRL_EXPORT_DEFINES")) << Qt::endl;
985 if(!project->isEmpty(v: "PRL_EXPORT_CFLAGS"))
986 t << "QMAKE_PRL_CFLAGS =" << qv(val: project->values(v: "PRL_EXPORT_CFLAGS")) << Qt::endl;
987 if(!project->isEmpty(v: "PRL_EXPORT_CXXFLAGS"))
988 t << "QMAKE_PRL_CXXFLAGS =" << qv(val: project->values(v: "PRL_EXPORT_CXXFLAGS")) << Qt::endl;
989 if(!project->isEmpty(v: "CONFIG"))
990 t << "QMAKE_PRL_CONFIG =" << qv(val: project->values(v: "CONFIG")) << Qt::endl;
991 if(!project->isEmpty(v: "TARGET_VERSION_EXT"))
992 t << "QMAKE_PRL_VERSION = " << project->first(variableName: "TARGET_VERSION_EXT") << Qt::endl;
993 else if(!project->isEmpty(v: "VERSION"))
994 t << "QMAKE_PRL_VERSION = " << project->first(variableName: "VERSION") << Qt::endl;
995 if(project->isActiveConfig(config: "staticlib") || project->isActiveConfig(config: "explicitlib")) {
996 ProStringList libs;
997 if (!project->isActiveConfig(config: "staticlib"))
998 libs << "LIBS" << "QMAKE_LIBS";
999 else
1000 libs << "LIBS" << "LIBS_PRIVATE" << "QMAKE_LIBS" << "QMAKE_LIBS_PRIVATE";
1001 t << "QMAKE_PRL_LIBS =";
1002 for (ProStringList::Iterator it = libs.begin(); it != libs.end(); ++it)
1003 t << qv(val: project->values(v: (*it).toKey()));
1004 t << Qt::endl;
1005
1006 t << "QMAKE_PRL_LIBS_FOR_CMAKE = ";
1007 QString sep;
1008 for (ProStringList::Iterator it = libs.begin(); it != libs.end(); ++it) {
1009 t << sep << project->values(v: (*it).toKey()).join(sep: ';').replace(c: '\\', after: "\\\\");
1010 sep = ';';
1011 }
1012 t << Qt::endl;
1013 }
1014}
1015
1016bool
1017MakefileGenerator::writeProjectMakefile()
1018{
1019 QTextStream t(&Option::output);
1020
1021 //header
1022 writeHeader(t);
1023
1024 QList<SubTarget*> targets;
1025 {
1026 ProStringList builds = project->values(v: "BUILDS");
1027 targets.reserve(asize: builds.size());
1028 for (ProStringList::Iterator it = builds.begin(); it != builds.end(); ++it) {
1029 SubTarget *st = new SubTarget;
1030 targets.append(t: st);
1031 st->makefile = "$(MAKEFILE)." + (*it);
1032 st->name = (*it).toQString();
1033 const ProKey tkey(*it + ".target");
1034 st->target = (project->isEmpty(v: tkey) ? (*it) : project->first(variableName: tkey)).toQString();
1035 }
1036 }
1037 if(project->isActiveConfig(config: "build_all")) {
1038 t << "first: all\n";
1039 QList<SubTarget*>::Iterator it;
1040
1041 //install
1042 t << "install: ";
1043 for (SubTarget *s : std::as_const(t&: targets))
1044 t << s->target << '-';
1045 t << "install " << Qt::endl;
1046
1047 //uninstall
1048 t << "uninstall: ";
1049 for(it = targets.begin(); it != targets.end(); ++it)
1050 t << (*it)->target << "-uninstall ";
1051 t << Qt::endl;
1052 } else {
1053 t << "first: " << targets.first()->target << Qt::endl
1054 << "install: " << targets.first()->target << "-install\n"
1055 << "uninstall: " << targets.first()->target << "-uninstall\n";
1056 }
1057
1058 writeSubTargets(t, subtargets: targets, flags: SubTargetsNoFlags);
1059 if(!project->isActiveConfig(config: "no_autoqmake")) {
1060 QString mkf = escapeDependencyPath(path: fileFixify(file: Option::output.fileName()));
1061 for(QList<SubTarget*>::Iterator it = targets.begin(); it != targets.end(); ++it)
1062 t << escapeDependencyPath(path: (*it)->makefile) << ": " << mkf << Qt::endl;
1063 }
1064 qDeleteAll(c: targets);
1065 return true;
1066}
1067
1068bool
1069MakefileGenerator::write()
1070{
1071 if(!project)
1072 return false;
1073 writePrlFile();
1074 if(Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE || //write makefile
1075 Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) {
1076 QTextStream t(&Option::output);
1077 if(!writeMakefile(t)) {
1078#if 1
1079 warn_msg(t: WarnLogic, fmt: "Unable to generate output for: %s [TEMPLATE %s]",
1080 Option::output.fileName().toLatin1().constData(),
1081 project->first(variableName: "TEMPLATE").toLatin1().constData());
1082 if(Option::output.exists())
1083 Option::output.remove();
1084#endif
1085 }
1086 }
1087 return true;
1088}
1089
1090QString
1091MakefileGenerator::prlFileName(bool fixify)
1092{
1093 QString ret = project->first(variableName: "PRL_TARGET") + Option::prl_ext;
1094 if(fixify) {
1095 if(!project->isEmpty(v: "DESTDIR"))
1096 ret.prepend(s: project->first(variableName: "DESTDIR").toQString());
1097 ret = fileFixify(file: ret, fix: FileFixifyBackwards);
1098 }
1099 return ret;
1100}
1101
1102void
1103MakefileGenerator::writePrlFile()
1104{
1105 if((Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE ||
1106 Option::qmake_mode == Option::QMAKE_GENERATE_PRL)
1107 && project->values(v: "QMAKE_FAILED_REQUIREMENTS").isEmpty()
1108 && project->isActiveConfig(config: "create_prl")
1109 && (project->first(variableName: "TEMPLATE") == "lib"
1110 || project->first(variableName: "TEMPLATE") == "vclib"
1111 || project->first(variableName: "TEMPLATE") == "aux")
1112 && (!project->isActiveConfig(config: "plugin") || project->isActiveConfig(config: "static"))) { //write prl file
1113 QString local_prl = prlFileName();
1114 QString prl = fileFixify(file: local_prl);
1115 mkdir(in_path: fileInfo(file: local_prl).path());
1116 QFile ft(local_prl);
1117 if(ft.open(flags: QIODevice::WriteOnly)) {
1118 project->values(v: "ALL_DEPS").append(t: prl);
1119 project->values(v: "QMAKE_INTERNAL_PRL_FILE").append(t: prl);
1120 project->values(v: "QMAKE_DISTCLEAN").append(t: prl);
1121 QTextStream t(&ft);
1122 writePrlFile(t);
1123 }
1124 }
1125}
1126
1127void
1128MakefileGenerator::writeObj(QTextStream &t, const char *src)
1129{
1130 const ProStringList &srcl = project->values(v: src);
1131 const ProStringList objl = createObjectList(sources: srcl);
1132
1133 ProStringList::ConstIterator oit = objl.begin();
1134 ProStringList::ConstIterator sit = srcl.begin();
1135 QLatin1String stringSrc("$src");
1136 QLatin1String stringObj("$obj");
1137 for(;sit != srcl.end() && oit != objl.end(); ++oit, ++sit) {
1138 if((*sit).isEmpty())
1139 continue;
1140
1141 QString srcf = (*sit).toQString();
1142 QString dstf = (*oit).toQString();
1143 t << escapeDependencyPath(path: dstf) << ": " << escapeDependencyPath(path: srcf)
1144 << " " << finalizeDependencyPaths(paths: findDependencies(file: srcf)).join(sep: " \\\n\t\t");
1145
1146 ProKey comp;
1147 for (const ProString &compiler : project->values(v: "QMAKE_BUILTIN_COMPILERS")) {
1148 // Unfortunately we were not consistent about the C++ naming
1149 ProString extensionSuffix = compiler;
1150 if (extensionSuffix == "CXX")
1151 extensionSuffix = ProString("CPP");
1152
1153 // Nor the C naming
1154 ProString compilerSuffix = compiler;
1155 if (compilerSuffix == "C")
1156 compilerSuffix = ProString("CC");
1157
1158 for (const ProString &extension : project->values(v: ProKey("QMAKE_EXT_" + extensionSuffix))) {
1159 if ((*sit).endsWith(sub: extension)) {
1160 comp = ProKey("QMAKE_RUN_" + compilerSuffix);
1161 break;
1162 }
1163 }
1164
1165 if (!comp.isNull())
1166 break;
1167 }
1168
1169 if (comp.isEmpty())
1170 comp = "QMAKE_RUN_CC";
1171 if (!project->isEmpty(v: comp)) {
1172 QString p = var(var: comp);
1173 p.replace(before: stringSrc, after: escapeFilePath(path: srcf));
1174 p.replace(before: stringObj, after: escapeFilePath(path: dstf));
1175 t << "\n\t" << p;
1176 }
1177 t << Qt::endl << Qt::endl;
1178 }
1179}
1180
1181QString
1182MakefileGenerator::filePrefixRoot(const QString &root, const QString &path)
1183{
1184 QString ret(path);
1185 if(path.size() > 2 && path[1] == ':') //c:\foo
1186 ret.insert(i: 2, s: root);
1187 else
1188 ret.prepend(s: root);
1189 while (ret.endsWith(c: '\\'))
1190 ret.chop(n: 1);
1191 return ret;
1192}
1193
1194void
1195MakefileGenerator::writeInstalls(QTextStream &t, bool noBuild)
1196{
1197 QString rm_dir_contents("-$(DEL_FILE)");
1198 if (!isWindowsShell()) //ick
1199 rm_dir_contents = "-$(DEL_FILE) -r";
1200
1201 QString all_installs, all_uninstalls;
1202 QSet<QString> made_dirs, removed_dirs;
1203 const ProStringList &l = project->values(v: "INSTALLS");
1204 for (ProStringList::ConstIterator it = l.begin(); it != l.end(); ++it) {
1205 const ProKey pvar(*it + ".path");
1206 const ProStringList &installConfigValues = project->values(v: ProKey(*it + ".CONFIG"));
1207 if (installConfigValues.indexOf(t: "no_path") == -1 &&
1208 installConfigValues.indexOf(t: "dummy_install") == -1 &&
1209 project->values(v: pvar).isEmpty()) {
1210 warn_msg(t: WarnLogic, fmt: "%s is not defined: install target not created\n", pvar.toLatin1().constData());
1211 continue;
1212 }
1213
1214 bool do_default = true;
1215 const QString root = installRoot();
1216 QString dst;
1217 if (installConfigValues.indexOf(t: "no_path") == -1 &&
1218 installConfigValues.indexOf(t: "dummy_install") == -1) {
1219 dst = fileFixify(file: project->first(variableName: pvar).toQString(), fix: FileFixifyAbsolute, canon: false);
1220 if(!dst.endsWith(s: Option::dir_sep))
1221 dst += Option::dir_sep;
1222 }
1223
1224 QStringList tmp, inst, uninst;
1225 //other
1226 ProStringList tmp2 = project->values(v: ProKey(*it + ".extra"));
1227 if (tmp2.isEmpty())
1228 tmp2 = project->values(v: ProKey(*it + ".commands")); //to allow compatible name
1229 if (!tmp2.isEmpty()) {
1230 do_default = false;
1231 inst << tmp2.join(sep: ' ');
1232 }
1233 //masks
1234 tmp2 = findFilesInVPATH(l: project->values(v: ProKey(*it + ".files")), flags: VPATH_NoFixify);
1235 tmp = fileFixify(files: tmp2.toQStringList(), fix: FileFixifyAbsolute);
1236 if(!tmp.isEmpty()) {
1237 do_default = false;
1238 QString base_path = project->first(variableName: ProKey(*it + ".base")).toQString();
1239 if (!base_path.isEmpty()) {
1240 base_path = Option::fixPathToTargetOS(in: base_path, fix_env: false, canonical: true);
1241 if (!base_path.endsWith(s: Option::dir_sep))
1242 base_path += Option::dir_sep;
1243 }
1244 for(QStringList::Iterator wild_it = tmp.begin(); wild_it != tmp.end(); ++wild_it) {
1245 QString wild = Option::fixPathToTargetOS(in: (*wild_it), fix_env: false, canonical: false);
1246 QString dirstr = qmake_getpwd(), filestr = wild;
1247 int slsh = filestr.lastIndexOf(s: Option::dir_sep);
1248 if(slsh != -1) {
1249 dirstr = filestr.left(n: slsh+1);
1250 filestr.remove(i: 0, len: slsh+1);
1251 }
1252 if(!dirstr.endsWith(s: Option::dir_sep))
1253 dirstr += Option::dir_sep;
1254 QString dst_dir = dst;
1255 if (!base_path.isEmpty()) {
1256 if (!dirstr.startsWith(s: base_path)) {
1257 warn_msg(t: WarnLogic, fmt: "File %s in install rule %s does not start with base %s",
1258 qPrintable(wild), qPrintable((*it).toQString()),
1259 qPrintable(base_path));
1260 } else {
1261 QString dir_sfx = dirstr.mid(position: base_path.size());
1262 dst_dir += dir_sfx;
1263 if (!dir_sfx.isEmpty() && !made_dirs.contains(value: dir_sfx)) {
1264 made_dirs.insert(value: dir_sfx);
1265 QString tmp_dst = fileFixify(file: dst_dir, fix: FileFixifyAbsolute, canon: false);
1266 tmp_dst.chop(n: 1);
1267 inst << mkdir_p_asstring(dir: filePrefixRoot(root, path: tmp_dst));
1268 for (int i = dst.size(); i < dst_dir.size(); i++) {
1269 if (dst_dir.at(i) == Option::dir_sep) {
1270 QString subd = dst_dir.left(n: i);
1271 if (!removed_dirs.contains(value: subd)) {
1272 removed_dirs.insert(value: subd);
1273 tmp_dst = fileFixify(file: subd, fix: FileFixifyAbsolute, canon: false);
1274 uninst << "-$(DEL_DIR) "
1275 + escapeFilePath(path: filePrefixRoot(root, path: tmp_dst));
1276 }
1277 }
1278 }
1279 }
1280 }
1281 }
1282 bool is_target = (wild == fileFixify(file: var(var: "TARGET"), fix: FileFixifyAbsolute));
1283 const bool noStrip = installConfigValues.contains(str: "nostrip");
1284 if(is_target || exists(file: wild)) { //real file or target
1285 QFileInfo fi(fileInfo(file: wild));
1286 QString dst_file = filePrefixRoot(root, path: dst_dir);
1287 if (!dst_file.endsWith(s: Option::dir_sep))
1288 dst_file += Option::dir_sep;
1289 dst_file += fi.fileName();
1290 QString cmd;
1291 if (is_target || (!fi.isDir() && fi.isExecutable()))
1292 cmd = QLatin1String("$(QINSTALL_PROGRAM)");
1293 else
1294 cmd = QLatin1String("$(QINSTALL)");
1295 cmd += " " + escapeFilePath(path: wild) + " " + escapeFilePath(path: dst_file);
1296
1297 QString sedArgs = createSedArgs(replace_rule: ProKey("QMAKE_INSTALL_REPLACE"), file_type: fi.fileName());
1298 if (!sedArgs.isEmpty())
1299 inst << "$(SED) " + sedArgs + ' ' + escapeFilePath(path: wild) + " > "
1300 + escapeFilePath(path: dst_file);
1301 else
1302 inst << cmd;
1303
1304 if (!noStrip && !project->isActiveConfig(config: "debug_info") && !project->isActiveConfig(config: "nostrip") &&
1305 !fi.isDir() && fi.isExecutable() && !project->isEmpty(v: "QMAKE_STRIP"))
1306 inst << QString("-") + var(var: "QMAKE_STRIP") + " " +
1307 escapeFilePath(path: filePrefixRoot(root, path: fileFixify(file: dst_dir + filestr, fix: FileFixifyAbsolute, canon: false)));
1308 uninst.append(t: rm_dir_contents + " " + escapeFilePath(path: filePrefixRoot(root, path: fileFixify(file: dst_dir + filestr, fix: FileFixifyAbsolute, canon: false))));
1309 continue;
1310 }
1311 QString local_dirstr = Option::normalizePath(in: dirstr);
1312 QStringList files = QDir(local_dirstr).entryList(nameFilters: QStringList(filestr),
1313 filters: QDir::NoDotAndDotDot | QDir::AllEntries);
1314 if (installConfigValues.contains(str: "no_check_exist") && files.isEmpty()) {
1315 QString dst_file = filePrefixRoot(root, path: dst_dir);
1316 if (!dst_file.endsWith(s: Option::dir_sep))
1317 dst_file += Option::dir_sep;
1318 dst_file += filestr;
1319 QString cmd;
1320 if (installConfigValues.contains(str: "executable"))
1321 cmd = QLatin1String("$(QINSTALL_PROGRAM)");
1322 else
1323 cmd = QLatin1String("$(QINSTALL)");
1324 cmd += " " + escapeFilePath(path: wild) + " " + escapeFilePath(path: dst_file);
1325 inst << cmd;
1326 uninst.append(t: rm_dir_contents + " " + escapeFilePath(path: filePrefixRoot(root, path: fileFixify(file: dst_dir + filestr, fix: FileFixifyAbsolute, canon: false))));
1327 }
1328 for(int x = 0; x < files.size(); x++) {
1329 QString file = files[x];
1330 uninst.append(t: rm_dir_contents + " " + escapeFilePath(path: filePrefixRoot(root, path: fileFixify(file: dst_dir + file, fix: FileFixifyAbsolute, canon: false))));
1331 QFileInfo fi(fileInfo(file: dirstr + file));
1332 QString dst_file = filePrefixRoot(root, path: fileFixify(file: dst_dir, fix: FileFixifyAbsolute, canon: false));
1333 if (!dst_file.endsWith(s: Option::dir_sep))
1334 dst_file += Option::dir_sep;
1335 dst_file += fi.fileName();
1336 QString cmd = QLatin1String("$(QINSTALL) ") +
1337 escapeFilePath(path: dirstr + file) + " " + escapeFilePath(path: dst_file);
1338 inst << cmd;
1339 if (!noStrip && !project->isActiveConfig(config: "debug_info") && !project->isActiveConfig(config: "nostrip") &&
1340 !fi.isDir() && fi.isExecutable() && !project->isEmpty(v: "QMAKE_STRIP"))
1341 inst << QString("-") + var(var: "QMAKE_STRIP") + " " +
1342 escapeFilePath(path: filePrefixRoot(root, path: fileFixify(file: dst_dir + file, fix: FileFixifyAbsolute, canon: false)));
1343 }
1344 }
1345 }
1346 QString target;
1347 //default?
1348 if (do_default)
1349 target = defaultInstall((*it).toQString());
1350 else
1351 target = inst.join(sep: "\n\t");
1352 QString puninst = project->values(v: ProKey(*it + ".uninstall")).join(sep: ' ');
1353 if (!puninst.isEmpty())
1354 uninst << puninst;
1355
1356 if (!target.isEmpty() || installConfigValues.indexOf(t: "dummy_install") != -1) {
1357 if (noBuild || installConfigValues.indexOf(t: "no_build") != -1)
1358 t << "install_" << (*it) << ":";
1359 else if(project->isActiveConfig(config: "build_all"))
1360 t << "install_" << (*it) << ": all";
1361 else
1362 t << "install_" << (*it) << ": first";
1363 const ProStringList &deps = project->values(v: ProKey(*it + ".depends"));
1364 if(!deps.isEmpty()) {
1365 for (ProStringList::ConstIterator dep_it = deps.begin(); dep_it != deps.end(); ++dep_it) {
1366 QString targ = var(var: ProKey(*dep_it + ".target"));
1367 if(targ.isEmpty())
1368 targ = (*dep_it).toQString();
1369 t << " " << escapeDependencyPath(path: targ);
1370 }
1371 }
1372 t << " FORCE\n\t";
1373 const ProStringList &dirs = project->values(v: pvar);
1374 for (ProStringList::ConstIterator pit = dirs.begin(); pit != dirs.end(); ++pit) {
1375 QString tmp_dst = fileFixify(file: (*pit).toQString(), fix: FileFixifyAbsolute, canon: false);
1376 t << mkdir_p_asstring(dir: filePrefixRoot(root, path: tmp_dst)) << "\n\t";
1377 }
1378 t << target << Qt::endl << Qt::endl;
1379 if(!uninst.isEmpty()) {
1380 t << "uninstall_" << (*it) << ": FORCE";
1381 for (int i = uninst.size(); --i >= 0; )
1382 t << "\n\t" << uninst.at(i);
1383 t << "\n\t-$(DEL_DIR) " << escapeFilePath(path: filePrefixRoot(root, path: dst)) << " \n\n";
1384 }
1385 t << Qt::endl;
1386
1387 if (installConfigValues.indexOf(t: "no_default_install") == -1) {
1388 all_installs += QString("install_") + (*it) + " ";
1389 if(!uninst.isEmpty())
1390 all_uninstalls += "uninstall_" + (*it) + " ";
1391 }
1392 } else {
1393 debug_msg(level: 1, fmt: "no definition for install %s: install target not created",(*it).toLatin1().constData());
1394 }
1395 }
1396 t << "install:" << depVar(var: "INSTALLDEPS") << ' ' << all_installs
1397 << " FORCE\n\nuninstall: " << all_uninstalls << depVar(var: "UNINSTALLDEPS")
1398 << " FORCE\n\n";
1399}
1400
1401QString
1402MakefileGenerator::var(const ProKey &var) const
1403{
1404 return val(varList: project->values(v: var));
1405}
1406
1407QString
1408MakefileGenerator::fileVar(const ProKey &var) const
1409{
1410 return val(varList: escapeFilePaths(paths: project->values(v: var)));
1411}
1412
1413QString
1414MakefileGenerator::fileVarList(const ProKey &var) const
1415{
1416 return valList(varList: escapeFilePaths(paths: project->values(v: var)));
1417}
1418
1419QString
1420MakefileGenerator::depVar(const ProKey &var) const
1421{
1422 return val(varList: escapeDependencyPaths(paths: project->values(v: var)));
1423}
1424
1425QString
1426MakefileGenerator::val(const ProStringList &varList) const
1427{
1428 return valGlue(varList, before: "", glue: " ", after: "");
1429}
1430
1431QString
1432MakefileGenerator::val(const QStringList &varList) const
1433{
1434 return valGlue(varList, before: "", glue: " ", after: "");
1435}
1436
1437QString
1438MakefileGenerator::varGlue(const ProKey &var, const QString &before, const QString &glue, const QString &after) const
1439{
1440 return valGlue(varList: project->values(v: var), before, glue, after);
1441}
1442
1443QString
1444MakefileGenerator::fileVarGlue(const ProKey &var, const QString &before, const QString &glue, const QString &after) const
1445{
1446 return valGlue(varList: escapeFilePaths(paths: project->values(v: var)), before, glue, after);
1447}
1448
1449QString
1450MakefileGenerator::fixFileVarGlue(const ProKey &var, const QString &before, const QString &glue, const QString &after) const
1451{
1452 ProStringList varList;
1453 const auto values = project->values(v: var);
1454 varList.reserve(asize: values.size());
1455 for (const ProString &val : values)
1456 varList << escapeFilePath(path: Option::fixPathToTargetOS(in: val.toQString()));
1457 return valGlue(varList, before, glue, after);
1458}
1459
1460QString
1461MakefileGenerator::valGlue(const ProStringList &varList, const QString &before, const QString &glue, const QString &after) const
1462{
1463 QString ret;
1464 for (ProStringList::ConstIterator it = varList.begin(); it != varList.end(); ++it) {
1465 if (!(*it).isEmpty()) {
1466 if (!ret.isEmpty())
1467 ret += glue;
1468 ret += (*it).toQString();
1469 }
1470 }
1471 return ret.isEmpty() ? QString("") : before + ret + after;
1472}
1473
1474QString
1475MakefileGenerator::valGlue(const QStringList &varList, const QString &before, const QString &glue, const QString &after) const
1476{
1477 QString ret;
1478 for(QStringList::ConstIterator it = varList.begin(); it != varList.end(); ++it) {
1479 if(!(*it).isEmpty()) {
1480 if(!ret.isEmpty())
1481 ret += glue;
1482 ret += (*it);
1483 }
1484 }
1485 return ret.isEmpty() ? QString("") : before + ret + after;
1486}
1487
1488
1489QString
1490MakefileGenerator::varList(const ProKey &var) const
1491{
1492 return valList(varList: project->values(v: var));
1493}
1494
1495QString
1496MakefileGenerator::valList(const ProStringList &varList) const
1497{
1498 return valGlue(varList, before: "", glue: " \\\n\t\t", after: "");
1499}
1500
1501QString
1502MakefileGenerator::valList(const QStringList &varList) const
1503{
1504 return valGlue(varList, before: "", glue: " \\\n\t\t", after: "");
1505}
1506
1507ProStringList
1508MakefileGenerator::createObjectList(const ProStringList &sources)
1509{
1510 ProStringList ret;
1511 QString objdir;
1512 if(!project->values(v: "OBJECTS_DIR").isEmpty())
1513 objdir = project->first(variableName: "OBJECTS_DIR").toQString();
1514 for (ProStringList::ConstIterator it = sources.begin(); it != sources.end(); ++it) {
1515 QString sfn = (*it).toQString();
1516 QFileInfo fi(fileInfo(file: Option::normalizePath(in: sfn)));
1517 QString dir;
1518 if (project->isActiveConfig(config: "object_parallel_to_source")) {
1519 // The source paths are relative to the output dir, but we need source-relative paths
1520 QString sourceRelativePath = fileFixify(file: sfn, fix: FileFixifyBackwards);
1521
1522 if (sourceRelativePath.startsWith(s: ".." + Option::dir_sep))
1523 sourceRelativePath = fileFixify(file: sourceRelativePath, fix: FileFixifyAbsolute);
1524
1525 if (QDir::isAbsolutePath(path: sourceRelativePath))
1526 sourceRelativePath.remove(i: 0, len: sourceRelativePath.indexOf(s: Option::dir_sep) + 1);
1527
1528 dir = objdir; // We still respect OBJECTS_DIR
1529
1530 int lastDirSepPosition = sourceRelativePath.lastIndexOf(s: Option::dir_sep);
1531 if (lastDirSepPosition != -1)
1532 dir += QStringView{sourceRelativePath}.left(n: lastDirSepPosition + 1);
1533
1534 if (!noIO()) {
1535 // Ensure that the final output directory of each object exists
1536 QString outRelativePath = fileFixify(file: dir, fix: FileFixifyBackwards);
1537 if (!outRelativePath.isEmpty() && !mkdir(in_path: outRelativePath))
1538 warn_msg(t: WarnLogic, fmt: "Cannot create directory '%s'", outRelativePath.toLatin1().constData());
1539 }
1540 } else {
1541 dir = objdir;
1542 }
1543 ret.append(t: dir + fi.completeBaseName() + Option::obj_ext);
1544 }
1545 return ret;
1546}
1547
1548ReplaceExtraCompilerCacheKey::ReplaceExtraCompilerCacheKey(
1549 const QString &v, const QStringList &i, const QStringList &o, MakefileGenerator::ReplaceFor s)
1550{
1551 static QString doubleColon = QLatin1String("::");
1552
1553 hash = 0;
1554 pwd = qmake_getpwd();
1555 var = v;
1556 {
1557 QStringList il = i;
1558 il.sort();
1559 in = il.join(sep: doubleColon);
1560 }
1561 {
1562 QStringList ol = o;
1563 ol.sort();
1564 out = ol.join(sep: doubleColon);
1565 }
1566 forShell = s;
1567}
1568
1569bool ReplaceExtraCompilerCacheKey::operator==(const ReplaceExtraCompilerCacheKey &f) const
1570{
1571 return (hashCode() == f.hashCode() &&
1572 f.forShell == forShell &&
1573 f.in == in &&
1574 f.out == out &&
1575 f.var == var &&
1576 f.pwd == pwd);
1577}
1578
1579
1580QString
1581MakefileGenerator::replaceExtraCompilerVariables(
1582 const QString &orig_var, const QStringList &in, const QStringList &out, ReplaceFor forShell)
1583{
1584 //lazy cache
1585 ReplaceExtraCompilerCacheKey cacheKey(orig_var, in, out, forShell);
1586 QString cacheVal = extraCompilerVariablesCache.value(key: cacheKey);
1587 if(!cacheVal.isNull())
1588 return cacheVal;
1589
1590 //do the work
1591 QString ret = orig_var;
1592 QRegularExpression reg_var("\\$\\{.*\\}", QRegularExpression::InvertedGreedinessOption);
1593 QRegularExpressionMatch match;
1594 for (int rep = 0; (match = reg_var.match(subject: ret, offset: rep)).hasMatch(); ) {
1595 rep = match.capturedStart();
1596 QStringList val;
1597 const ProString var(ret.mid(position: rep + 2, n: match.capturedLength() - 3));
1598 bool filePath = false;
1599 if(val.isEmpty() && var.startsWith(sub: QLatin1String("QMAKE_VAR_"))) {
1600 const ProKey varname = var.mid(off: 10).toKey();
1601 val += project->values(v: varname).toQStringList();
1602 }
1603 if(val.isEmpty() && var.startsWith(sub: QLatin1String("QMAKE_VAR_FIRST_"))) {
1604 const ProKey varname = var.mid(off: 16).toKey();
1605 val += project->first(variableName: varname).toQString();
1606 }
1607
1608 if(val.isEmpty() && !in.isEmpty()) {
1609 if(var.startsWith(sub: QLatin1String("QMAKE_FUNC_FILE_IN_"))) {
1610 filePath = true;
1611 const ProKey funcname = var.mid(off: 19).toKey();
1612 val += project->expand(func: funcname, args: QList<ProStringList>() << ProStringList(in));
1613 } else if(var == QLatin1String("QMAKE_FILE_BASE") || var == QLatin1String("QMAKE_FILE_IN_BASE")) {
1614 filePath = true;
1615 for(int i = 0; i < in.size(); ++i) {
1616 QFileInfo fi(fileInfo(file: Option::normalizePath(in: in.at(i))));
1617 QString base = fi.completeBaseName();
1618 if(base.isNull())
1619 base = fi.fileName();
1620 val += base;
1621 }
1622 } else if (var == QLatin1String("QMAKE_FILE_EXT") || var == QLatin1String("QMAKE_FILE_IN_EXT")) {
1623 filePath = true;
1624 for(int i = 0; i < in.size(); ++i) {
1625 QFileInfo fi(fileInfo(file: Option::normalizePath(in: in.at(i))));
1626 QString ext;
1627 // Ensure complementarity with QMAKE_FILE_BASE
1628 int baseLen = fi.completeBaseName().size();
1629 if(baseLen == 0)
1630 ext = fi.fileName();
1631 else
1632 ext = fi.fileName().remove(i: 0, len: baseLen);
1633 val += ext;
1634 }
1635 } else if (var == QLatin1String("QMAKE_FILE_IN_NAME")) {
1636 filePath = true;
1637 for (int i = 0; i < in.size(); ++i)
1638 val += fileInfo(file: Option::normalizePath(in: in.at(i))).fileName();
1639 } else if(var == QLatin1String("QMAKE_FILE_PATH") || var == QLatin1String("QMAKE_FILE_IN_PATH")) {
1640 filePath = true;
1641 for(int i = 0; i < in.size(); ++i)
1642 val += fileInfo(file: Option::normalizePath(in: in.at(i))).path();
1643 } else if(var == QLatin1String("QMAKE_FILE_NAME") || var == QLatin1String("QMAKE_FILE_IN")) {
1644 filePath = true;
1645 for(int i = 0; i < in.size(); ++i)
1646 val += fileInfo(file: Option::normalizePath(in: in.at(i))).filePath();
1647
1648 }
1649 }
1650 if(val.isEmpty() && !out.isEmpty()) {
1651 if(var.startsWith(sub: QLatin1String("QMAKE_FUNC_FILE_OUT_"))) {
1652 filePath = true;
1653 const ProKey funcname = var.mid(off: 20).toKey();
1654 val += project->expand(func: funcname, args: QList<ProStringList>() << ProStringList(out));
1655 } else if (var == QLatin1String("QMAKE_FILE_OUT_PATH")) {
1656 filePath = true;
1657 for (int i = 0; i < out.size(); ++i)
1658 val += fileInfo(file: Option::normalizePath(in: out.at(i))).path();
1659 } else if(var == QLatin1String("QMAKE_FILE_OUT")) {
1660 filePath = true;
1661 for(int i = 0; i < out.size(); ++i)
1662 val += fileInfo(file: Option::normalizePath(in: out.at(i))).filePath();
1663 } else if(var == QLatin1String("QMAKE_FILE_OUT_BASE")) {
1664 filePath = true;
1665 for(int i = 0; i < out.size(); ++i) {
1666 QFileInfo fi(fileInfo(file: Option::normalizePath(in: out.at(i))));
1667 QString base = fi.completeBaseName();
1668 if(base.isNull())
1669 base = fi.fileName();
1670 val += base;
1671 }
1672 }
1673 }
1674 if(val.isEmpty() && var.startsWith(sub: QLatin1String("QMAKE_FUNC_"))) {
1675 const ProKey funcname = var.mid(off: 11).toKey();
1676 val += project->expand(func: funcname, args: QList<ProStringList>() << ProStringList(in) << ProStringList(out));
1677 }
1678
1679 if(!val.isEmpty()) {
1680 QString fullVal;
1681 if (filePath && forShell != NoShell) {
1682 for(int i = 0; i < val.size(); ++i) {
1683 if(!fullVal.isEmpty())
1684 fullVal += " ";
1685 if (forShell == LocalShell)
1686 fullVal += IoUtils::shellQuote(arg: Option::fixPathToLocalOS(in: val.at(i), fix_env: false));
1687 else
1688 fullVal += escapeFilePath(path: Option::fixPathToTargetOS(in: val.at(i), fix_env: false));
1689 }
1690 } else {
1691 fullVal = val.join(sep: ' ');
1692 }
1693 ret.replace(i: match.capturedStart(), len: match.capturedLength(), after: fullVal);
1694 rep += fullVal.size();
1695 } else {
1696 rep = match.capturedEnd();
1697 }
1698 }
1699
1700 //cache the value
1701 extraCompilerVariablesCache.insert(key: cacheKey, value: ret);
1702 return ret;
1703}
1704
1705bool
1706MakefileGenerator::verifyExtraCompiler(const ProString &comp, const QString &file_unfixed)
1707{
1708 if(noIO())
1709 return false;
1710 const QString file = Option::normalizePath(in: file_unfixed);
1711
1712 const ProStringList &config = project->values(v: ProKey(comp + ".CONFIG"));
1713 if (config.indexOf(t: "moc_verify") != -1) {
1714 if(!file.isNull()) {
1715 QMakeSourceFileInfo::addSourceFile(file, seek: QMakeSourceFileInfo::SEEK_MOCS);
1716 if(!mocable(file)) {
1717 return false;
1718 } else {
1719 project->values(v: "MOCABLES").append(t: file);
1720 }
1721 }
1722 } else if (config.indexOf(t: "function_verify") != -1) {
1723 ProString tmp_out = project->first(variableName: ProKey(comp + ".output"));
1724 if(tmp_out.isEmpty())
1725 return false;
1726 ProStringList verify_function = project->values(v: ProKey(comp + ".verify_function"));
1727 if(verify_function.isEmpty())
1728 return false;
1729
1730 for(int i = 0; i < verify_function.size(); ++i) {
1731 bool invert = false;
1732 ProString verify = verify_function.at(i);
1733 if(verify.at(i: 0) == QLatin1Char('!')) {
1734 invert = true;
1735 verify = verify.mid(off: 1);
1736 }
1737
1738 if (config.indexOf(t: "combine") != -1) {
1739 bool pass = project->test(func: verify.toKey(), args: QList<ProStringList>() << ProStringList(tmp_out) << ProStringList(file));
1740 if(invert)
1741 pass = !pass;
1742 if(!pass)
1743 return false;
1744 } else {
1745 const ProStringList &tmp = project->values(v: ProKey(comp + ".input"));
1746 for (ProStringList::ConstIterator it = tmp.begin(); it != tmp.end(); ++it) {
1747 const ProStringList &inputs = project->values(v: (*it).toKey());
1748 for (ProStringList::ConstIterator input = inputs.begin(); input != inputs.end(); ++input) {
1749 if((*input).isEmpty())
1750 continue;
1751 QString inpf = (*input).toQString();
1752 QString in = fileFixify(file: inpf);
1753 if(in == file) {
1754 bool pass = project->test(func: verify.toKey(),
1755 args: QList<ProStringList>() << ProStringList(replaceExtraCompilerVariables(val: tmp_out.toQString(), in: inpf, out: QString(), forShell: NoShell)) <<
1756 ProStringList(file));
1757 if(invert)
1758 pass = !pass;
1759 if(!pass)
1760 return false;
1761 break;
1762 }
1763 }
1764 }
1765 }
1766 }
1767 } else if (config.indexOf(t: "verify") != -1) {
1768 QString tmp_out = project->first(variableName: ProKey(comp + ".output")).toQString();
1769 if(tmp_out.isEmpty())
1770 return false;
1771 const QString tmp_cmd = project->values(v: ProKey(comp + ".commands")).join(sep: ' ');
1772 if (config.indexOf(t: "combine") != -1) {
1773 QString cmd = replaceExtraCompilerVariables(val: tmp_cmd, in: QString(), out: tmp_out, forShell: LocalShell);
1774 if(system(command: cmd.toLatin1().constData()))
1775 return false;
1776 } else {
1777 const ProStringList &tmp = project->values(v: ProKey(comp + ".input"));
1778 for (ProStringList::ConstIterator it = tmp.begin(); it != tmp.end(); ++it) {
1779 const ProStringList &inputs = project->values(v: (*it).toKey());
1780 for (ProStringList::ConstIterator input = inputs.begin(); input != inputs.end(); ++input) {
1781 if((*input).isEmpty())
1782 continue;
1783 QString inpf = (*input).toQString();
1784 QString in = fileFixify(file: inpf);
1785 if(in == file) {
1786 QString out = replaceExtraCompilerVariables(val: tmp_out, in: inpf, out: QString(), forShell: NoShell);
1787 QString cmd = replaceExtraCompilerVariables(val: tmp_cmd, in, out, forShell: LocalShell);
1788 if(system(command: cmd.toLatin1().constData()))
1789 return false;
1790 break;
1791 }
1792 }
1793 }
1794 }
1795 }
1796 return true;
1797}
1798
1799void
1800MakefileGenerator::writeExtraTargets(QTextStream &t)
1801{
1802 const ProStringList &qut = project->values(v: "QMAKE_EXTRA_TARGETS");
1803 for (ProStringList::ConstIterator it = qut.begin(); it != qut.end(); ++it) {
1804 QString targ = var(var: ProKey(*it + ".target")),
1805 cmd = var(var: ProKey(*it + ".commands")), deps;
1806 if(targ.isEmpty())
1807 targ = (*it).toQString();
1808 const ProStringList &deplist = project->values(v: ProKey(*it + ".depends"));
1809 for (ProStringList::ConstIterator dep_it = deplist.begin(); dep_it != deplist.end(); ++dep_it) {
1810 QString dep = var(var: ProKey(*dep_it + ".target"));
1811 if(dep.isEmpty())
1812 dep = (*dep_it).toQString();
1813 deps += " " + escapeDependencyPath(path: dep);
1814 }
1815 const ProStringList &config = project->values(v: ProKey(*it + ".CONFIG"));
1816 if (config.indexOf(t: "fix_target") != -1)
1817 targ = fileFixify(file: targ, fix: FileFixifyFromOutdir);
1818 if (config.indexOf(t: "phony") != -1)
1819 deps += QLatin1String(" FORCE");
1820 t << escapeDependencyPath(path: targ) << ":" << deps;
1821 if(!cmd.isEmpty())
1822 t << "\n\t" << cmd;
1823 t << Qt::endl << Qt::endl;
1824 }
1825}
1826
1827static QStringList splitDeps(const QString &indeps, bool lineMode)
1828{
1829 if (!lineMode)
1830 return indeps.simplified().split(sep: ' ');
1831 QStringList deps = indeps.split(sep: '\n', behavior: Qt::SkipEmptyParts);
1832#ifdef Q_OS_WIN
1833 for (auto &dep : deps) {
1834 if (dep.endsWith(QLatin1Char('\r')))
1835 dep.chop(1);
1836 }
1837#endif
1838 return deps;
1839}
1840
1841QString MakefileGenerator::resolveDependency(const QDir &outDir, const QString &file)
1842{
1843 const QList<QMakeLocalFileName> &depdirs = QMakeSourceFileInfo::dependencyPaths();
1844 for (const auto &depdir : depdirs) {
1845 const QString &local = depdir.local();
1846 QString lf = outDir.absoluteFilePath(fileName: local + '/' + file);
1847 if (exists(file: lf))
1848 return lf;
1849
1850 if (resolveDependenciesInFrameworks) {
1851 // Given a file like "QtWidgets/QWidget", try to resolve it
1852 // as framework header "QtWidgets.framework/Headers/QWidget".
1853 int cut = file.indexOf(c: '/');
1854 if (cut < 0 || cut + 1 >= file.size())
1855 continue;
1856 QStringView framework = QStringView{file}.left(n: cut);
1857 QStringView include = QStringView(file).mid(pos: cut + 1);
1858 if (local.endsWith(s: '/' + framework + ".framework/Headers")) {
1859 lf = outDir.absoluteFilePath(fileName: local + '/' + include);
1860 if (exists(file: lf))
1861 return lf;
1862 }
1863 }
1864 }
1865 return {};
1866}
1867
1868void MakefileGenerator::callExtraCompilerDependCommand(const ProString &extraCompiler,
1869 const QString &tmp_dep_cmd,
1870 const QString &inpf,
1871 const QString &tmp_out,
1872 bool dep_lines,
1873 QStringList *deps,
1874 bool existingDepsOnly,
1875 bool checkCommandAvailability)
1876{
1877 char buff[256];
1878 QString dep_cmd = replaceExtraCompilerVariables(val: tmp_dep_cmd, in: inpf, out: tmp_out, forShell: LocalShell);
1879 if (checkCommandAvailability && !canExecute(cmdline: dep_cmd))
1880 return;
1881 dep_cmd = QLatin1String("cd ")
1882 + IoUtils::shellQuote(arg: Option::fixPathToLocalOS(in: Option::output_dir, fix_env: false))
1883 + QLatin1String(" && ")
1884 + fixEnvVariables(x: dep_cmd);
1885 if (FILE *proc = QT_POPEN(command: dep_cmd.toLatin1().constData(), QT_POPEN_READ)) {
1886 QByteArray depData;
1887 while (int read_in = feof(stream: proc) ? 0 : (int)fread(ptr: buff, size: 1, n: 255, stream: proc))
1888 depData.append(s: buff, len: read_in);
1889 QT_PCLOSE(stream: proc);
1890 const QString indeps = QString::fromLocal8Bit(ba: depData);
1891 if (indeps.isEmpty())
1892 return;
1893 QDir outDir(Option::output_dir);
1894 QStringList dep_cmd_deps = splitDeps(indeps, lineMode: dep_lines);
1895 for (int i = 0; i < dep_cmd_deps.size(); ++i) {
1896 QString &file = dep_cmd_deps[i];
1897 const QString absFile = outDir.absoluteFilePath(fileName: file);
1898 if (absFile == file) {
1899 // already absolute; don't do any checks.
1900 } else if (exists(file: absFile)) {
1901 file = absFile;
1902 } else {
1903 const QString localFile = resolveDependency(outDir, file);
1904 if (localFile.isEmpty()) {
1905 if (exists(file)) {
1906 warn_msg(t: WarnDeprecated, fmt: ".depend_command for extra compiler %s"
1907 " prints paths relative to source directory",
1908 extraCompiler.toLatin1().constData());
1909 } else if (existingDepsOnly) {
1910 file.clear();
1911 } else {
1912 file = absFile; // fallback for generated resources
1913 }
1914 } else {
1915 file = localFile;
1916 }
1917 }
1918 if (!file.isEmpty())
1919 file = fileFixify(file);
1920 }
1921 deps->append(l: dep_cmd_deps);
1922 }
1923}
1924
1925void
1926MakefileGenerator::writeExtraCompilerTargets(QTextStream &t)
1927{
1928 QString clean_targets;
1929 const ProStringList &quc = project->values(v: "QMAKE_EXTRA_COMPILERS");
1930 for (ProStringList::ConstIterator it = quc.begin(); it != quc.end(); ++it) {
1931 const ProStringList &config = project->values(v: ProKey(*it + ".CONFIG"));
1932 QString tmp_out = fileFixify(file: project->first(variableName: ProKey(*it + ".output")).toQString(),
1933 fix: FileFixifyFromOutdir);
1934 const QString tmp_cmd = project->values(v: ProKey(*it + ".commands")).join(sep: ' ');
1935 const QString tmp_dep_cmd = project->values(v: ProKey(*it + ".depend_command")).join(sep: ' ');
1936 const bool dep_lines = (config.indexOf(t: "dep_lines") != -1);
1937 const ProStringList &vars = project->values(v: ProKey(*it + ".variables"));
1938 if(tmp_out.isEmpty() || tmp_cmd.isEmpty())
1939 continue;
1940 ProStringList tmp_inputs;
1941 {
1942 const ProStringList &comp_inputs = project->values(v: ProKey(*it + ".input"));
1943 for (ProStringList::ConstIterator it2 = comp_inputs.begin(); it2 != comp_inputs.end(); ++it2) {
1944 const ProStringList &tmp = project->values(v: (*it2).toKey());
1945 for (ProStringList::ConstIterator input = tmp.begin(); input != tmp.end(); ++input) {
1946 if (verifyExtraCompiler(comp: (*it), file_unfixed: (*input).toQString()))
1947 tmp_inputs.append(t: (*input));
1948 }
1949 }
1950 }
1951
1952 t << "compiler_" << (*it) << "_make_all:";
1953 if (config.indexOf(t: "combine") != -1) {
1954 // compilers with a combined input only have one output
1955 QString input = project->first(variableName: ProKey(*it + ".output")).toQString();
1956 t << ' ' << escapeDependencyPath(path: fileFixify(
1957 file: replaceExtraCompilerVariables(val: tmp_out, in: input, out: QString(), forShell: NoShell),
1958 fix: FileFixifyFromOutdir));
1959 } else {
1960 for (ProStringList::ConstIterator input = tmp_inputs.cbegin(); input != tmp_inputs.cend(); ++input) {
1961 t << ' ' << escapeDependencyPath(path: fileFixify(
1962 file: replaceExtraCompilerVariables(val: tmp_out, in: (*input).toQString(), out: QString(), forShell: NoShell),
1963 fix: FileFixifyFromOutdir));
1964 }
1965 }
1966 t << Qt::endl;
1967
1968 if (config.indexOf(t: "no_clean") == -1) {
1969 QStringList raw_clean = project->values(v: ProKey(*it + ".clean")).toQStringList();
1970 if (raw_clean.isEmpty())
1971 raw_clean << tmp_out;
1972 QString tmp_clean;
1973 for (const QString &rc : std::as_const(t&: raw_clean))
1974 tmp_clean += ' ' + escapeFilePath(path: Option::fixPathToTargetOS(in: rc));
1975 QString tmp_clean_cmds = project->values(v: ProKey(*it + ".clean_commands")).join(sep: ' ');
1976 if(!tmp_inputs.isEmpty())
1977 clean_targets += QString("compiler_" + (*it) + "_clean ");
1978 t << "compiler_" << (*it) << "_clean:";
1979 bool wrote_clean_cmds = false, wrote_clean = false;
1980 if(tmp_clean_cmds.isEmpty()) {
1981 wrote_clean_cmds = true;
1982 } else if(tmp_clean_cmds.indexOf(s: "${QMAKE_") == -1) {
1983 t << "\n\t" << tmp_clean_cmds;
1984 wrote_clean_cmds = true;
1985 }
1986 if(tmp_clean.indexOf(s: "${QMAKE_") == -1) {
1987 t << "\n\t-$(DEL_FILE)" << tmp_clean;
1988 wrote_clean = true;
1989 }
1990 if(!wrote_clean_cmds || !wrote_clean) {
1991 QStringList cleans;
1992 const QString del_statement("-$(DEL_FILE)");
1993 if(!wrote_clean) {
1994 QStringList dels;
1995 for (ProStringList::ConstIterator input = tmp_inputs.cbegin(); input != tmp_inputs.cend(); ++input) {
1996 QString tinp = (*input).toQString();
1997 QString out = replaceExtraCompilerVariables(val: tmp_out, in: tinp, out: QString(), forShell: NoShell);
1998 for (const QString &rc : std::as_const(t&: raw_clean)) {
1999 dels << ' ' + escapeFilePath(path: fileFixify(
2000 file: replaceExtraCompilerVariables(val: rc, in: tinp, out, forShell: NoShell),
2001 fix: FileFixifyFromOutdir));
2002 }
2003 }
2004 if(project->isActiveConfig(config: "no_delete_multiple_files")) {
2005 cleans = dels;
2006 } else {
2007 QString files;
2008 const int commandlineLimit = 2047; // NT limit, expanded
2009 for (const QString &file : std::as_const(t&: dels)) {
2010 if(del_statement.size() + files.size() +
2011 qMax(a: fixEnvVariables(x: file).size(), b: file.size()) > commandlineLimit) {
2012 cleans.append(t: files);
2013 files.clear();
2014 }
2015 files += file;
2016 }
2017 if(!files.isEmpty())
2018 cleans.append(t: files);
2019 }
2020 }
2021 if(!cleans.isEmpty())
2022 t << valGlue(varList: cleans, before: "\n\t" + del_statement, glue: "\n\t" + del_statement, after: "");
2023 if(!wrote_clean_cmds) {
2024 for (ProStringList::ConstIterator input = tmp_inputs.cbegin(); input != tmp_inputs.cend(); ++input) {
2025 QString tinp = (*input).toQString();
2026 t << "\n\t" << replaceExtraCompilerVariables(val: tmp_clean_cmds, in: tinp,
2027 out: replaceExtraCompilerVariables(val: tmp_out, in: tinp, out: QString(), forShell: NoShell), forShell: TargetShell);
2028 }
2029 }
2030 }
2031 t << Qt::endl;
2032 }
2033 const bool existingDepsOnly = config.contains(str: "dep_existing_only");
2034 QStringList tmp_dep = project->values(v: ProKey(*it + ".depends")).toQStringList();
2035 if (config.indexOf(t: "combine") != -1) {
2036 if (tmp_out.contains(re: QRegularExpression("(^|[^$])\\$\\{QMAKE_(?!VAR_)"))) {
2037 warn_msg(t: WarnLogic, fmt: "QMAKE_EXTRA_COMPILERS(%s) with combine has variable output.",
2038 (*it).toLatin1().constData());
2039 continue;
2040 }
2041 QStringList deps, inputs;
2042 if(!tmp_dep.isEmpty())
2043 deps += fileFixify(files: tmp_dep, fix: FileFixifyFromOutdir);
2044 for (ProStringList::ConstIterator input = tmp_inputs.cbegin(); input != tmp_inputs.cend(); ++input) {
2045 QString inpf = (*input).toQString();
2046 deps += findDependencies(file: inpf);
2047 inputs += Option::fixPathToTargetOS(in: inpf, fix_env: false);
2048 if(!tmp_dep_cmd.isEmpty() && doDepends()) {
2049 callExtraCompilerDependCommand(extraCompiler: *it, tmp_dep_cmd, inpf,
2050 tmp_out, dep_lines, deps: &deps, existingDepsOnly);
2051 }
2052 }
2053 for(int i = 0; i < inputs.size(); ) {
2054 if(tmp_out == inputs.at(i))
2055 inputs.removeAt(i);
2056 else
2057 ++i;
2058 }
2059 for(int i = 0; i < deps.size(); ) {
2060 if(tmp_out == deps.at(i))
2061 deps.removeAt(i);
2062 else
2063 ++i;
2064 }
2065 if (inputs.isEmpty())
2066 continue;
2067
2068 QString out = replaceExtraCompilerVariables(val: tmp_out, in: QString(), out: QString(), forShell: NoShell);
2069 QString cmd = replaceExtraCompilerVariables(orig_var: tmp_cmd, in: inputs, out: QStringList() << out, forShell: TargetShell);
2070 t << escapeDependencyPath(path: fileFixify(file: out, fix: FileFixifyFromOutdir)) << ":";
2071 // compiler.CONFIG+=explicit_dependencies means that ONLY compiler.depends gets to cause Makefile dependencies
2072 if (config.indexOf(t: "explicit_dependencies") != -1) {
2073 t << " " << valList(varList: escapeDependencyPaths(paths: fileFixify(files: tmp_dep, fix: FileFixifyFromOutdir)));
2074 } else {
2075 t << " " << valList(varList: escapeDependencyPaths(paths: inputs)) << " " << valList(varList: finalizeDependencyPaths(paths: deps));
2076 }
2077 t << "\n\t" << cmd << Qt::endl << Qt::endl;
2078 continue;
2079 }
2080 for (ProStringList::ConstIterator input = tmp_inputs.cbegin(); input != tmp_inputs.cend(); ++input) {
2081 QString inpf = (*input).toQString();
2082 QStringList deps;
2083 deps << fileFixify(file: inpf, fix: FileFixifyFromOutdir);
2084 deps += findDependencies(file: inpf);
2085 QString out = fileFixify(file: replaceExtraCompilerVariables(val: tmp_out, in: inpf, out: QString(), forShell: NoShell),
2086 fix: FileFixifyFromOutdir);
2087 if(!tmp_dep.isEmpty()) {
2088 QStringList pre_deps = fileFixify(files: tmp_dep, fix: FileFixifyFromOutdir);
2089 for(int i = 0; i < pre_deps.size(); ++i)
2090 deps << fileFixify(file: replaceExtraCompilerVariables(val: pre_deps.at(i), in: inpf, out, forShell: NoShell),
2091 fix: FileFixifyFromOutdir);
2092 }
2093 QString cmd = replaceExtraCompilerVariables(val: tmp_cmd, in: inpf, out, forShell: TargetShell);
2094 // NOTE: The var -> QMAKE_COMP_var replace feature is unsupported, do not use!
2095 for (ProStringList::ConstIterator it3 = vars.constBegin(); it3 != vars.constEnd(); ++it3)
2096 cmd.replace(before: "$(" + (*it3) + ")", after: "$(QMAKE_COMP_" + (*it3)+")");
2097 if(!tmp_dep_cmd.isEmpty() && doDepends()) {
2098 callExtraCompilerDependCommand(extraCompiler: *it, tmp_dep_cmd, inpf,
2099 tmp_out, dep_lines, deps: &deps, existingDepsOnly);
2100 //use the depend system to find includes of these included files
2101 QStringList inc_deps;
2102 for(int i = 0; i < deps.size(); ++i) {
2103 const QString dep = fileFixify(file: deps.at(i), fix: FileFixifyFromOutdir | FileFixifyAbsolute);
2104 if(QFile::exists(fileName: dep)) {
2105 SourceFileType type = TYPE_UNKNOWN;
2106 if(type == TYPE_UNKNOWN) {
2107 for(QStringList::Iterator cit = Option::c_ext.begin();
2108 cit != Option::c_ext.end(); ++cit) {
2109 if(dep.endsWith(s: (*cit))) {
2110 type = TYPE_C;
2111 break;
2112 }
2113 }
2114 }
2115 if(type == TYPE_UNKNOWN) {
2116 for(QStringList::Iterator cppit = Option::cpp_ext.begin();
2117 cppit != Option::cpp_ext.end(); ++cppit) {
2118 if(dep.endsWith(s: (*cppit))) {
2119 type = TYPE_C;
2120 break;
2121 }
2122 }
2123 }
2124 if(type == TYPE_UNKNOWN) {
2125 for(QStringList::Iterator hit = Option::h_ext.begin();
2126 type == TYPE_UNKNOWN && hit != Option::h_ext.end(); ++hit) {
2127 if(dep.endsWith(s: (*hit))) {
2128 type = TYPE_C;
2129 break;
2130 }
2131 }
2132 }
2133 if(type != TYPE_UNKNOWN) {
2134 if(!QMakeSourceFileInfo::containsSourceFile(dep, type))
2135 QMakeSourceFileInfo::addSourceFile(dep, seek: type);
2136 inc_deps += QMakeSourceFileInfo::dependencies(file: dep);
2137 }
2138 }
2139 }
2140 deps += fileFixify(files: inc_deps, fix: FileFixifyFromOutdir);
2141 }
2142 for(int i = 0; i < deps.size(); ) {
2143 QString &dep = deps[i];
2144 if(out == dep)
2145 deps.removeAt(i);
2146 else
2147 ++i;
2148 }
2149 t << escapeDependencyPath(path: out) << ": " << valList(varList: finalizeDependencyPaths(paths: deps)) << "\n\t"
2150 << cmd << Qt::endl << Qt::endl;
2151 }
2152 }
2153 t << "compiler_clean: " << clean_targets << Qt::endl << Qt::endl;
2154}
2155
2156void
2157MakefileGenerator::writeExtraCompilerVariables(QTextStream &t)
2158{
2159 bool first = true;
2160 const ProStringList &quc = project->values(v: "QMAKE_EXTRA_COMPILERS");
2161 for (ProStringList::ConstIterator it = quc.begin(); it != quc.end(); ++it) {
2162 const ProStringList &vars = project->values(v: ProKey(*it + ".variables"));
2163 for (ProStringList::ConstIterator varit = vars.begin(); varit != vars.end(); ++varit) {
2164 if(first) {
2165 t << "\n####### Custom Compiler Variables\n";
2166 first = false;
2167 }
2168 t << "QMAKE_COMP_" << (*varit) << " = "
2169 << valList(varList: project->values(v: (*varit).toKey())) << Qt::endl;
2170 }
2171 }
2172 if(!first)
2173 t << Qt::endl;
2174}
2175
2176void
2177MakefileGenerator::writeExtraVariables(QTextStream &t)
2178{
2179 t << Qt::endl;
2180
2181 ProStringList outlist;
2182 const ProValueMap &vars = project->variables();
2183 const ProStringList &exports = project->values(v: "QMAKE_EXTRA_VARIABLES");
2184 for (ProStringList::ConstIterator exp_it = exports.begin(); exp_it != exports.end(); ++exp_it) {
2185 auto rx = QRegularExpression::fromWildcard(pattern: (*exp_it).toQString(), cs: Qt::CaseInsensitive);
2186 for (ProValueMap::ConstIterator it = vars.begin(); it != vars.end(); ++it) {
2187 if (rx.match(subject: it.key().toQString()).hasMatch())
2188 outlist << ("EXPORT_" + it.key() + " = " + it.value().join(sep: ' '));
2189 }
2190 }
2191 if (!outlist.isEmpty()) {
2192 t << "####### Custom Variables\n";
2193 t << outlist.join(sep: '\n') << Qt::endl << Qt::endl;
2194 }
2195}
2196
2197// This is a more powerful alternative to the above function.
2198// It's meant to be internal, as one can make quite a mess with it.
2199void
2200MakefileGenerator::writeExportedVariables(QTextStream &t)
2201{
2202 const auto &vars = project->values(v: "QMAKE_EXPORTED_VARIABLES");
2203 if (vars.isEmpty())
2204 return;
2205 for (const auto &exp : vars) {
2206 const ProString &name = project->first(variableName: ProKey(exp + ".name"));
2207 const ProString &value = project->first(variableName: ProKey(exp + ".value"));
2208 if (!value.isEmpty())
2209 t << name << " = " << value << Qt::endl;
2210 else
2211 t << name << " =\n";
2212 }
2213 t << Qt::endl;
2214}
2215
2216bool
2217MakefileGenerator::writeDummyMakefile(QTextStream &t)
2218{
2219 if (project->values(v: "QMAKE_FAILED_REQUIREMENTS").isEmpty())
2220 return false;
2221 t << "QMAKE = " << var(var: "QMAKE_QMAKE") << Qt::endl;
2222 const ProStringList &qut = project->values(v: "QMAKE_EXTRA_TARGETS");
2223 for (ProStringList::ConstIterator it = qut.begin(); it != qut.end(); ++it)
2224 t << *it << " ";
2225 t << "first all clean install distclean uninstall qmake_all:\n\t"
2226 << "@echo \"Some of the required modules ("
2227 << var(var: "QMAKE_FAILED_REQUIREMENTS") << ") are not available.\"\n\t"
2228 << "@echo \"Skipped.\"\n\n";
2229 writeMakeQmake(t);
2230 t << "FORCE:\n\n";
2231 suppressBuiltinRules(t);
2232 return true;
2233}
2234
2235bool
2236MakefileGenerator::writeMakefile(QTextStream &t)
2237{
2238 t << "####### Compile\n\n";
2239 writeObj(t, src: "SOURCES");
2240 writeObj(t, src: "GENERATED_SOURCES");
2241
2242 t << "####### Install\n\n";
2243 writeInstalls(t);
2244
2245 t << "FORCE:\n\n";
2246 suppressBuiltinRules(t);
2247 return true;
2248}
2249
2250void
2251MakefileGenerator::writeDefaultVariables(QTextStream &t)
2252{
2253 t << "QMAKE = " << var(var: "QMAKE_QMAKE") << Qt::endl;
2254 t << "DEL_FILE = " << var(var: "QMAKE_DEL_FILE") << Qt::endl;
2255 t << "CHK_DIR_EXISTS= " << var(var: "QMAKE_CHK_DIR_EXISTS") << Qt::endl;
2256 t << "MKDIR = " << var(var: "QMAKE_MKDIR") << Qt::endl;
2257 t << "COPY = " << var(var: "QMAKE_COPY") << Qt::endl;
2258 t << "COPY_FILE = " << var(var: "QMAKE_COPY_FILE") << Qt::endl;
2259 t << "COPY_DIR = " << var(var: "QMAKE_COPY_DIR") << Qt::endl;
2260 t << "INSTALL_FILE = " << var(var: "QMAKE_INSTALL_FILE") << Qt::endl;
2261 t << "INSTALL_PROGRAM = " << var(var: "QMAKE_INSTALL_PROGRAM") << Qt::endl;
2262 t << "INSTALL_DIR = " << var(var: "QMAKE_INSTALL_DIR") << Qt::endl;
2263 t << "QINSTALL = " << var(var: "QMAKE_QMAKE") << " -install qinstall" << Qt::endl;
2264 t << "QINSTALL_PROGRAM = " << var(var: "QMAKE_QMAKE") << " -install qinstall -exe" << Qt::endl;
2265 t << "DEL_FILE = " << var(var: "QMAKE_DEL_FILE") << Qt::endl;
2266 t << "SYMLINK = " << var(var: "QMAKE_SYMBOLIC_LINK") << Qt::endl;
2267 t << "DEL_DIR = " << var(var: "QMAKE_DEL_DIR") << Qt::endl;
2268 t << "MOVE = " << var(var: "QMAKE_MOVE") << Qt::endl;
2269}
2270
2271QString MakefileGenerator::buildArgs(bool withExtra)
2272{
2273 QString ret;
2274
2275 for (const QString &arg : std::as_const(t&: Option::globals->qmake_args))
2276 ret += " " + shellQuote(str: arg);
2277 if (withExtra && !Option::globals->qmake_extra_args.isEmpty()) {
2278 ret += " --";
2279 for (const QString &arg : std::as_const(t&: Option::globals->qmake_extra_args))
2280 ret += " " + shellQuote(str: arg);
2281 }
2282 return ret;
2283}
2284
2285//could get stored argv, but then it would have more options than are
2286//probably necesary this will try to guess the bare minimum..
2287QString MakefileGenerator::fullBuildArgs()
2288{
2289 QString ret;
2290
2291 //output
2292 QString ofile = fileFixify(file: Option::output.fileName());
2293 if (!ofile.isEmpty() && ofile != project->first(variableName: "QMAKE_MAKEFILE").toQStringView())
2294 ret += " -o " + escapeFilePath(path: ofile);
2295
2296 //inputs
2297 ret += " " + escapeFilePath(path: fileFixify(file: project->projectFile()));
2298
2299 // general options and arguments
2300 ret += buildArgs(withExtra: true);
2301
2302 return ret;
2303}
2304
2305void
2306MakefileGenerator::writeHeader(QTextStream &t)
2307{
2308 t << "#############################################################################\n";
2309 t << "# Makefile for building: " << escapeFilePath(path: var(var: "TARGET")) << Qt::endl;
2310 t << "# Generated by qmake (" QMAKE_VERSION_STR ") (Qt " QT_VERSION_STR ")\n";
2311 t << "# Project: " << fileFixify(file: project->projectFile()) << Qt::endl;
2312 t << "# Template: " << var(var: "TEMPLATE") << Qt::endl;
2313 if(!project->isActiveConfig(config: "build_pass"))
2314 t << "# Command: " << var(var: "QMAKE_QMAKE") << fullBuildArgs() << Qt::endl;
2315 t << "#############################################################################\n";
2316 t << Qt::endl;
2317 QString ofile = Option::fixPathToTargetOS(in: Option::output.fileName());
2318 if (ofile.lastIndexOf(s: Option::dir_sep) != -1)
2319 ofile.remove(i: 0, len: ofile.lastIndexOf(s: Option::dir_sep) +1);
2320 t << "MAKEFILE = " << escapeFilePath(path: ofile) << Qt::endl << Qt::endl;
2321 t << "EQ = =\n\n";
2322}
2323
2324QList<MakefileGenerator::SubTarget*>
2325MakefileGenerator::findSubDirsSubTargets() const
2326{
2327 QList<SubTarget*> targets;
2328 {
2329 const ProStringList &subdirs = project->values(v: "SUBDIRS");
2330 for(int subdir = 0; subdir < subdirs.size(); ++subdir) {
2331 ProString ofile = subdirs[subdir];
2332 QString oname = ofile.toQString();
2333 QString fixedSubdir = oname;
2334 fixedSubdir.replace(re: QRegularExpression("[^a-zA-Z0-9_]"),after: "-");
2335
2336 SubTarget *st = new SubTarget;
2337 st->name = oname;
2338 targets.append(t: st);
2339
2340 bool fromFile = false;
2341 const ProKey fkey(fixedSubdir + ".file");
2342 const ProKey skey(fixedSubdir + ".subdir");
2343 if (!project->isEmpty(v: fkey)) {
2344 if (!project->isEmpty(v: skey))
2345 warn_msg(t: WarnLogic, fmt: "Cannot assign both file and subdir for subdir %s",
2346 subdirs[subdir].toLatin1().constData());
2347 ofile = project->first(variableName: fkey);
2348 fromFile = true;
2349 } else if (!project->isEmpty(v: skey)) {
2350 ofile = project->first(variableName: skey);
2351 fromFile = false;
2352 } else {
2353 fromFile = ofile.endsWith(sub: Option::pro_ext);
2354 }
2355 QString file = Option::fixPathToTargetOS(in: ofile.toQString());
2356
2357 if(fromFile) {
2358 int slsh = file.lastIndexOf(s: Option::dir_sep);
2359 if(slsh != -1) {
2360 st->in_directory = file.left(n: slsh+1);
2361 st->profile = file.mid(position: slsh+1);
2362 } else {
2363 st->profile = file;
2364 }
2365 } else {
2366 if (!file.isEmpty() && !project->isActiveConfig(config: "subdir_first_pro")) {
2367 const QString baseName = file.section(in_sep: Option::dir_sep, start: -1);
2368 if (baseName.isEmpty()) {
2369 warn_msg(t: WarnLogic, fmt: "Ignoring invalid SUBDIRS entry %s",
2370 subdirs[subdir].toLatin1().constData());
2371 continue;
2372 }
2373 st->profile = baseName + Option::pro_ext;
2374 }
2375 st->in_directory = file;
2376 }
2377 while(st->in_directory.endsWith(s: Option::dir_sep))
2378 st->in_directory.chop(n: 1);
2379 if(fileInfo(file: st->in_directory).isRelative())
2380 st->out_directory = st->in_directory;
2381 else
2382 st->out_directory = fileFixify(file: st->in_directory, fix: FileFixifyBackwards);
2383 const ProKey mkey(fixedSubdir + ".makefile");
2384 if (!project->isEmpty(v: mkey)) {
2385 st->makefile = project->first(variableName: mkey).toQString();
2386 } else {
2387 st->makefile = "Makefile";
2388 if(!st->profile.isEmpty()) {
2389 QString basename = st->in_directory;
2390 int new_slsh = basename.lastIndexOf(s: Option::dir_sep);
2391 if(new_slsh != -1)
2392 basename = basename.mid(position: new_slsh+1);
2393 if(st->profile != basename + Option::pro_ext)
2394 st->makefile += "." + st->profile.left(n: st->profile.size() - Option::pro_ext.size());
2395 }
2396 }
2397 const ProKey dkey(fixedSubdir + ".depends");
2398 if (!project->isEmpty(v: dkey)) {
2399 const ProStringList &depends = project->values(v: dkey);
2400 for(int depend = 0; depend < depends.size(); ++depend) {
2401 bool found = false;
2402 for(int subDep = 0; subDep < subdirs.size(); ++subDep) {
2403 if(subdirs[subDep] == depends.at(i: depend)) {
2404 QString subName = subdirs[subDep].toQString();
2405 QString fixedSubDep = subName;
2406 fixedSubDep.replace(re: QRegularExpression("[^a-zA-Z0-9_]"),after: "-");
2407 const ProKey dtkey(fixedSubDep + ".target");
2408 if (!project->isEmpty(v: dtkey)) {
2409 st->depends += project->first(variableName: dtkey);
2410 } else {
2411 QString d = Option::fixPathToTargetOS(in: subName);
2412 const ProKey dfkey(fixedSubDep + ".file");
2413 if (!project->isEmpty(v: dfkey)) {
2414 d = project->first(variableName: dfkey).toQString();
2415 } else {
2416 const ProKey dskey(fixedSubDep + ".subdir");
2417 if (!project->isEmpty(v: dskey))
2418 d = project->first(variableName: dskey).toQString();
2419 }
2420 st->depends += "sub-" + d.replace(re: QRegularExpression("[^a-zA-Z0-9_]"),after: "-");
2421 }
2422 found = true;
2423 break;
2424 }
2425 }
2426 if(!found) {
2427 QString depend_str = depends.at(i: depend).toQString();
2428 st->depends += depend_str.replace(re: QRegularExpression("[^a-zA-Z0-9_]"),after: "-");
2429 }
2430 }
2431 }
2432 const ProKey tkey(fixedSubdir + ".target");
2433 if (!project->isEmpty(v: tkey)) {
2434 st->target = project->first(variableName: tkey).toQString();
2435 } else {
2436 st->target = "sub-" + file;
2437 st->target.replace(re: QRegularExpression("[^a-zA-Z0-9_]"), after: "-");
2438 }
2439 }
2440 }
2441 return targets;
2442}
2443
2444void
2445MakefileGenerator::writeSubDirs(QTextStream &t)
2446{
2447 QList<SubTarget*> targets = findSubDirsSubTargets();
2448 t << "first: make_first\n";
2449 int flags = SubTargetInstalls;
2450 if(project->isActiveConfig(config: "ordered"))
2451 flags |= SubTargetOrdered;
2452 writeSubTargets(t, subtargets: targets, flags);
2453 qDeleteAll(c: targets);
2454}
2455
2456void MakefileGenerator::writeSubMakeCall(QTextStream &t, const QString &callPrefix,
2457 const QString &makeArguments)
2458{
2459 t << callPrefix << "$(MAKE)" << makeArguments << Qt::endl;
2460}
2461
2462void
2463MakefileGenerator::writeSubTargetCall(QTextStream &t,
2464 const QString &in_directory, const QString &in, const QString &out_directory, const QString &out,
2465 const QString &out_directory_cdin, const QString &makefilein)
2466{
2467 QString pfx;
2468 if (!in.isEmpty()) {
2469 if (!in_directory.isEmpty())
2470 t << "\n\t" << mkdir_p_asstring(dir: out_directory);
2471 pfx = "( " + chkexists.arg(a: out) +
2472 + " $(QMAKE) -o " + out + ' ' + in + buildArgs(withExtra: false)
2473 + " ) && ";
2474 }
2475 writeSubMakeCall(t, callPrefix: out_directory_cdin + pfx, makeArguments: makefilein);
2476}
2477
2478static void chopEndLines(QString *s)
2479{
2480 while (!s->isEmpty()) {
2481 const ushort c = s->at(i: s->size() - 1).unicode();
2482 if (c != '\n' && c != '\r')
2483 break;
2484 s->chop(n: 1);
2485 }
2486}
2487
2488void
2489MakefileGenerator::writeSubTargets(QTextStream &t, QList<MakefileGenerator::SubTarget*> targets, int flags)
2490{
2491 // blasted includes
2492 const ProStringList &qeui = project->values(v: "QMAKE_EXTRA_INCLUDES");
2493 for (ProStringList::ConstIterator qeui_it = qeui.begin(); qeui_it != qeui.end(); ++qeui_it)
2494 t << "include " << (*qeui_it) << Qt::endl;
2495
2496 if (!(flags & SubTargetSkipDefaultVariables)) {
2497 writeDefaultVariables(t);
2498 t << "SUBTARGETS = "; // subtargets are sub-directory
2499 for(int target = 0; target < targets.size(); ++target)
2500 t << " \\\n\t\t" << targets.at(i: target)->target;
2501 t << Qt::endl << Qt::endl;
2502 }
2503 writeExtraVariables(t);
2504
2505 QStringList targetSuffixes;
2506 const QString abs_source_path = project->first(variableName: "QMAKE_ABSOLUTE_SOURCE_PATH").toQString();
2507 if (!(flags & SubTargetSkipDefaultTargets)) {
2508 targetSuffixes << "make_first" << "all" << "clean" << "distclean"
2509 << QString((flags & SubTargetInstalls) ? "install_subtargets" : "install")
2510 << QString((flags & SubTargetInstalls) ? "uninstall_subtargets" : "uninstall");
2511 }
2512
2513 struct SequentialInstallData
2514 {
2515 QString targetPrefix;
2516 QString commands;
2517 QTextStream commandsStream;
2518 SequentialInstallData() : commandsStream(&commands) {}
2519 };
2520 std::unique_ptr<SequentialInstallData> sequentialInstallData;
2521 bool dont_recurse = project->isActiveConfig(config: "dont_recurse");
2522
2523 // generate target rules
2524 for(int target = 0; target < targets.size(); ++target) {
2525 SubTarget *subtarget = targets.at(i: target);
2526 QString in_directory = subtarget->in_directory;
2527 if(!in_directory.isEmpty() && !in_directory.endsWith(s: Option::dir_sep))
2528 in_directory += Option::dir_sep;
2529 QString out_directory = subtarget->out_directory;
2530 if(!out_directory.isEmpty() && !out_directory.endsWith(s: Option::dir_sep))
2531 out_directory += Option::dir_sep;
2532 if(!abs_source_path.isEmpty() && out_directory.startsWith(s: abs_source_path))
2533 out_directory = Option::output_dir + out_directory.mid(position: abs_source_path.size());
2534
2535 QString out_directory_cdin = out_directory.isEmpty() ? QString("\n\t")
2536 : "\n\tcd " + escapeFilePath(path: out_directory) + " && ";
2537 QString makefilein = " -f " + escapeFilePath(path: subtarget->makefile);
2538
2539 //qmake it
2540 QString out;
2541 QString in;
2542 if(!subtarget->profile.isEmpty()) {
2543 out = subtarget->makefile;
2544 in = escapeFilePath(path: fileFixify(file: in_directory + subtarget->profile, fix: FileFixifyAbsolute));
2545 if(out.startsWith(s: in_directory))
2546 out = out.mid(position: in_directory.size());
2547 out = escapeFilePath(path: out);
2548 t << subtarget->target << "-qmake_all: ";
2549 if (flags & SubTargetOrdered) {
2550 if (target)
2551 t << targets.at(i: target - 1)->target << "-qmake_all";
2552 } else {
2553 if (!subtarget->depends.isEmpty())
2554 t << valGlue(varList: subtarget->depends, before: QString(), glue: "-qmake_all ", after: "-qmake_all");
2555 }
2556 t << " FORCE\n\t";
2557 if(!in_directory.isEmpty()) {
2558 t << mkdir_p_asstring(dir: out_directory)
2559 << out_directory_cdin;
2560 }
2561 t << "$(QMAKE) -o " << out << ' ' << in << buildArgs(withExtra: false);
2562 if (!dont_recurse)
2563 writeSubMakeCall(t, callPrefix: out_directory_cdin, makeArguments: makefilein + " qmake_all");
2564 else
2565 t << Qt::endl;
2566 }
2567
2568 { //actually compile
2569 t << subtarget->target << ":";
2570 auto extraDeps = extraSubTargetDependencies();
2571 if (!extraDeps.isEmpty())
2572 t << " " << valList(varList: extraDeps);
2573 if(!subtarget->depends.isEmpty())
2574 t << " " << valList(varList: subtarget->depends);
2575 t << " FORCE";
2576 writeSubTargetCall(t, in_directory, in, out_directory, out,
2577 out_directory_cdin, makefilein);
2578 }
2579
2580 for(int suffix = 0; suffix < targetSuffixes.size(); ++suffix) {
2581 QString s = targetSuffixes.at(i: suffix);
2582 if(s == "install_subtargets")
2583 s = "install";
2584 else if(s == "uninstall_subtargets")
2585 s = "uninstall";
2586 else if(s == "make_first")
2587 s = QString();
2588
2589 if (project->isActiveConfig(config: "build_all") && s == "install") {
2590 if (!sequentialInstallData)
2591 sequentialInstallData.reset(p: new SequentialInstallData);
2592 sequentialInstallData->targetPrefix += subtarget->target + '-';
2593 writeSubTargetCall(t&: sequentialInstallData->commandsStream, in_directory, in,
2594 out_directory, out, out_directory_cdin,
2595 makefilein: makefilein + " " + s);
2596 chopEndLines(s: &sequentialInstallData->commands);
2597 }
2598
2599 if(flags & SubTargetOrdered) {
2600 t << subtarget->target << "-" << targetSuffixes.at(i: suffix) << "-ordered:";
2601 if(target)
2602 t << " " << targets.at(i: target-1)->target << "-" << targetSuffixes.at(i: suffix) << "-ordered ";
2603 t << " FORCE";
2604 writeSubTargetCall(t, in_directory, in, out_directory, out,
2605 out_directory_cdin, makefilein: makefilein + " " + s);
2606 }
2607 t << subtarget->target << "-" << targetSuffixes.at(i: suffix) << ":";
2608 if(!subtarget->depends.isEmpty())
2609 t << " " << valGlue(varList: subtarget->depends, before: QString(), glue: "-" + targetSuffixes.at(i: suffix) + " ",
2610 after: "-"+targetSuffixes.at(i: suffix));
2611 t << " FORCE";
2612 writeSubTargetCall(t, in_directory, in, out_directory, out,
2613 out_directory_cdin, makefilein: makefilein + " " + s);
2614 }
2615 }
2616 t << Qt::endl;
2617
2618 if (sequentialInstallData) {
2619 t << sequentialInstallData->targetPrefix << "install: FORCE"
2620 << sequentialInstallData->commands << Qt::endl << Qt::endl;
2621 }
2622
2623 if (!(flags & SubTargetSkipDefaultTargets)) {
2624 writeMakeQmake(t, noDummyQmakeAll: true);
2625
2626 t << "qmake_all:";
2627 if(!targets.isEmpty()) {
2628 for(QList<SubTarget*>::Iterator it = targets.begin(); it != targets.end(); ++it) {
2629 if(!(*it)->profile.isEmpty())
2630 t << " " << (*it)->target << "-qmake_all";
2631 }
2632 }
2633 t << " FORCE\n\n";
2634 }
2635
2636 for(int s = 0; s < targetSuffixes.size(); ++s) {
2637 QString suffix = targetSuffixes.at(i: s);
2638 if(!(flags & SubTargetInstalls) && suffix.endsWith(s: "install"))
2639 continue;
2640
2641 t << suffix << ":";
2642 for(int target = 0; target < targets.size(); ++target) {
2643 SubTarget *subTarget = targets.at(i: target);
2644 const ProStringList &config = project->values(v: ProKey(subTarget->name + ".CONFIG"));
2645 if (suffix == "make_first"
2646 && config.indexOf(t: "no_default_target") != -1) {
2647 continue;
2648 }
2649 if((suffix == "install_subtargets" || suffix == "uninstall_subtargets")
2650 && config.indexOf(t: "no_default_install") != -1) {
2651 continue;
2652 }
2653 QString targetRule = subTarget->target + "-" + suffix;
2654 if(flags & SubTargetOrdered)
2655 targetRule += "-ordered";
2656 t << " " << targetRule;
2657 }
2658 if(suffix == "all" || suffix == "make_first")
2659 t << ' ' << depVar(var: "ALL_DEPS");
2660 if(suffix == "clean")
2661 t << ' ' << depVar(var: "CLEAN_DEPS");
2662 else if (suffix == "distclean")
2663 t << ' ' << depVar(var: "DISTCLEAN_DEPS");
2664 t << " FORCE\n";
2665 if(suffix == "clean") {
2666 t << fixFileVarGlue(var: "QMAKE_CLEAN", before: "\t-$(DEL_FILE) ", glue: "\n\t-$(DEL_FILE) ", after: "\n");
2667 } else if(suffix == "distclean") {
2668 QString ofile = fileFixify(file: Option::output.fileName());
2669 if(!ofile.isEmpty())
2670 t << "\t-$(DEL_FILE) " << escapeFilePath(path: ofile) << Qt::endl;
2671 t << fixFileVarGlue(var: "QMAKE_DISTCLEAN", before: "\t-$(DEL_FILE) ", glue: " ", after: "\n");
2672 }
2673 }
2674
2675 // user defined targets
2676 const ProStringList &qut = project->values(v: "QMAKE_EXTRA_TARGETS");
2677 for (ProStringList::ConstIterator qut_it = qut.begin(); qut_it != qut.end(); ++qut_it) {
2678 const ProStringList &config = project->values(v: ProKey(*qut_it + ".CONFIG"));
2679 QString targ = var(var: ProKey(*qut_it + ".target")),
2680 cmd = var(var: ProKey(*qut_it + ".commands")), deps;
2681 if(targ.isEmpty())
2682 targ = (*qut_it).toQString();
2683 t << Qt::endl;
2684
2685 const ProStringList &deplist = project->values(v: ProKey(*qut_it + ".depends"));
2686 for (ProStringList::ConstIterator dep_it = deplist.begin(); dep_it != deplist.end(); ++dep_it) {
2687 QString dep = var(var: ProKey(*dep_it + ".target"));
2688 if(dep.isEmpty())
2689 dep = (*dep_it).toQString();
2690 deps += ' ' + escapeDependencyPath(path: Option::fixPathToTargetOS(in: dep, fix_env: false));
2691 }
2692 if (config.indexOf(t: "recursive") != -1) {
2693 QSet<QString> recurse;
2694 const ProKey rkey(*qut_it + ".recurse");
2695 if (project->isSet(v: rkey)) {
2696 const QStringList values = project->values(v: rkey).toQStringList();
2697 recurse = QSet<QString>(values.begin(), values.end());
2698 } else {
2699 for(int target = 0; target < targets.size(); ++target)
2700 recurse.insert(value: targets.at(i: target)->name);
2701 }
2702 for(int target = 0; target < targets.size(); ++target) {
2703 SubTarget *subtarget = targets.at(i: target);
2704 QString in_directory = subtarget->in_directory;
2705 if(!in_directory.isEmpty() && !in_directory.endsWith(s: Option::dir_sep))
2706 in_directory += Option::dir_sep;
2707 QString out_directory = subtarget->out_directory;
2708 if(!out_directory.isEmpty() && !out_directory.endsWith(s: Option::dir_sep))
2709 out_directory += Option::dir_sep;
2710 if(!abs_source_path.isEmpty() && out_directory.startsWith(s: abs_source_path))
2711 out_directory = Option::output_dir + out_directory.mid(position: abs_source_path.size());
2712
2713 if(!recurse.contains(value: subtarget->name))
2714 continue;
2715
2716 QString out_directory_cdin = out_directory.isEmpty() ? QString("\n\t")
2717 : "\n\tcd " + escapeFilePath(path: out_directory) + " && ";
2718 QString makefilein = " -f " + escapeFilePath(path: subtarget->makefile);
2719
2720 QString out;
2721 QString in;
2722 if (!subtarget->profile.isEmpty()) {
2723 out = subtarget->makefile;
2724 in = escapeFilePath(path: fileFixify(file: in_directory + subtarget->profile, fix: FileFixifyAbsolute));
2725 if (out.startsWith(s: in_directory))
2726 out = out.mid(position: in_directory.size());
2727 out = escapeFilePath(path: out);
2728 }
2729
2730 //write the rule/depends
2731 if(flags & SubTargetOrdered) {
2732 const QString dep = subtarget->target + "-" + (*qut_it) + "_ordered";
2733 t << dep << ":";
2734 if(target)
2735 t << " " << targets.at(i: target-1)->target << "-" << (*qut_it) << "_ordered ";
2736 deps += " " + dep;
2737 } else {
2738 const QString dep = subtarget->target + "-" + (*qut_it);
2739 t << dep << ":";
2740 if(!subtarget->depends.isEmpty())
2741 t << " " << valGlue(varList: subtarget->depends, before: QString(), glue: "-" + (*qut_it) + " ", after: "-" + (*qut_it));
2742 deps += " " + dep;
2743 }
2744
2745 QString sub_targ = targ;
2746 const ProKey rtkey(*qut_it + ".recurse_target");
2747 if (project->isSet(v: rtkey))
2748 sub_targ = project->first(variableName: rtkey).toQString();
2749
2750 //write the commands
2751 writeSubTargetCall(t, in_directory, in, out_directory, out,
2752 out_directory_cdin, makefilein: makefilein + " " + sub_targ);
2753 }
2754 }
2755 if (config.indexOf(t: "phony") != -1)
2756 deps += " FORCE";
2757 t << escapeDependencyPath(path: Option::fixPathToTargetOS(in: targ, fix_env: false)) << ":" << deps << "\n";
2758 if(!cmd.isEmpty())
2759 t << "\t" << cmd << Qt::endl;
2760 }
2761
2762 if(flags & SubTargetInstalls) {
2763 project->values(v: "INSTALLDEPS") += "install_subtargets";
2764 project->values(v: "UNINSTALLDEPS") += "uninstall_subtargets";
2765 writeInstalls(t, noBuild: true);
2766 }
2767 t << "FORCE:\n\n";
2768 suppressBuiltinRules(t);
2769}
2770
2771void
2772MakefileGenerator::suppressBuiltinRules(QTextStream &t) const
2773{
2774 t << ".SUFFIXES:\n\n";
2775}
2776
2777void
2778MakefileGenerator::writeMakeQmake(QTextStream &t, bool noDummyQmakeAll)
2779{
2780 QString ofile = fileFixify(file: Option::output.fileName());
2781 if(project->isEmpty(v: "QMAKE_FAILED_REQUIREMENTS") && !project->isEmpty(v: "QMAKE_INTERNAL_PRL_FILE")) {
2782 QStringList files = escapeFilePaths(paths: fileFixify(files: Option::mkfile::project_files));
2783 t << escapeDependencyPath(path: project->first(variableName: "QMAKE_INTERNAL_PRL_FILE").toQString()) << ": \n\t"
2784 << "@$(QMAKE) -prl " << files.join(sep: ' ') << ' ' << buildArgs(withExtra: true) << Qt::endl;
2785 }
2786
2787 QString qmake = "$(QMAKE)" + fullBuildArgs();
2788 if(!ofile.isEmpty() && !project->isActiveConfig(config: "no_autoqmake")) {
2789 t << escapeDependencyPath(path: ofile) << ": "
2790 << escapeDependencyPath(path: fileFixify(file: project->projectFile())) << " ";
2791 if (Option::globals->do_cache) {
2792 if (!project->confFile().isEmpty())
2793 t << escapeDependencyPath(path: fileFixify(file: project->confFile())) << " ";
2794 if (!project->cacheFile().isEmpty())
2795 t << escapeDependencyPath(path: fileFixify(file: project->cacheFile())) << " ";
2796 }
2797 if(!specdir().isEmpty()) {
2798 if (exists(file: Option::normalizePath(in: specdir() + "/qmake.conf")))
2799 t << escapeDependencyPath(path: specdir() + Option::dir_sep + "qmake.conf") << " ";
2800 }
2801 const ProStringList &included = escapeDependencyPaths(paths: project->values(v: "QMAKE_INTERNAL_INCLUDED_FILES"));
2802 t << included.join(sep: QString(" \\\n\t\t")) << "\n\t"
2803 << qmake << Qt::endl;
2804 const ProStringList &extraCommands = project->values(v: "QMAKE_MAKE_QMAKE_EXTRA_COMMANDS");
2805 if (!extraCommands.isEmpty())
2806 t << "\t" << extraCommands.join(sep: QString("\n\t")) << Qt::endl;
2807 for(int include = 0; include < included.size(); ++include) {
2808 const ProString &i = included.at(i: include);
2809 if(!i.isEmpty())
2810 t << i << ":\n";
2811 }
2812 }
2813 if(project->first(variableName: "QMAKE_ORIG_TARGET") != "qmake") {
2814 t << "qmake: FORCE\n\t@" << qmake << Qt::endl << Qt::endl;
2815 if (!noDummyQmakeAll)
2816 t << "qmake_all: FORCE\n\n";
2817 }
2818}
2819
2820QFileInfo
2821MakefileGenerator::fileInfo(QString file) const
2822{
2823 Q_CONSTINIT static QHash<FileInfoCacheKey, QFileInfo> *cache = nullptr;
2824 static QFileInfo noInfo = QFileInfo();
2825 if(!cache) {
2826 cache = new QHash<FileInfoCacheKey, QFileInfo>;
2827 qmakeAddCacheClear(func: qmakeDeleteCacheClear<QHash<FileInfoCacheKey, QFileInfo> >, (void**)&cache);
2828 }
2829 FileInfoCacheKey cacheKey(file);
2830 QFileInfo value = cache->value(key: cacheKey, defaultValue: noInfo);
2831 if (value != noInfo)
2832 return value;
2833
2834 QFileInfo fi(file);
2835 if (fi.exists())
2836 cache->insert(key: cacheKey, value: fi);
2837 return fi;
2838}
2839
2840MakefileGenerator::LibFlagType
2841MakefileGenerator::parseLibFlag(const ProString &flag, ProString *arg)
2842{
2843 if (flag.startsWith(sub: "-L")) {
2844 *arg = flag.mid(off: 2);
2845 return LibFlagPath;
2846 }
2847 if (flag.startsWith(sub: "-l")) {
2848 *arg = flag.mid(off: 2);
2849 return LibFlagLib;
2850 }
2851 if (flag.startsWith(c: '-'))
2852 return LibFlagOther;
2853 return LibFlagFile;
2854}
2855
2856ProStringList
2857MakefileGenerator::fixLibFlags(const ProKey &var)
2858{
2859 const ProStringList &in = project->values(v: var);
2860 ProStringList ret;
2861
2862 ret.reserve(asize: in.size());
2863 for (const ProString &v : in)
2864 ret << fixLibFlag(lib: v);
2865 return ret;
2866}
2867
2868ProString MakefileGenerator::fixLibFlag(const ProString &)
2869{
2870 qFatal(msg: "MakefileGenerator::fixLibFlag() called");
2871 return ProString();
2872}
2873
2874ProString
2875MakefileGenerator::escapeFilePath(const ProString &path) const
2876{
2877 return ProString(escapeFilePath(path: path.toQString()));
2878}
2879
2880QStringList
2881MakefileGenerator::escapeFilePaths(const QStringList &paths) const
2882{
2883 QStringList ret;
2884 for(int i = 0; i < paths.size(); ++i)
2885 ret.append(t: escapeFilePath(path: paths.at(i)));
2886 return ret;
2887}
2888
2889ProStringList
2890MakefileGenerator::escapeFilePaths(const ProStringList &paths) const
2891{
2892 ProStringList ret;
2893 const int size = paths.size();
2894 ret.reserve(asize: size);
2895 for (int i = 0; i < size; ++i)
2896 ret.append(t: escapeFilePath(path: paths.at(i)));
2897 return ret;
2898}
2899
2900QString
2901MakefileGenerator::escapeDependencyPath(const QString &path) const
2902{
2903 QString ret = path;
2904 if (!ret.isEmpty()) {
2905 // Unix make semantics, to be inherited by unix and mingw generators.
2906#ifdef Q_OS_UNIX
2907 // When running on Unix, we need to escape colons (which may appear
2908 // anywhere in a path, and would be mis-parsed as dependency separators).
2909 static const QRegularExpression criticalChars(QStringLiteral("([\t :#])"));
2910#else
2911 // MinGW make has a hack for colons which denote drive letters, and no
2912 // other colons may appear in paths. And escaping colons actually breaks
2913 // the make from the Android SDK.
2914 static const QRegularExpression criticalChars(QStringLiteral("([\t #])"));
2915#endif
2916 ret.replace(re: criticalChars, QStringLiteral("\\\\1"));
2917 ret.replace(c: QLatin1Char('='), QStringLiteral("$(EQ)"));
2918 debug_msg(level: 2, fmt: "escapeDependencyPath: %s -> %s", path.toLatin1().constData(), ret.toLatin1().constData());
2919 }
2920 return ret;
2921}
2922
2923ProString
2924MakefileGenerator::escapeDependencyPath(const ProString &path) const
2925{
2926 return ProString(escapeDependencyPath(path: path.toQString()));
2927}
2928
2929QStringList
2930MakefileGenerator::escapeDependencyPaths(const QStringList &paths) const
2931{
2932 QStringList ret;
2933 const int size = paths.size();
2934 ret.reserve(asize: size);
2935 for (int i = 0; i < size; ++i)
2936 ret.append(t: escapeDependencyPath(path: paths.at(i)));
2937 return ret;
2938}
2939
2940ProStringList
2941MakefileGenerator::escapeDependencyPaths(const ProStringList &paths) const
2942{
2943 ProStringList ret;
2944 const int size = paths.size();
2945 ret.reserve(asize: size);
2946 for (int i = 0; i < size; ++i)
2947 ret.append(t: escapeDependencyPath(path: paths.at(i).toQString()));
2948 return ret;
2949}
2950
2951QStringList
2952MakefileGenerator::finalizeDependencyPaths(const QStringList &paths) const
2953{
2954 QStringList ret;
2955 const int size = paths.size();
2956 ret.reserve(asize: size);
2957 for (int i = 0; i < size; ++i)
2958 ret.append(t: escapeDependencyPath(path: Option::fixPathToTargetOS(in: paths.at(i), fix_env: false)));
2959 return ret;
2960}
2961
2962QStringList
2963MakefileGenerator::fileFixify(const QStringList &files, FileFixifyTypes fix, bool canon) const
2964{
2965 if(files.isEmpty())
2966 return files;
2967 QStringList ret;
2968 for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
2969 if(!(*it).isEmpty())
2970 ret << fileFixify(file: (*it), fix, canon);
2971 }
2972 return ret;
2973}
2974
2975QString
2976MakefileGenerator::fileFixify(const QString &file, FileFixifyTypes fix, bool canon) const
2977{
2978 if(file.isEmpty())
2979 return file;
2980 QString ret = file;
2981
2982 //do the fixin'
2983 QString orig_file = ret;
2984 if(ret.startsWith(c: QLatin1Char('~'))) {
2985 if(ret.startsWith(s: QLatin1String("~/")))
2986 ret = QDir::homePath() + ret.mid(position: 1);
2987 else
2988 warn_msg(t: WarnLogic, fmt: "Unable to expand ~ in %s", ret.toLatin1().constData());
2989 }
2990 if ((fix & FileFixifyAbsolute)
2991 || (!(fix & FileFixifyRelative) && project->isActiveConfig(config: "no_fixpath"))) {
2992 if ((fix & FileFixifyAbsolute) && QDir::isRelativePath(path: ret)) {
2993 QString pwd = !(fix & FileFixifyFromOutdir) ? project->projectDir() : Option::output_dir;
2994 {
2995 QFileInfo in_fi(fileInfo(file: pwd));
2996 if (in_fi.exists())
2997 pwd = in_fi.canonicalFilePath();
2998 }
2999 if (!pwd.endsWith(c: QLatin1Char('/')))
3000 pwd += QLatin1Char('/');
3001 ret.prepend(s: pwd);
3002 }
3003 ret = Option::fixPathToTargetOS(in: ret, fix_env: false, canonical: canon);
3004 } else { //fix it..
3005 QString out_dir = (fix & FileFixifyToIndir) ? project->projectDir() : Option::output_dir;
3006 QString in_dir = !(fix & FileFixifyFromOutdir) ? project->projectDir() : Option::output_dir;
3007 {
3008 QFileInfo in_fi(fileInfo(file: in_dir));
3009 if(in_fi.exists())
3010 in_dir = in_fi.canonicalFilePath();
3011 QFileInfo out_fi(fileInfo(file: out_dir));
3012 if(out_fi.exists())
3013 out_dir = out_fi.canonicalFilePath();
3014 }
3015
3016 QString qfile(Option::normalizePath(in: ret));
3017 QFileInfo qfileinfo(fileInfo(file: qfile));
3018 if(out_dir != in_dir || !qfileinfo.isRelative()) {
3019 if(qfileinfo.isRelative()) {
3020 ret = in_dir + "/" + qfile;
3021 qfileinfo.setFile(ret);
3022 }
3023 ret = Option::fixPathToTargetOS(in: ret, fix_env: false, canonical: canon);
3024 QString match_dir = Option::fixPathToTargetOS(in: out_dir, fix_env: false, canonical: canon);
3025 if(ret == match_dir) {
3026 ret = "";
3027 } else if(ret.startsWith(s: match_dir + Option::dir_sep)) {
3028 ret = ret.mid(position: match_dir.size() + Option::dir_sep.size());
3029 } else {
3030 //figure out the depth
3031 int depth = 4;
3032 if(Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE ||
3033 Option::qmake_mode == Option::QMAKE_GENERATE_PRL) {
3034 if(project && !project->isEmpty(v: "QMAKE_PROJECT_DEPTH"))
3035 depth = project->first(variableName: "QMAKE_PROJECT_DEPTH").toInt();
3036 else if(Option::mkfile::cachefile_depth != -1)
3037 depth = Option::mkfile::cachefile_depth;
3038 }
3039 //calculate how much can be removed
3040 QString dot_prefix;
3041 for(int i = 1; i <= depth; i++) {
3042 int sl = match_dir.lastIndexOf(s: Option::dir_sep);
3043 if(sl == -1)
3044 break;
3045 match_dir = match_dir.left(n: sl);
3046 if(match_dir.isEmpty())
3047 break;
3048 if(ret.startsWith(s: match_dir + Option::dir_sep)) {
3049 //concat
3050 int remlen = ret.size() - (match_dir.size() + 1);
3051 if(remlen < 0)
3052 remlen = 0;
3053 ret = ret.right(n: remlen);
3054 //prepend
3055 for(int o = 0; o < i; o++)
3056 dot_prefix += ".." + Option::dir_sep;
3057 break;
3058 }
3059 }
3060 ret.prepend(s: dot_prefix);
3061 }
3062 } else {
3063 ret = Option::fixPathToTargetOS(in: ret, fix_env: false, canonical: canon);
3064 }
3065 }
3066 if(ret.isEmpty())
3067 ret = ".";
3068 debug_msg(level: 3, fmt: "Fixed[%d,%d] %s :: to :: %s [%s::%s]",
3069 int(fix), canon, orig_file.toLatin1().constData(), ret.toLatin1().constData(),
3070 qmake_getpwd().toLatin1().constData(), Option::output_dir.toLatin1().constData());
3071 return ret;
3072}
3073
3074QMakeLocalFileName
3075MakefileGenerator::fixPathForFile(const QMakeLocalFileName &file, bool forOpen)
3076{
3077 if(forOpen)
3078 return QMakeLocalFileName(fileFixify(file: file.real(), fix: FileFixifyBackwards));
3079 return QMakeLocalFileName(fileFixify(file: file.real()));
3080}
3081
3082QFileInfo
3083MakefileGenerator::findFileInfo(const QMakeLocalFileName &file)
3084{
3085 return fileInfo(file: file.local());
3086}
3087
3088QMakeLocalFileName
3089MakefileGenerator::findFileForDep(const QMakeLocalFileName &dep, const QMakeLocalFileName &file)
3090{
3091 QMakeLocalFileName ret;
3092 if(!project->isEmpty(v: "SKIP_DEPENDS")) {
3093 bool found = false;
3094 const ProStringList &nodeplist = project->values(v: "SKIP_DEPENDS");
3095 for (ProStringList::ConstIterator it = nodeplist.begin();
3096 it != nodeplist.end(); ++it) {
3097 QRegularExpression regx((*it).toQString());
3098 if (regx.match(subject: dep.local()).hasMatch()) {
3099 found = true;
3100 break;
3101 }
3102 }
3103 if(found)
3104 return ret;
3105 }
3106
3107 ret = QMakeSourceFileInfo::findFileForDep(dep, file);
3108 if(!ret.isNull())
3109 return ret;
3110
3111 //these are some "hacky" heuristics it will try to do on an include
3112 //however these can be turned off at runtime, I'm not sure how
3113 //reliable these will be, most likely when problems arise turn it off
3114 //and see if they go away..
3115 if(Option::mkfile::do_dep_heuristics) {
3116 if(depHeuristicsCache.contains(key: dep.real()))
3117 return depHeuristicsCache[dep.real()];
3118
3119 if(Option::output_dir != qmake_getpwd()
3120 && QDir::isRelativePath(path: dep.real())) { //is it from the shadow tree
3121 QList<QMakeLocalFileName> depdirs = QMakeSourceFileInfo::dependencyPaths();
3122 depdirs.prepend(t: fileInfo(file: file.real()).absoluteDir().path());
3123 QString pwd = qmake_getpwd();
3124 if(pwd.at(i: pwd.size()-1) != '/')
3125 pwd += '/';
3126 for(int i = 0; i < depdirs.size(); i++) {
3127 QString dir = depdirs.at(i).real();
3128 if(!QDir::isRelativePath(path: dir) && dir.startsWith(s: pwd))
3129 dir = dir.mid(position: pwd.size());
3130 if(QDir::isRelativePath(path: dir)) {
3131 if(!dir.endsWith(s: Option::dir_sep))
3132 dir += Option::dir_sep;
3133 QString shadow = fileFixify(file: dir + dep.local(), fix: FileFixifyBackwards);
3134 if(exists(file: shadow)) {
3135 ret = QMakeLocalFileName(shadow);
3136 goto found_dep_from_heuristic;
3137 }
3138 }
3139 }
3140 }
3141 { //is it from an EXTRA_TARGET
3142 const QString dep_basename = dep.local().section(asep: '/', astart: -1);
3143 const ProStringList &qut = project->values(v: "QMAKE_EXTRA_TARGETS");
3144 for (ProStringList::ConstIterator it = qut.begin(); it != qut.end(); ++it) {
3145 QString targ = var(var: ProKey(*it + ".target"));
3146 if(targ.isEmpty())
3147 targ = (*it).toQString();
3148 QString out = Option::fixPathToTargetOS(in: targ);
3149 if(out == dep.real() || out.section(in_sep: Option::dir_sep, start: -1) == dep_basename) {
3150 ret = QMakeLocalFileName(out);
3151 goto found_dep_from_heuristic;
3152 }
3153 }
3154 }
3155 { //is it from an EXTRA_COMPILER
3156 const QString dep_basename = dep.local().section(asep: '/', astart: -1);
3157 const ProStringList &quc = project->values(v: "QMAKE_EXTRA_COMPILERS");
3158 for (ProStringList::ConstIterator it = quc.begin(); it != quc.end(); ++it) {
3159 const ProString &tmp_out = project->first(variableName: ProKey(*it + ".output"));
3160 if(tmp_out.isEmpty())
3161 continue;
3162 const ProStringList &tmp = project->values(v: ProKey(*it + ".input"));
3163 for (ProStringList::ConstIterator it2 = tmp.begin(); it2 != tmp.end(); ++it2) {
3164 const ProStringList &inputs = project->values(v: (*it2).toKey());
3165 for (ProStringList::ConstIterator input = inputs.begin(); input != inputs.end(); ++input) {
3166 QString out = Option::fixPathToTargetOS(
3167 in: replaceExtraCompilerVariables(val: tmp_out.toQString(), in: (*input).toQString(), out: QString(), forShell: NoShell));
3168 if (out == dep.real() || out.section(in_sep: Option::dir_sep, start: -1) == dep_basename) {
3169 ret = QMakeLocalFileName(fileFixify(file: out, fix: FileFixifyBackwards));
3170 goto found_dep_from_heuristic;
3171 }
3172 }
3173 }
3174 }
3175 }
3176 found_dep_from_heuristic:
3177 depHeuristicsCache.insert(key: dep.real(), value: ret);
3178 }
3179 return ret;
3180}
3181
3182QStringList
3183&MakefileGenerator::findDependencies(const QString &file)
3184{
3185 const QString fixedFile = fileFixify(file);
3186 if(!dependsCache.contains(key: fixedFile)) {
3187#if 1
3188 QStringList deps = QMakeSourceFileInfo::dependencies(file);
3189 if(file != fixedFile)
3190 deps += QMakeSourceFileInfo::dependencies(file: fixedFile);
3191#else
3192 QStringList deps = QMakeSourceFileInfo::dependencies(fixedFile);
3193#endif
3194 dependsCache.insert(key: fixedFile, value: deps);
3195 }
3196 return dependsCache[fixedFile];
3197}
3198
3199QString
3200MakefileGenerator::specdir()
3201{
3202 if (spec.isEmpty())
3203 spec = fileFixify(file: project->specDir());
3204 return spec;
3205}
3206
3207bool
3208MakefileGenerator::openOutput(QFile &file, const QString &build) const
3209{
3210 debug_msg(level: 3, fmt: "asked to open output file '%s' in %s",
3211 qPrintable(file.fileName()), qPrintable(Option::output_dir));
3212
3213 if (file.fileName().isEmpty()) {
3214 file.setFileName(!project->isEmpty(v: "MAKEFILE")
3215 ? project->first(variableName: "MAKEFILE").toQString() : "Makefile");
3216 }
3217
3218 file.setFileName(QDir(Option::output_dir).absoluteFilePath(fileName: file.fileName()));
3219
3220 if (!build.isEmpty())
3221 file.setFileName(file.fileName() + "." + build);
3222
3223 if (project->isEmpty(v: "QMAKE_MAKEFILE"))
3224 project->values(v: "QMAKE_MAKEFILE").append(t: file.fileName());
3225
3226 // Make required directories. Note that we do this based on the
3227 // filename, not Option::output_dir, as the filename may include
3228 // generator specific directories not included in output_dir.
3229 int slsh = file.fileName().lastIndexOf(c: '/');
3230 if (slsh != -1)
3231 mkdir(in_path: file.fileName().left(n: slsh));
3232
3233 debug_msg(level: 3, fmt: "opening output file %s", qPrintable(file.fileName()));
3234 return file.open(flags: QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
3235}
3236
3237QString
3238MakefileGenerator::pkgConfigFileName(bool fixify)
3239{
3240 QString ret = project->first(variableName: "QMAKE_PKGCONFIG_FILE").toQString();
3241 if (ret.isEmpty()) {
3242 ret = project->first(variableName: "TARGET").toQString();
3243 int slsh = ret.lastIndexOf(s: Option::dir_sep);
3244 if (slsh != -1)
3245 ret = ret.right(n: ret.size() - slsh - 1);
3246 if (ret.startsWith(s: "lib"))
3247 ret = ret.mid(position: 3);
3248 int dot = ret.indexOf(c: '.');
3249 if (dot != -1)
3250 ret = ret.left(n: dot);
3251 }
3252 ret += Option::pkgcfg_ext;
3253 QString subdir = project->first(variableName: "QMAKE_PKGCONFIG_DESTDIR").toQString();
3254 if(!subdir.isEmpty()) {
3255 // initOutPaths() appends dir_sep, but just to be safe..
3256 if (!subdir.endsWith(s: Option::dir_sep))
3257 ret.prepend(s: Option::dir_sep);
3258 ret.prepend(s: subdir);
3259 }
3260 if(fixify) {
3261 if(QDir::isRelativePath(path: ret) && !project->isEmpty(v: "DESTDIR"))
3262 ret.prepend(s: project->first(variableName: "DESTDIR").toQString());
3263 ret = fileFixify(file: ret, fix: FileFixifyBackwards);
3264 }
3265 return ret;
3266}
3267
3268QString
3269MakefileGenerator::pkgConfigPrefix() const
3270{
3271 if(!project->isEmpty(v: "QMAKE_PKGCONFIG_PREFIX"))
3272 return project->first(variableName: "QMAKE_PKGCONFIG_PREFIX").toQString();
3273 return project->propertyValue(val: ProKey("QT_INSTALL_PREFIX")).toQString();
3274}
3275
3276QString
3277MakefileGenerator::pkgConfigFixPath(QString path) const
3278{
3279 QString prefix = pkgConfigPrefix();
3280 if(path.startsWith(s: prefix))
3281 path.replace(before: prefix, after: QLatin1String("${prefix}"));
3282 return path;
3283}
3284
3285void
3286MakefileGenerator::writePkgConfigFile()
3287{
3288 QString fname = pkgConfigFileName();
3289 mkdir(in_path: fileInfo(file: fname).path());
3290 QFile ft(fname);
3291 if(!ft.open(flags: QIODevice::WriteOnly))
3292 return;
3293 QString ffname(fileFixify(file: fname));
3294 project->values(v: "ALL_DEPS").append(t: ffname);
3295 project->values(v: "QMAKE_DISTCLEAN").append(t: ffname);
3296 QTextStream t(&ft);
3297
3298 QString prefix = pkgConfigPrefix();
3299 QString libDir = project->first(variableName: "QMAKE_PKGCONFIG_LIBDIR").toQString();
3300 if(libDir.isEmpty())
3301 libDir = prefix + "/lib";
3302 QString includeDir = project->first(variableName: "QMAKE_PKGCONFIG_INCDIR").toQString();
3303 if(includeDir.isEmpty())
3304 includeDir = prefix + "/include";
3305
3306 t << "prefix=" << prefix << Qt::endl;
3307 t << "exec_prefix=${prefix}\n"
3308 << "libdir=" << pkgConfigFixPath(path: libDir) << "\n"
3309 << "includedir=" << pkgConfigFixPath(path: includeDir) << Qt::endl;
3310 t << Qt::endl;
3311
3312 //extra PKGCONFIG variables
3313 const ProStringList &pkgconfig_vars = project->values(v: "QMAKE_PKGCONFIG_VARIABLES");
3314 for(int i = 0; i < pkgconfig_vars.size(); ++i) {
3315 const ProString &var = project->first(variableName: ProKey(pkgconfig_vars.at(i) + ".name"));
3316 QString val = project->values(v: ProKey(pkgconfig_vars.at(i) + ".value")).join(sep: ' ');
3317 if(var.isEmpty())
3318 continue;
3319 if(val.isEmpty()) {
3320 const ProStringList &var_vars = project->values(v: ProKey(pkgconfig_vars.at(i) + ".variable"));
3321 for(int v = 0; v < var_vars.size(); ++v) {
3322 const ProStringList &vars = project->values(v: var_vars.at(i: v).toKey());
3323 for(int var = 0; var < vars.size(); ++var) {
3324 if(!val.isEmpty())
3325 val += " ";
3326 val += pkgConfigFixPath(path: vars.at(i: var).toQString());
3327 }
3328 }
3329 }
3330 if (!val.isEmpty())
3331 t << var << "=" << val << Qt::endl;
3332 }
3333
3334 t << Qt::endl;
3335
3336 QString name = project->first(variableName: "QMAKE_PKGCONFIG_NAME").toQString();
3337 if(name.isEmpty()) {
3338 name = project->first(variableName: "QMAKE_ORIG_TARGET").toQString().toLower();
3339 name.replace(i: 0, len: 1, after: name[0].toUpper());
3340 }
3341 t << "Name: " << name << Qt::endl;
3342 QString desc = project->values(v: "QMAKE_PKGCONFIG_DESCRIPTION").join(sep: ' ');
3343 if(desc.isEmpty()) {
3344 if(name.isEmpty()) {
3345 desc = project->first(variableName: "QMAKE_ORIG_TARGET").toQString().toLower();
3346 desc.replace(i: 0, len: 1, after: desc[0].toUpper());
3347 } else {
3348 desc = name;
3349 }
3350 if(project->first(variableName: "TEMPLATE") == "lib") {
3351 if(project->isActiveConfig(config: "plugin"))
3352 desc += " Plugin";
3353 else
3354 desc += " Library";
3355 } else if(project->first(variableName: "TEMPLATE") == "app") {
3356 desc += " Application";
3357 }
3358 }
3359 t << "Description: " << desc << Qt::endl;
3360 ProString version = project->first(variableName: "QMAKE_PKGCONFIG_VERSION");
3361 if (version.isEmpty())
3362 version = project->first(variableName: "VERSION");
3363 if (!version.isEmpty())
3364 t << "Version: " << version << Qt::endl;
3365
3366 if (project->first(variableName: "TEMPLATE") == "lib") {
3367 // libs
3368 t << "Libs: ";
3369 QString pkgConfiglibName;
3370 if (target_mode == TARG_MAC_MODE && project->isActiveConfig(config: "lib_bundle")) {
3371 if (libDir != QLatin1String("/Library/Frameworks"))
3372 t << "-F${libdir} ";
3373 ProString bundle;
3374 if (!project->isEmpty(v: "QMAKE_FRAMEWORK_BUNDLE_NAME"))
3375 bundle = project->first(variableName: "QMAKE_FRAMEWORK_BUNDLE_NAME");
3376 else
3377 bundle = project->first(variableName: "TARGET");
3378 int suffix = bundle.lastIndexOf(s: ".framework");
3379 if (suffix != -1)
3380 bundle = bundle.left(len: suffix);
3381 t << "-framework ";
3382 pkgConfiglibName = bundle.toQString();
3383 } else {
3384 if (!project->values(v: "QMAKE_DEFAULT_LIBDIRS").contains(str: libDir))
3385 t << "-L${libdir} ";
3386 pkgConfiglibName = "-l" + project->first(variableName: "QMAKE_ORIG_TARGET");
3387 if (project->isActiveConfig(config: "shared"))
3388 pkgConfiglibName += project->first(variableName: "TARGET_VERSION_EXT").toQString();
3389 }
3390 t << shellQuote(str: pkgConfiglibName) << " \n";
3391
3392 if (project->isActiveConfig(config: "staticlib")) {
3393 ProStringList libs;
3394 libs << "LIBS"; // FIXME: this should not be conditional on staticlib
3395 libs << "LIBS_PRIVATE";
3396 libs << "QMAKE_LIBS"; // FIXME: this should not be conditional on staticlib
3397 libs << "QMAKE_LIBS_PRIVATE";
3398 libs << "QMAKE_LFLAGS_THREAD"; //not sure about this one, but what about things like -pthread?
3399 t << "Libs.private:";
3400 for (ProStringList::ConstIterator it = libs.cbegin(); it != libs.cend(); ++it)
3401 t << ' ' << fixLibFlags(var: (*it).toKey()).join(sep: ' ');
3402 t << Qt::endl;
3403 }
3404 }
3405
3406 // flags
3407 // ### too many
3408 t << "Cflags: "
3409 // << var("QMAKE_CXXFLAGS") << " "
3410 << varGlue(var: "PRL_EXPORT_DEFINES",before: "-D",glue: " -D",after: " ")
3411 << varGlue(var: "PRL_EXPORT_CXXFLAGS", before: "", glue: " ", after: " ")
3412 << varGlue(var: "QMAKE_PKGCONFIG_CFLAGS", before: "", glue: " ", after: " ")
3413 // << varGlue("DEFINES","-D"," -D"," ")
3414 ;
3415 if (!project->values(v: "QMAKE_DEFAULT_INCDIRS").contains(str: includeDir))
3416 t << "-I${includedir}";
3417 if (target_mode == TARG_MAC_MODE && project->isActiveConfig(config: "lib_bundle")
3418 && libDir != QLatin1String("/Library/Frameworks")) {
3419 t << " -F${libdir}";
3420 }
3421 t << Qt::endl;
3422
3423 // requires
3424 const QString requiresString = project->values(v: "QMAKE_PKGCONFIG_REQUIRES").join(sep: ' ');
3425 if (!requiresString.isEmpty()) {
3426 t << "Requires: " << requiresString << Qt::endl;
3427 }
3428
3429 t << Qt::endl;
3430}
3431
3432static QString windowsifyPath(const QString &str)
3433{
3434 // The paths are escaped in prl files, so every slash needs to turn into two backslashes.
3435 // Then each backslash needs to be escaped for sed. And another level for C quoting here.
3436 return QString(str).replace(c: '/', after: QLatin1String("\\\\\\\\"));
3437}
3438
3439QString MakefileGenerator::createSedArgs(const ProKey &replace_rule, const QString &file_name) const
3440{
3441 QString sedargs;
3442 if (!project->isEmpty(v: replace_rule) && !project->isActiveConfig(config: "no_sed_meta_install")) {
3443 const ProStringList &replace_rules = project->values(v: replace_rule);
3444 for (int r = 0; r < replace_rules.size(); ++r) {
3445 const ProString match = project->first(variableName: ProKey(replace_rules.at(i: r) + ".match")),
3446 replace = project->first(variableName: ProKey(replace_rules.at(i: r) + ".replace")),
3447 filename = project->first(variableName: ProKey(replace_rules.at(i: r) + ".filename"));
3448 if (!match.isEmpty() /*&& match != replace*/
3449 && (filename.isEmpty() || filename == file_name)) {
3450 sedargs += " -e " + shellQuote(str: "s," + match + "," + replace + ",g");
3451 if (isWindowsShell()
3452 && project->first(variableName: ProKey(replace_rules.at(i: r) + ".CONFIG")).contains(s: "path"))
3453 sedargs += " -e "
3454 + shellQuote(str: "s," + windowsifyPath(str: match.toQString()) + ","
3455 + windowsifyPath(str: replace.toQString()) + ",gi");
3456 }
3457 }
3458 }
3459 return sedargs;
3460}
3461
3462QString MakefileGenerator::installMetaFile(const ProKey &replace_rule, const QString &src,
3463 const QString &dst) const
3464{
3465 QString ret;
3466 QString sedargs = createSedArgs(replace_rule);
3467 if (sedargs.isEmpty()) {
3468 ret = "$(INSTALL_FILE) " + escapeFilePath(path: src) + ' ' + escapeFilePath(path: dst);
3469 } else {
3470 ret = "$(SED) " + sedargs + ' ' + escapeFilePath(path: src) + " > " + escapeFilePath(path: dst);
3471 }
3472 return ret;
3473}
3474
3475QString MakefileGenerator::shellQuote(const QString &str) const
3476{
3477 return isWindowsShell() ? IoUtils::shellQuoteWin(arg: str) : IoUtils::shellQuoteUnix(arg: str);
3478}
3479
3480/*
3481 * Returns the name of the variable that contains the fully resolved target
3482 * (including DESTDIR) of this generator.
3483 */
3484ProKey MakefileGenerator::fullTargetVariable() const
3485{
3486 return "TARGET";
3487}
3488
3489/*
3490 * Create a response file and return its file name.
3491 */
3492QString MakefileGenerator::createResponseFile(
3493 const QString &baseName,
3494 const ProStringList &objList,
3495 const QString &prefix) const
3496{
3497 QString fileName = baseName + '.' + var(var: "QMAKE_ORIG_TARGET");
3498 if (!var(var: "BUILD_NAME").isEmpty())
3499 fileName += '.' + var(var: "BUILD_NAME");
3500 if (!var(var: "MAKEFILE").isEmpty())
3501 fileName += '.' + var(var: "MAKEFILE");
3502 QString filePath = Option::output_dir + QDir::separator() + fileName;
3503 QFile file(filePath);
3504 if (!file.open(flags: QIODevice::WriteOnly | QIODevice::Text)) {
3505 fprintf(stderr, format: "Error: Cannot open response file '%s' for writing.\n",
3506 qPrintable(filePath));
3507 exit(status: 1);
3508 }
3509 QTextStream t(&file);
3510 for (ProStringList::ConstIterator it = objList.constBegin(); it != objList.constEnd(); ++it) {
3511 QString path = (*it).toQString();
3512 // In response files, whitespace and special characters are
3513 // escaped with a backslash; backslashes themselves can either
3514 // be escaped into double backslashes, or, as this is a list of
3515 // path names, converted to forward slashes.
3516 path.replace(c: QLatin1Char('\\'), after: QLatin1String("/"))
3517 .replace(c: QLatin1Char(' '), after: QLatin1String("\\ "))
3518 .replace(c: QLatin1Char('\t'), after: QLatin1String("\\\t"))
3519 .replace(c: QLatin1Char('"'), after: QLatin1String("\\\""))
3520 .replace(c: QLatin1Char('\''), after: QLatin1String("\\'"));
3521 t << prefix << path << Qt::endl;
3522 }
3523 t.flush();
3524 file.close();
3525 return fileName;
3526}
3527
3528/*
3529 * If the threshold for response files are overstepped, create a response file for the linker
3530 * command line.
3531 */
3532MakefileGenerator::LinkerResponseFileInfo MakefileGenerator::maybeCreateLinkerResponseFile() const
3533{
3534 bool useLinkObjectMax = false;
3535 bool ok;
3536 int threshold = project->first(variableName: "QMAKE_RESPONSEFILE_THRESHOLD").toInt(ok: &ok);
3537 if (!ok) {
3538 threshold = project->first(variableName: "QMAKE_LINK_OBJECT_MAX").toInt(ok: &ok);
3539 if (ok)
3540 useLinkObjectMax = true;
3541 }
3542 if (!ok)
3543 return {};
3544
3545 ProStringList linkerInputs = project->values(v: "OBJECTS");
3546 if (useLinkObjectMax) {
3547 // When using QMAKE_LINK_OBJECT_MAX, the number of object files (regardless of their path
3548 // length) decides whether to use a response file. This is far from being a useful
3549 // heuristic but let's keep this behavior for backwards compatibility.
3550 if (linkerInputs.size() < threshold)
3551 return {};
3552 } else {
3553 // When using QMAKE_REPONSEFILE_THRESHOLD, try to determine the command line length of the
3554 // inputs.
3555 linkerInputs += project->values(v: "LIBS");
3556 int totalLength = std::accumulate(first: linkerInputs.cbegin(), last: linkerInputs.cend(), init: 0,
3557 binary_op: [](int total, const ProString &input) {
3558 return total + input.size() + 1;
3559 });
3560 if (totalLength < threshold)
3561 return {};
3562 }
3563
3564 return {
3565 .filePath: createResponseFile(baseName: fileVar(var: "OBJECTS_DIR") + var(var: "QMAKE_LINK_OBJECT_SCRIPT"), objList: linkerInputs),
3566 .onlyObjects: useLinkObjectMax
3567 };
3568}
3569
3570QT_END_NAMESPACE
3571

source code of qtbase/qmake/generators/makefile.cpp