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 <qregularexpression.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
56using namespace QMakeInternal;
57
58QT_BEGIN_NAMESPACE
59
60#ifdef Q_OS_WIN
61
62struct SedSubst {
63 QRegularExpression from;
64 QString to;
65};
66Q_DECLARE_TYPEINFO(SedSubst, Q_MOVABLE_TYPE);
67
68static int doSed(int argc, char **argv)
69{
70 QList<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 QRegularExpression::PatternOptions matchcase = QRegularExpression::NoPatternOption;
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 QRegularExpression
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 = QRegularExpression::CaseInsensitiveOption;
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 = QRegularExpression(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
174static 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
245static 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
254static 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#else
269 Q_UNUSED(mustEnsureWritability);
270#endif
271 if (!IoUtils::touchFile(targetFile.fileName(), sourceFilePath, errorString))
272 return false;
273#ifdef Q_OS_WIN
274 if (mustRestorePermissions && !setFilePermissions(targetFile, targetPermissions))
275 return false;
276#endif
277 return true;
278}
279
280static int installFile(const QString &source, const QString &target, bool exe = false,
281 bool preservePermissions = false)
282{
283 QFile sourceFile(source);
284 QFile targetFile(target);
285 if (targetFile.exists()) {
286#ifdef Q_OS_WIN
287 targetFile.setPermissions(targetFile.permissions() | QFile::WriteUser);
288#endif
289 QFile::remove(target);
290 } else {
291 QDir::root().mkpath(QFileInfo(target).absolutePath());
292 }
293
294 if (!sourceFile.copy(target)) {
295 fprintf(stderr, "Error copying %s to %s: %s\n", source.toLatin1().constData(), qPrintable(target), qPrintable(sourceFile.errorString()));
296 return 3;
297 }
298
299 QFileDevice::Permissions targetPermissions = preservePermissions
300 ? sourceFile.permissions()
301 : (QFileDevice::ReadOwner | QFileDevice::WriteOwner
302 | QFileDevice::ReadUser | QFileDevice::WriteUser
303 | QFileDevice::ReadGroup | QFileDevice::ReadOther);
304 if (exe) {
305 targetPermissions |= QFileDevice::ExeOwner | QFileDevice::ExeUser |
306 QFileDevice::ExeGroup | QFileDevice::ExeOther;
307 }
308 if (!setFilePermissions(targetFile, targetPermissions))
309 return 3;
310
311 QString error;
312 if (!copyFileTimes(targetFile, sourceFile.fileName(), preservePermissions, &error)) {
313 fprintf(stderr, "%s", qPrintable(error));
314 return 3;
315 }
316
317 return 0;
318}
319
320static int installFileOrDirectory(const QString &source, const QString &target,
321 bool preservePermissions = false)
322{
323 QFileInfo fi(source);
324 if (false) {
325#if defined(Q_OS_UNIX)
326 } else if (fi.isSymLink()) {
327 QString linkTarget;
328 if (!IoUtils::readLinkTarget(fi.absoluteFilePath(), &linkTarget)) {
329 fprintf(stderr, "Could not read link %s: %s\n", qPrintable(fi.absoluteFilePath()), strerror(errno));
330 return 3;
331 }
332 QFile::remove(target);
333 if (::symlink(linkTarget.toLocal8Bit().constData(), target.toLocal8Bit().constData()) < 0) {
334 fprintf(stderr, "Could not create link: %s\n", strerror(errno));
335 return 3;
336 }
337#endif
338 } else if (fi.isDir()) {
339 QDir::current().mkpath(target);
340
341 QDirIterator it(source, QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden);
342 while (it.hasNext()) {
343 it.next();
344 const QFileInfo &entry = it.fileInfo();
345 const QString &entryTarget = target + QDir::separator() + entry.fileName();
346
347 const int recursionResult = installFileOrDirectory(entry.filePath(), entryTarget, true);
348 if (recursionResult != 0)
349 return recursionResult;
350 }
351 } else {
352 const int fileCopyResult = installFile(source, target, /*exe*/ false, preservePermissions);
353 if (fileCopyResult != 0)
354 return fileCopyResult;
355 }
356 return 0;
357}
358
359static int doQInstall(int argc, char **argv)
360{
361 bool installExecutable = false;
362 if (argc == 3 && !strcmp(argv[0], "-exe")) {
363 installExecutable = true;
364 --argc;
365 ++argv;
366 }
367
368 if (argc != 2 && !installExecutable) {
369 fprintf(stderr, "Error: usage: [-exe] source target\n");
370 return 3;
371 }
372
373 const QString source = QString::fromLocal8Bit(argv[0]);
374 const QString target = QString::fromLocal8Bit(argv[1]);
375
376 if (installExecutable)
377 return installFile(source, target, /*exe=*/true);
378 return installFileOrDirectory(source, target);
379}
380
381
382static int doInstall(int argc, char **argv)
383{
384 if (!argc) {
385 fprintf(stderr, "Error: -install requires further arguments\n");
386 return 3;
387 }
388#ifdef Q_OS_WIN
389 if (!strcmp(argv[0], "sed"))
390 return doSed(argc - 1, argv + 1);
391 if (!strcmp(argv[0], "ln"))
392 return doLink(argc - 1, argv + 1);
393#endif
394 if (!strcmp(argv[0], "qinstall"))
395 return doQInstall(argc - 1, argv + 1);
396 fprintf(stderr, "Error: unrecognized -install subcommand '%s'\n", argv[0]);
397 return 3;
398}
399
400
401#ifdef Q_OS_WIN
402
403static int dumpMacros(const wchar_t *cmdline)
404{
405 // from http://stackoverflow.com/questions/3665537/how-to-find-out-cl-exes-built-in-macros
406 int argc;
407 wchar_t **argv = CommandLineToArgvW(cmdline, &argc);
408 if (!argv)
409 return 2;
410 for (int i = 0; i < argc; ++i) {
411 if (argv[i][0] != L'-' || argv[i][1] != 'D')
412 continue;
413
414 wchar_t *value = wcschr(argv[i], L'=');
415 if (value) {
416 *value = 0;
417 ++value;
418 } else {
419 // point to the NUL at the end, so we don't print anything
420 value = argv[i] + wcslen(argv[i]);
421 }
422 wprintf(L"#define %Ls %Ls\n", argv[i] + 2, value);
423 }
424 return 0;
425}
426
427#endif // Q_OS_WIN
428
429/* This is to work around lame implementation on Darwin. It has been noted that the getpwd(3) function
430 is much too slow, and called much too often inside of Qt (every fileFixify). With this we use a locally
431 cached copy because I can control all the times it is set (because Qt never sets the pwd under me).
432*/
433static QString pwd;
434QString qmake_getpwd()
435{
436 if(pwd.isNull())
437 pwd = QDir::currentPath();
438 return pwd;
439}
440bool qmake_setpwd(const QString &p)
441{
442 if(QDir::setCurrent(p)) {
443 pwd = QDir::currentPath();
444 return true;
445 }
446 return false;
447}
448
449int runQMake(int argc, char **argv)
450{
451 qSetGlobalQHashSeed(0);
452
453 // stderr is unbuffered by default, but stdout buffering depends on whether
454 // there is a terminal attached. Buffering can make output from stderr and stdout
455 // appear out of sync, so force stdout to be unbuffered as well.
456 // This is particularly important for things like QtCreator and scripted builds.
457 setvbuf(stdout, (char *)NULL, _IONBF, 0);
458
459 // Workaround for inferior/missing command line tools on Windows: make our own!
460 if (argc >= 4 && !strcmp(argv[1], "-qtconf") && !strcmp(argv[3], "-install"))
461 return doInstall(argc - 4, argv + 4);
462 if (argc >= 2 && !strcmp(argv[1], "-install"))
463 return doInstall(argc - 2, argv + 2);
464
465#ifdef Q_OS_WIN
466 {
467 // Support running as Visual C++'s compiler
468 const wchar_t *cmdline = _wgetenv(L"MSC_CMD_FLAGS");
469 if (!cmdline || !*cmdline)
470 cmdline = _wgetenv(L"MSC_IDE_FLAGS");
471 if (cmdline && *cmdline)
472 return dumpMacros(cmdline);
473 }
474#endif
475
476 QMakeVfs vfs;
477 Option::vfs = &vfs;
478 QMakeGlobals globals;
479 Option::globals = &globals;
480
481 // parse command line
482 int ret = Option::init(argc, argv);
483 if(ret != Option::QMAKE_CMDLINE_SUCCESS) {
484 if ((ret & Option::QMAKE_CMDLINE_ERROR) != 0)
485 return 1;
486 return 0;
487 }
488
489 QString oldpwd = qmake_getpwd();
490
491 Option::output_dir = oldpwd; //for now this is the output dir
492 if (!Option::output.fileName().isEmpty() && Option::output.fileName() != "-") {
493 // The output 'filename', as given by the -o option, might include one
494 // or more directories, so we may need to rebase the output directory.
495 QFileInfo fi(Option::output);
496
497 QDir dir(QDir::cleanPath(fi.isDir() ? fi.absoluteFilePath() : fi.absolutePath()));
498
499 // Don't treat Xcode project directory as part of OUT_PWD
500 if (dir.dirName().endsWith(QLatin1String(".xcodeproj"))) {
501 // Note: we're intentionally not using cdUp(), as the dir may not exist
502 dir.setPath(QDir::cleanPath(dir.filePath("..")));
503 }
504
505 Option::output_dir = dir.path();
506 QString absoluteFilePath = QDir::cleanPath(fi.absoluteFilePath());
507 Option::output.setFileName(absoluteFilePath.mid(Option::output_dir.length() + 1));
508 }
509
510 QMakeProperty prop;
511 if(Option::qmake_mode == Option::QMAKE_QUERY_PROPERTY ||
512 Option::qmake_mode == Option::QMAKE_SET_PROPERTY ||
513 Option::qmake_mode == Option::QMAKE_UNSET_PROPERTY)
514 return prop.exec() ? 0 : 101;
515 globals.setQMakeProperty(&prop);
516
517 ProFileCache proFileCache;
518 Option::proFileCache = &proFileCache;
519 QMakeParser parser(&proFileCache, &vfs, &Option::evalHandler);
520 Option::parser = &parser;
521
522 QMakeProject project;
523 int exit_val = 0;
524 QStringList files;
525 if(Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT)
526 files << "(*hack*)"; //we don't even use files, but we do the for() body once
527 else
528 files = Option::mkfile::project_files;
529 for(QStringList::Iterator pfile = files.begin(); pfile != files.end(); pfile++) {
530 if(Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE ||
531 Option::qmake_mode == Option::QMAKE_GENERATE_PRL) {
532 QString fn = Option::normalizePath(*pfile);
533 if(!QFile::exists(fn)) {
534 fprintf(stderr, "Cannot find file: %s.\n",
535 QDir::toNativeSeparators(fn).toLatin1().constData());
536 exit_val = 2;
537 continue;
538 }
539
540 //setup pwd properly
541 debug_msg(1, "Resetting dir to: %s",
542 QDir::toNativeSeparators(oldpwd).toLatin1().constData());
543 qmake_setpwd(oldpwd); //reset the old pwd
544 int di = fn.lastIndexOf(QLatin1Char('/'));
545 if(di != -1) {
546 debug_msg(1, "Changing dir to: %s",
547 QDir::toNativeSeparators(fn.left(di)).toLatin1().constData());
548 if(!qmake_setpwd(fn.left(di)))
549 fprintf(stderr, "Cannot find directory: %s\n",
550 QDir::toNativeSeparators(fn.left(di)).toLatin1().constData());
551 fn = fn.right(fn.length() - di - 1);
552 }
553
554 Option::prepareProject(fn);
555
556 // read project..
557 if(!project.read(fn)) {
558 fprintf(stderr, "Error processing project file: %s\n",
559 QDir::toNativeSeparators(*pfile).toLatin1().constData());
560 exit_val = 3;
561 continue;
562 }
563 if (Option::mkfile::do_preprocess) {
564 project.dump();
565 continue; //no need to create makefile
566 }
567 }
568
569 bool success = true;
570 MetaMakefileGenerator *mkfile = MetaMakefileGenerator::createMetaGenerator(&project, QString(), false, &success);
571 if (!success)
572 exit_val = 3;
573
574 if (mkfile && !mkfile->write()) {
575 if(Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT)
576 fprintf(stderr, "Unable to generate project file.\n");
577 else
578 fprintf(stderr, "Unable to generate makefile for: %s\n",
579 QDir::toNativeSeparators(*pfile).toLatin1().constData());
580 exit_val = 5;
581 }
582 delete mkfile;
583 mkfile = nullptr;
584 }
585 qmakeClearCaches();
586 return exit_val;
587}
588
589QT_END_NAMESPACE
590
591int main(int argc, char **argv)
592{
593 return QT_PREPEND_NAMESPACE(runQMake)(argc, argv);
594}
595