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
69QT_BEGIN_NAMESPACE
70using namespace QMakeInternal; // for IoUtils
71
72#define fL1S(s) QString::fromLatin1(s)
73
74QMakeGlobals::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
90QMakeGlobals::~QMakeGlobals()
91{
92 qDeleteAll(baseEnvs);
93}
94
95QString 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
106QMakeGlobals::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
185void 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
203void 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
214void 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
225void 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
246QString 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
258QStringList 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
271QString 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
280QStringList QMakeGlobals::getPathListEnv(const QString &var) const
281{
282 return splitPathList(getEnv(var));
283}
284
285QString 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
311bool 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
334void 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
392QT_END_NAMESPACE
393