1// Copyright (C) 2020 The Qt Company Ltd.
2// Copyright (C) 2016 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
4
5#include "project.h"
6#include "property.h"
7#include "option.h"
8#include "cachekeys.h"
9#include "metamakefile.h"
10#include <qcoreapplication.h>
11#include <qnamespace.h>
12#include <qdebug.h>
13#include <qregularexpression.h>
14#include <qdir.h>
15#include <qdiriterator.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <ctype.h>
19#include <fcntl.h>
20#include <sys/types.h>
21#include <sys/stat.h>
22
23#if defined(Q_OS_UNIX)
24#include <errno.h>
25#include <unistd.h>
26#endif
27
28#ifdef Q_OS_WIN
29# include <qt_windows.h>
30#endif
31
32using namespace QMakeInternal;
33
34QT_BEGIN_NAMESPACE
35
36#ifdef Q_OS_WIN
37
38struct SedSubst {
39 QRegularExpression from;
40 QString to;
41};
42Q_DECLARE_TYPEINFO(SedSubst, Q_RELOCATABLE_TYPE);
43
44static int doSed(int argc, char **argv)
45{
46 QList<SedSubst> substs;
47 QList<const char *> inFiles;
48 for (int i = 0; i < argc; i++) {
49 if (!strcmp(argv[i], "-e")) {
50 if (++i == argc) {
51 fprintf(stderr, "Error: sed option -e requires an argument\n");
52 return 3;
53 }
54 QString cmd = QString::fromLocal8Bit(argv[i]);
55 for (int j = 0; j < cmd.length(); j++) {
56 QChar c = cmd.at(j);
57 if (c.isSpace())
58 continue;
59 if (c != QLatin1Char('s')) {
60 fprintf(stderr, "Error: unrecognized sed command '%c'\n", c.toLatin1());
61 return 3;
62 }
63 QChar sep = ++j < cmd.length() ? cmd.at(j) : QChar();
64 QRegularExpression::PatternOptions matchcase = QRegularExpression::NoPatternOption;
65 bool escaped = false;
66 int phase = 1;
67 QStringList phases;
68 QString curr;
69 while (++j < cmd.length()) {
70 c = cmd.at(j);
71 if (!escaped) {
72 if (c == QLatin1Char(';'))
73 break;
74 if (c == QLatin1Char('\\')) {
75 escaped = true;
76 continue;
77 }
78 if (c == sep) {
79 phase++;
80 phases << curr;
81 curr.clear();
82 continue;
83 }
84 }
85 if (phase == 1
86 && (c == QLatin1Char('+') || c == QLatin1Char('?') || c == QLatin1Char('|')
87 || c == QLatin1Char('{') || c == QLatin1Char('}')
88 || c == QLatin1Char('(') || c == QLatin1Char(')'))) {
89 // translate sed rx to QRegularExpression
90 escaped ^= 1;
91 }
92 if (escaped) {
93 escaped = false;
94 curr += QLatin1Char('\\');
95 }
96 curr += c;
97 }
98 if (escaped) {
99 fprintf(stderr, "Error: unterminated escape sequence in sed s command\n");
100 return 3;
101 }
102 if (phase != 3) {
103 fprintf(stderr, "Error: sed s command requires three arguments (%d, %c, %s)\n", phase, sep.toLatin1(), qPrintable(curr));
104 return 3;
105 }
106 if (curr.contains(QLatin1Char('i'))) {
107 curr.remove(QLatin1Char('i'));
108 matchcase = QRegularExpression::CaseInsensitiveOption;
109 }
110 if (curr != QLatin1String("g")) {
111 fprintf(stderr, "Error: sed s command supports only g & i options; g is required\n");
112 return 3;
113 }
114 SedSubst subst;
115 subst.from = QRegularExpression(phases.at(0), matchcase);
116 subst.to = phases.at(1);
117 subst.to.replace(QLatin1String("\\\\"), QLatin1String("\\")); // QString::replace(rx, sub) groks \1, but not \\.
118 substs << subst;
119 }
120 } else if (argv[i][0] == '-' && argv[i][1] != 0) {
121 fprintf(stderr, "Error: unrecognized sed option '%s'\n", argv[i]);
122 return 3;
123 } else {
124 inFiles << argv[i];
125 }
126 }
127 if (inFiles.isEmpty())
128 inFiles << "-";
129 for (const char *inFile : std::as_const(inFiles)) {
130 FILE *f;
131 if (!strcmp(inFile, "-")) {
132 f = stdin;
133 } else if (!(f = fopen(inFile, "rb"))) {
134 perror(inFile);
135 return 1;
136 }
137 QTextStream is(f);
138 while (!is.atEnd()) {
139 QString line = is.readLine();
140 for (int i = 0; i < substs.size(); i++)
141 line.replace(substs.at(i).from, substs.at(i).to);
142 puts(qPrintable(line));
143 }
144 if (f != stdin)
145 fclose(f);
146 }
147 return 0;
148}
149
150static int doLink(int argc, char **argv)
151{
152 bool isSymlink = false;
153 bool force = false;
154 QList<const char *> inFiles;
155 for (int i = 0; i < argc; i++) {
156 if (!strcmp(argv[i], "-s")) {
157 isSymlink = true;
158 } else if (!strcmp(argv[i], "-f")) {
159 force = true;
160 } else if (argv[i][0] == '-') {
161 fprintf(stderr, "Error: unrecognized ln option '%s'\n", argv[i]);
162 return 3;
163 } else {
164 inFiles << argv[i];
165 }
166 }
167 if (inFiles.size() != 2) {
168 fprintf(stderr, "Error: this ln requires exactly two file arguments\n");
169 return 3;
170 }
171 if (!isSymlink) {
172 fprintf(stderr, "Error: this ln supports faking symlinks only\n");
173 return 3;
174 }
175 QString target = QString::fromLocal8Bit(inFiles[0]);
176 QString linkname = QString::fromLocal8Bit(inFiles[1]);
177
178 QDir destdir;
179 QFileInfo tfi(target);
180 QFileInfo lfi(linkname);
181 if (lfi.isDir()) {
182 destdir.setPath(linkname);
183 lfi.setFile(destdir, tfi.fileName());
184 } else {
185 destdir.setPath(lfi.path());
186 }
187 if (!destdir.exists()) {
188 fprintf(stderr, "Error: destination directory %s does not exist\n", qPrintable(destdir.path()));
189 return 1;
190 }
191 tfi.setFile(destdir.absoluteFilePath(tfi.filePath()));
192 if (!tfi.exists()) {
193 fprintf(stderr, "Error: this ln does not support symlinking non-existing targets\n");
194 return 3;
195 }
196 if (tfi.isDir()) {
197 fprintf(stderr, "Error: this ln does not support symlinking directories\n");
198 return 3;
199 }
200 if (lfi.exists()) {
201 if (!force) {
202 fprintf(stderr, "Error: %s exists\n", qPrintable(lfi.filePath()));
203 return 1;
204 }
205 if (!QFile::remove(lfi.filePath())) {
206 fprintf(stderr, "Error: cannot overwrite %s\n", qPrintable(lfi.filePath()));
207 return 1;
208 }
209 }
210 if (!QFile::copy(tfi.filePath(), lfi.filePath())) {
211 fprintf(stderr, "Error: cannot copy %s to %s\n",
212 qPrintable(tfi.filePath()), qPrintable(lfi.filePath()));
213 return 1;
214 }
215
216 return 0;
217}
218
219#endif
220
221static bool setFilePermissions(QFile &file, QFileDevice::Permissions permissions)
222{
223 if (file.setPermissions(permissions))
224 return true;
225 fprintf(stderr, format: "Error setting permissions on %s: %s\n",
226 qPrintable(file.fileName()), qPrintable(file.errorString()));
227 return false;
228}
229
230static bool copyFileTimes(QFile &targetFile, const QString &sourceFilePath,
231 bool mustEnsureWritability, QString *errorString)
232{
233#ifdef Q_OS_WIN
234 bool mustRestorePermissions = false;
235 QFileDevice::Permissions targetPermissions;
236 if (mustEnsureWritability) {
237 targetPermissions = targetFile.permissions();
238 if (!targetPermissions.testFlag(QFileDevice::WriteUser)) {
239 mustRestorePermissions = true;
240 if (!setFilePermissions(targetFile, targetPermissions | QFileDevice::WriteUser))
241 return false;
242 }
243 }
244#else
245 Q_UNUSED(mustEnsureWritability);
246#endif
247 if (!IoUtils::touchFile(targetFileName: targetFile.fileName(), referenceFileName: sourceFilePath, errorString))
248 return false;
249#ifdef Q_OS_WIN
250 if (mustRestorePermissions && !setFilePermissions(targetFile, targetPermissions))
251 return false;
252#endif
253 return true;
254}
255
256static int installFile(const QString &source, const QString &target, bool exe = false,
257 bool preservePermissions = false)
258{
259 QFile sourceFile(source);
260 QFile targetFile(target);
261 if (targetFile.exists()) {
262#ifdef Q_OS_WIN
263 targetFile.setPermissions(targetFile.permissions() | QFile::WriteUser);
264#endif
265 QFile::remove(fileName: target);
266 } else {
267 QDir::root().mkpath(dirPath: QFileInfo(target).absolutePath());
268 }
269
270 if (!sourceFile.copy(newName: target)) {
271 fprintf(stderr, format: "Error copying %s to %s: %s\n", source.toLatin1().constData(), qPrintable(target), qPrintable(sourceFile.errorString()));
272 return 3;
273 }
274
275 QFileDevice::Permissions targetPermissions = preservePermissions
276 ? sourceFile.permissions()
277 : (QFileDevice::ReadOwner | QFileDevice::WriteOwner
278 | QFileDevice::ReadUser | QFileDevice::WriteUser
279 | QFileDevice::ReadGroup | QFileDevice::ReadOther);
280 if (exe) {
281 targetPermissions |= QFileDevice::ExeOwner | QFileDevice::ExeUser |
282 QFileDevice::ExeGroup | QFileDevice::ExeOther;
283 }
284 if (!setFilePermissions(file&: targetFile, permissions: targetPermissions))
285 return 3;
286
287 QString error;
288 if (!copyFileTimes(targetFile, sourceFilePath: sourceFile.fileName(), mustEnsureWritability: preservePermissions, errorString: &error)) {
289 fprintf(stderr, format: "%s", qPrintable(error));
290 return 3;
291 }
292
293 return 0;
294}
295
296static int installFileOrDirectory(const QString &source, const QString &target,
297 bool preservePermissions = false)
298{
299 QFileInfo fi(source);
300 if (false) {
301#if defined(Q_OS_UNIX)
302 } else if (fi.isSymLink()) {
303 QString linkTarget;
304 if (!IoUtils::readLinkTarget(symlinkPath: fi.absoluteFilePath(), target: &linkTarget)) {
305 fprintf(stderr, format: "Could not read link %s: %s\n", qPrintable(fi.absoluteFilePath()), strerror(errno));
306 return 3;
307 }
308 QFile::remove(fileName: target);
309 if (::symlink(from: linkTarget.toLocal8Bit().constData(), to: target.toLocal8Bit().constData()) < 0) {
310 fprintf(stderr, format: "Could not create link: %s\n", strerror(errno));
311 return 3;
312 }
313#endif
314 } else if (fi.isDir()) {
315 QDir::current().mkpath(dirPath: target);
316
317 QDirIterator it(source, QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden);
318 while (it.hasNext()) {
319 const QFileInfo entry = it.nextFileInfo();
320 const QString &entryTarget = target + QDir::separator() + entry.fileName();
321
322 const int recursionResult = installFileOrDirectory(source: entry.filePath(), target: entryTarget, preservePermissions: true);
323 if (recursionResult != 0)
324 return recursionResult;
325 }
326 } else {
327 const int fileCopyResult = installFile(source, target, /*exe*/ false, preservePermissions);
328 if (fileCopyResult != 0)
329 return fileCopyResult;
330 }
331 return 0;
332}
333
334static int doQInstall(int argc, char **argv)
335{
336 bool installExecutable = false;
337 if (argc == 3 && !strcmp(s1: argv[0], s2: "-exe")) {
338 installExecutable = true;
339 --argc;
340 ++argv;
341 }
342
343 if (argc != 2 && !installExecutable) {
344 fprintf(stderr, format: "Error: usage: [-exe] source target\n");
345 return 3;
346 }
347
348 const QString source = QString::fromLocal8Bit(ba: argv[0]);
349 const QString target = QString::fromLocal8Bit(ba: argv[1]);
350
351 if (installExecutable)
352 return installFile(source, target, /*exe=*/true);
353 return installFileOrDirectory(source, target);
354}
355
356
357static int doInstall(int argc, char **argv)
358{
359 if (!argc) {
360 fprintf(stderr, format: "Error: -install requires further arguments\n");
361 return 3;
362 }
363#ifdef Q_OS_WIN
364 if (!strcmp(argv[0], "sed"))
365 return doSed(argc - 1, argv + 1);
366 if (!strcmp(argv[0], "ln"))
367 return doLink(argc - 1, argv + 1);
368#endif
369 if (!strcmp(s1: argv[0], s2: "qinstall"))
370 return doQInstall(argc: argc - 1, argv: argv + 1);
371 fprintf(stderr, format: "Error: unrecognized -install subcommand '%s'\n", argv[0]);
372 return 3;
373}
374
375
376#ifdef Q_OS_WIN
377
378static int dumpMacros(const wchar_t *cmdline)
379{
380 // from http://stackoverflow.com/questions/3665537/how-to-find-out-cl-exes-built-in-macros
381 int argc;
382 wchar_t **argv = CommandLineToArgvW(cmdline, &argc);
383 if (!argv)
384 return 2;
385 for (int i = 0; i < argc; ++i) {
386 if (argv[i][0] != L'-' || argv[i][1] != 'D')
387 continue;
388
389 wchar_t *value = wcschr(argv[i], L'=');
390 if (value) {
391 *value = 0;
392 ++value;
393 } else {
394 // point to the NUL at the end, so we don't print anything
395 value = argv[i] + wcslen(argv[i]);
396 }
397 wprintf(L"#define %Ls %Ls\n", argv[i] + 2, value);
398 }
399 return 0;
400}
401
402#endif // Q_OS_WIN
403
404/* This is to work around lame implementation on Darwin. It has been noted that the getpwd(3) function
405 is much too slow, and called much too often inside of Qt (every fileFixify). With this we use a locally
406 cached copy because I can control all the times it is set (because Qt never sets the pwd under me).
407*/
408static QString pwd;
409QString qmake_getpwd()
410{
411 if(pwd.isNull())
412 pwd = QDir::currentPath();
413 return pwd;
414}
415bool qmake_setpwd(const QString &p)
416{
417 if(QDir::setCurrent(p)) {
418 pwd = QDir::currentPath();
419 return true;
420 }
421 return false;
422}
423
424int runQMake(int argc, char **argv)
425{
426 QHashSeed::setDeterministicGlobalSeed();
427
428 // stderr is unbuffered by default, but stdout buffering depends on whether
429 // there is a terminal attached. Buffering can make output from stderr and stdout
430 // appear out of sync, so force stdout to be unbuffered as well.
431 // This is particularly important for things like QtCreator and scripted builds.
432 setvbuf(stdout, buf: (char *)NULL, _IONBF, n: 0);
433
434 // Workaround for inferior/missing command line tools on Windows: make our own!
435 if (argc >= 4 && !strcmp(s1: argv[1], s2: "-qtconf") && !strcmp(s1: argv[3], s2: "-install"))
436 return doInstall(argc: argc - 4, argv: argv + 4);
437 if (argc >= 2 && !strcmp(s1: argv[1], s2: "-install"))
438 return doInstall(argc: argc - 2, argv: argv + 2);
439
440#ifdef Q_OS_WIN
441 {
442 // Support running as Visual C++'s compiler
443 const wchar_t *cmdline = _wgetenv(L"MSC_CMD_FLAGS");
444 if (!cmdline || !*cmdline)
445 cmdline = _wgetenv(L"MSC_IDE_FLAGS");
446 if (cmdline && *cmdline)
447 return dumpMacros(cmdline);
448 }
449#endif
450
451 QMakeVfs vfs;
452 Option::vfs = &vfs;
453 QMakeGlobals globals;
454 Option::globals = &globals;
455
456 // parse command line
457 int ret = Option::init(argc, argv);
458 if(ret != Option::QMAKE_CMDLINE_SUCCESS) {
459 if ((ret & Option::QMAKE_CMDLINE_ERROR) != 0)
460 return 1;
461 return 0;
462 }
463
464 QString oldpwd = qmake_getpwd();
465
466 Option::output_dir = oldpwd; //for now this is the output dir
467 if (!Option::output.fileName().isEmpty() && Option::output.fileName() != "-") {
468 // The output 'filename', as given by the -o option, might include one
469 // or more directories, so we may need to rebase the output directory.
470 QFileInfo fi(Option::output);
471
472 QDir dir(QDir::cleanPath(path: fi.isDir() ? fi.absoluteFilePath() : fi.absolutePath()));
473
474 // Don't treat Xcode project directory as part of OUT_PWD
475 if (dir.dirName().endsWith(s: QLatin1String(".xcodeproj"))) {
476 // Note: we're intentionally not using cdUp(), as the dir may not exist
477 dir.setPath(QDir::cleanPath(path: dir.filePath(fileName: "..")));
478 }
479
480 Option::output_dir = dir.path();
481 QString absoluteFilePath = QDir::cleanPath(path: fi.absoluteFilePath());
482 Option::output.setFileName(absoluteFilePath.mid(position: Option::output_dir.size() + 1));
483 }
484
485 QMakeProperty prop;
486 switch (Option::qmake_mode) {
487 case Option::QMAKE_QUERY_PROPERTY:
488 return prop.queryProperty(optionProperties: Option::prop::properties);
489 case Option::QMAKE_SET_PROPERTY:
490 return prop.setProperty(Option::prop::properties);
491 case Option::QMAKE_UNSET_PROPERTY:
492 prop.unsetProperty(optionProperties: Option::prop::properties);
493 return 0;
494 default:
495 break;
496 }
497
498 globals.setQMakeProperty(&prop);
499
500 ProFileCache proFileCache;
501 Option::proFileCache = &proFileCache;
502 QMakeParser parser(&proFileCache, &vfs, &Option::evalHandler);
503 Option::parser = &parser;
504
505 QMakeProject project;
506 int exit_val = 0;
507 QStringList files;
508 if(Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT)
509 files << "(*hack*)"; //we don't even use files, but we do the for() body once
510 else
511 files = Option::mkfile::project_files;
512 for(QStringList::Iterator pfile = files.begin(); pfile != files.end(); pfile++) {
513 if(Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE ||
514 Option::qmake_mode == Option::QMAKE_GENERATE_PRL) {
515 QString fn = Option::normalizePath(in: *pfile);
516 if(!QFile::exists(fileName: fn)) {
517 fprintf(stderr, format: "Cannot find file: %s.\n",
518 QDir::toNativeSeparators(pathName: fn).toLatin1().constData());
519 exit_val = 2;
520 continue;
521 }
522
523 //setup pwd properly
524 debug_msg(level: 1, fmt: "Resetting dir to: %s",
525 QDir::toNativeSeparators(pathName: oldpwd).toLatin1().constData());
526 qmake_setpwd(p: oldpwd); //reset the old pwd
527 int di = fn.lastIndexOf(c: QLatin1Char('/'));
528 if(di != -1) {
529 debug_msg(level: 1, fmt: "Changing dir to: %s",
530 QDir::toNativeSeparators(pathName: fn.left(n: di)).toLatin1().constData());
531 if(!qmake_setpwd(p: fn.left(n: di)))
532 fprintf(stderr, format: "Cannot find directory: %s\n",
533 QDir::toNativeSeparators(pathName: fn.left(n: di)).toLatin1().constData());
534 fn = fn.right(n: fn.size() - di - 1);
535 }
536
537 Option::prepareProject(pfile: fn);
538
539 // read project..
540 if(!project.read(project: fn)) {
541 fprintf(stderr, format: "Error processing project file: %s\n",
542 QDir::toNativeSeparators(pathName: *pfile).toLatin1().constData());
543 exit_val = 3;
544 continue;
545 }
546 if (Option::mkfile::do_preprocess) {
547 project.dump();
548 continue; //no need to create makefile
549 }
550 }
551
552 bool success = true;
553 MetaMakefileGenerator *mkfile = MetaMakefileGenerator::createMetaGenerator(proj: &project, name: QString(), op: false, success: &success);
554 if (!success)
555 exit_val = 3;
556
557 if (mkfile && !mkfile->write()) {
558 if(Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT)
559 fprintf(stderr, format: "Unable to generate project file.\n");
560 else
561 fprintf(stderr, format: "Unable to generate makefile for: %s\n",
562 QDir::toNativeSeparators(pathName: *pfile).toLatin1().constData());
563 exit_val = 5;
564 }
565 delete mkfile;
566 mkfile = nullptr;
567 }
568 qmakeClearCaches();
569 return exit_val;
570}
571
572QT_END_NAMESPACE
573
574int main(int argc, char **argv)
575{
576 // Set name of the qmake application in QCoreApplication instance
577 QT_PREPEND_NAMESPACE(QCoreApplication) app(argc, argv);
578 return QT_PREPEND_NAMESPACE(runQMake)(argc, argv);
579}
580

source code of qtbase/qmake/main.cpp