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