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

source code of qttools/src/linguist/shared/qmakeglobals.cpp