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