1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "qmakeglobals.h"
27 
28 #include "qmakeevaluator.h"
29 #include "ioutils.h"
30 
31 #include <qbytearray.h>
32 #include <qdatetime.h>
33 #include <qdebug.h>
34 #include <qdir.h>
35 #include <qfile.h>
36 #include <qfileinfo.h>
37 #include <qlist.h>
38 #include <qset.h>
39 #include <qstack.h>
40 #include <qstring.h>
41 #include <qstringlist.h>
42 #include <qtextstream.h>
43 #ifdef PROEVALUATOR_THREAD_SAFE
44 # include <qthreadpool.h>
45 #endif
46 
47 #ifdef Q_OS_UNIX
48 #include <unistd.h>
49 #include <sys/utsname.h>
50 #else
51 #include <windows.h>
52 #endif
53 #include <stdio.h>
54 #include <stdlib.h>
55 
56 #ifdef Q_OS_WIN32
57 #define QT_POPEN _popen
58 #define QT_POPEN_READ "rb"
59 #define QT_PCLOSE _pclose
60 #else
61 #define QT_POPEN popen
62 #define QT_POPEN_READ "r"
63 #define QT_PCLOSE pclose
64 #endif
65 
66 QT_BEGIN_NAMESPACE
67 using namespace QMakeInternal; // for IoUtils
68 
69 #define fL1S(s) QString::fromLatin1(s)
70 
QMakeGlobals()71 QMakeGlobals::QMakeGlobals()
72 {
73     do_cache = true;
74 
75 #ifdef PROEVALUATOR_DEBUG
76     debugLevel = 0;
77 #endif
78 #ifdef Q_OS_WIN
79     dirlist_sep = QLatin1Char(';');
80     dir_sep = QLatin1Char('\\');
81 #else
82     dirlist_sep = QLatin1Char(':');
83     dir_sep = QLatin1Char('/');
84 #endif
85 }
86 
~QMakeGlobals()87 QMakeGlobals::~QMakeGlobals()
88 {
89     qDeleteAll(baseEnvs);
90 }
91 
killProcesses()92 void QMakeGlobals::killProcesses()
93 {
94 #ifdef PROEVALUATOR_THREAD_SAFE
95     QMutexLocker lock(&mutex);
96     canceled = true;
97     for (QProcess * const proc : runningProcs)
98         proc->kill();
99     runningProcs.clear();
100 #endif
101 }
102 
cleanSpec(QMakeCmdLineParserState & state,const QString & spec)103 QString QMakeGlobals::cleanSpec(QMakeCmdLineParserState &state, const QString &spec)
104 {
105     QString ret = QDir::cleanPath(spec);
106     if (ret.contains(QLatin1Char('/'))) {
107         QString absRet = IoUtils::resolvePath(state.pwd, ret);
108         if (QFile::exists(absRet))
109             ret = absRet;
110     }
111     return ret;
112 }
113 
addCommandLineArguments(QMakeCmdLineParserState & state,QStringList & args,int * pos)114 QMakeGlobals::ArgumentReturn QMakeGlobals::addCommandLineArguments(
115         QMakeCmdLineParserState &state, QStringList &args, int *pos)
116 {
117     enum { ArgNone, ArgConfig, ArgSpec, ArgXSpec, ArgTmpl, ArgTmplPfx, ArgCache, ArgQtConf } argState = ArgNone;
118     for (; *pos < args.count(); (*pos)++) {
119         QString arg = args.at(*pos);
120         switch (argState) {
121         case ArgConfig:
122             state.configs[state.phase] << arg;
123             break;
124         case ArgSpec:
125             qmakespec = args[*pos] = cleanSpec(state, arg);
126             break;
127         case ArgXSpec:
128             xqmakespec = args[*pos] = cleanSpec(state, arg);
129             break;
130         case ArgTmpl:
131             user_template = arg;
132             break;
133         case ArgTmplPfx:
134             user_template_prefix = arg;
135             break;
136         case ArgCache:
137             cachefile = args[*pos] = IoUtils::resolvePath(state.pwd, arg);
138             break;
139         case ArgQtConf:
140             qtconf = args[*pos] = IoUtils::resolvePath(state.pwd, arg);
141             break;
142         default:
143             if (arg.startsWith(QLatin1Char('-'))) {
144                 if (arg == QLatin1String("--")) {
145                     state.extraargs = args.mid(*pos + 1);
146                     args.erase(args.begin() + *pos, args.end());
147                     return ArgumentsOk;
148                 }
149                 if (arg == QLatin1String("-early"))
150                     state.phase = QMakeEvalEarly;
151                 else if (arg == QLatin1String("-before"))
152                     state.phase = QMakeEvalBefore;
153                 else if (arg == QLatin1String("-after"))
154                     state.phase = QMakeEvalAfter;
155                 else if (arg == QLatin1String("-late"))
156                     state.phase = QMakeEvalLate;
157                 else if (arg == QLatin1String("-config"))
158                     argState = ArgConfig;
159                 else if (arg == QLatin1String("-nocache"))
160                     do_cache = false;
161                 else if (arg == QLatin1String("-cache"))
162                     argState = ArgCache;
163                 else if (arg == QLatin1String("-qtconf"))
164                     argState = ArgQtConf;
165                 else if (arg == QLatin1String("-platform") || arg == QLatin1String("-spec"))
166                     argState = ArgSpec;
167                 else if (arg == QLatin1String("-xplatform") || arg == QLatin1String("-xspec"))
168                     argState = ArgXSpec;
169                 else if (arg == QLatin1String("-template") || arg == QLatin1String("-t"))
170                     argState = ArgTmpl;
171                 else if (arg == QLatin1String("-template_prefix") || arg == QLatin1String("-tp"))
172                     argState = ArgTmplPfx;
173                 else if (arg == QLatin1String("-win32"))
174                     dir_sep = QLatin1Char('\\');
175                 else if (arg == QLatin1String("-unix"))
176                     dir_sep = QLatin1Char('/');
177                 else
178                     return ArgumentUnknown;
179             } else if (arg.contains(QLatin1Char('='))) {
180                 state.cmds[state.phase] << arg;
181             } else {
182                 return ArgumentUnknown;
183             }
184             continue;
185         }
186         argState = ArgNone;
187     }
188     if (argState != ArgNone)
189         return ArgumentMalformed;
190     return ArgumentsOk;
191 }
192 
commitCommandLineArguments(QMakeCmdLineParserState & state)193 void QMakeGlobals::commitCommandLineArguments(QMakeCmdLineParserState &state)
194 {
195     if (!state.extraargs.isEmpty()) {
196         QString extra = fL1S("QMAKE_EXTRA_ARGS =");
197         foreach (const QString &ea, state.extraargs)
198             extra += QLatin1Char(' ') + QMakeEvaluator::quoteValue(ProString(ea));
199         state.cmds[QMakeEvalBefore] << extra;
200     }
201     for (int p = 0; p < 4; p++) {
202         if (!state.configs[p].isEmpty())
203             state.cmds[p] << (fL1S("CONFIG += ") + state.configs[p].join(QLatin1Char(' ')));
204         extra_cmds[p] = state.cmds[p].join(QLatin1Char('\n'));
205     }
206 
207     if (xqmakespec.isEmpty())
208         xqmakespec = qmakespec;
209 }
210 
useEnvironment()211 void QMakeGlobals::useEnvironment()
212 {
213     if (xqmakespec.isEmpty())
214         xqmakespec = getEnv(QLatin1String("XQMAKESPEC"));
215     if (qmakespec.isEmpty()) {
216         qmakespec = getEnv(QLatin1String("QMAKESPEC"));
217         if (xqmakespec.isEmpty())
218             xqmakespec = qmakespec;
219     }
220 }
221 
setCommandLineArguments(const QString & pwd,const QStringList & _args)222 void QMakeGlobals::setCommandLineArguments(const QString &pwd, const QStringList &_args)
223 {
224     QStringList args = _args;
225 
226     QMakeCmdLineParserState state(pwd);
227     for (int pos = 0; pos < args.size(); pos++)
228         addCommandLineArguments(state, args, &pos);
229     commitCommandLineArguments(state);
230     useEnvironment();
231 }
232 
setDirectories(const QString & input_dir,const QString & output_dir)233 void QMakeGlobals::setDirectories(const QString &input_dir, const QString &output_dir)
234 {
235     if (input_dir != output_dir && !output_dir.isEmpty()) {
236         QString srcpath = input_dir;
237         if (!srcpath.endsWith(QLatin1Char('/')))
238             srcpath += QLatin1Char('/');
239         QString dstpath = output_dir;
240         if (!dstpath.endsWith(QLatin1Char('/')))
241             dstpath += QLatin1Char('/');
242         int srcLen = srcpath.length();
243         int dstLen = dstpath.length();
244         int lastSl = -1;
245         while (++lastSl, --srcLen, --dstLen,
246                srcLen && dstLen && srcpath.at(srcLen) == dstpath.at(dstLen))
247             if (srcpath.at(srcLen) == QLatin1Char('/'))
248                 lastSl = 0;
249         source_root = srcpath.left(srcLen + lastSl);
250         build_root = dstpath.left(dstLen + lastSl);
251     }
252 }
253 
shadowedPath(const QString & fileName) const254 QString QMakeGlobals::shadowedPath(const QString &fileName) const
255 {
256     if (source_root.isEmpty())
257         return fileName;
258     if (fileName.startsWith(source_root)
259         && (fileName.length() == source_root.length()
260             || fileName.at(source_root.length()) == QLatin1Char('/'))) {
261         return build_root + fileName.mid(source_root.length());
262     }
263     return QString();
264 }
265 
splitPathList(const QString & val) const266 QStringList QMakeGlobals::splitPathList(const QString &val) const
267 {
268     QStringList ret;
269     if (!val.isEmpty()) {
270         QString cwd(QDir::currentPath());
271         const QStringList vals = val.split(dirlist_sep);
272         ret.reserve(vals.length());
273         for (const QString &it : vals)
274             ret << IoUtils::resolvePath(cwd, it);
275     }
276     return ret;
277 }
278 
getEnv(const QString & var) const279 QString QMakeGlobals::getEnv(const QString &var) const
280 {
281 #ifdef PROEVALUATOR_SETENV
282     return environment.value(var);
283 #else
284     return QString::fromLocal8Bit(qgetenv(var.toLocal8Bit().constData()));
285 #endif
286 }
287 
getPathListEnv(const QString & var) const288 QStringList QMakeGlobals::getPathListEnv(const QString &var) const
289 {
290     return splitPathList(getEnv(var));
291 }
292 
expandEnvVars(const QString & str) const293 QString QMakeGlobals::expandEnvVars(const QString &str) const
294 {
295     QString string = str;
296     int startIndex = 0;
297     forever {
298         startIndex = string.indexOf(QLatin1Char('$'), startIndex);
299         if (startIndex < 0)
300             break;
301         if (string.length() < startIndex + 3)
302             break;
303         if (string.at(startIndex + 1) != QLatin1Char('(')) {
304             startIndex++;
305             continue;
306         }
307         int endIndex = string.indexOf(QLatin1Char(')'), startIndex + 2);
308         if (endIndex < 0)
309             break;
310         QString value = getEnv(string.mid(startIndex + 2, endIndex - startIndex - 2));
311         string.replace(startIndex, endIndex - startIndex + 1, value);
312         startIndex += value.length();
313     }
314     return string;
315 }
316 
317 #ifndef QT_BUILD_QMAKE
318 #ifdef PROEVALUATOR_INIT_PROPS
initProperties()319 bool QMakeGlobals::initProperties()
320 {
321     QByteArray data;
322 #ifndef QT_BOOTSTRAPPED
323     QProcess proc;
324     proc.start(qmake_abslocation, QStringList() << QLatin1String("-query"));
325     if (!proc.waitForFinished())
326         return false;
327     data = proc.readAll();
328 #else
329     if (FILE *proc = QT_POPEN(QString(IoUtils::shellQuote(qmake_abslocation)
330                                       + QLatin1String(" -query")).toLocal8Bit(), QT_POPEN_READ)) {
331         char buff[1024];
332         while (!feof(proc))
333             data.append(buff, int(fread(buff, 1, 1023, proc)));
334         QT_PCLOSE(proc);
335     }
336 #endif
337     parseProperties(data, properties);
338     return true;
339 }
340 #endif
341 
parseProperties(const QByteArray & data,QHash<ProKey,ProString> & properties)342 void QMakeGlobals::parseProperties(const QByteArray &data, QHash<ProKey, ProString> &properties)
343 {
344     const auto lines = data.split('\n');
345     for (QByteArray line : lines) {
346         int off = line.indexOf(':');
347         if (off < 0) // huh?
348             continue;
349         if (line.endsWith('\r'))
350             line.chop(1);
351         QString name = QString::fromLatin1(line.left(off));
352         ProString value = ProString(QDir::fromNativeSeparators(
353                     QString::fromLocal8Bit(line.mid(off + 1))));
354         if (value.isNull())
355             value = ProString(""); // Make sure it is not null, to discern from missing keys
356         properties.insert(ProKey(name), value);
357         if (name.startsWith(QLatin1String("QT_"))) {
358             enum { PropPut, PropRaw, PropGet } variant;
359             if (name.contains(QLatin1Char('/'))) {
360                 if (name.endsWith(QLatin1String("/raw")))
361                     variant = PropRaw;
362                 else if (name.endsWith(QLatin1String("/get")))
363                     variant = PropGet;
364                 else  // Nothing falls back on /src or /dev.
365                     continue;
366                 name.chop(4);
367             } else {
368                 variant = PropPut;
369             }
370             if (name.startsWith(QLatin1String("QT_INSTALL_"))) {
371                 if (variant < PropRaw) {
372                     if (name == QLatin1String("QT_INSTALL_PREFIX")
373                         || name == QLatin1String("QT_INSTALL_DATA")
374                         || name == QLatin1String("QT_INSTALL_LIBS")
375                         || name == QLatin1String("QT_INSTALL_BINS")) {
376                         // Qt4 fallback
377                         QString hname = name;
378                         hname.replace(3, 7, QLatin1String("HOST"));
379                         properties.insert(ProKey(hname), value);
380                         properties.insert(ProKey(hname + QLatin1String("/get")), value);
381                         properties.insert(ProKey(hname + QLatin1String("/src")), value);
382                     }
383                     properties.insert(ProKey(name + QLatin1String("/raw")), value);
384                 }
385                 if (variant <= PropRaw)
386                     properties.insert(ProKey(name + QLatin1String("/dev")), value);
387             } else if (!name.startsWith(QLatin1String("QT_HOST_"))) {
388                 continue;
389             }
390             if (variant != PropRaw) {
391                 if (variant < PropGet)
392                     properties.insert(ProKey(name + QLatin1String("/get")), value);
393                 properties.insert(ProKey(name + QLatin1String("/src")), value);
394             }
395         }
396     }
397 }
398 #endif // QT_BUILD_QMAKE
399 
400 QT_END_NAMESPACE
401