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