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 | |
44 | QT_BEGIN_NAMESPACE |
45 | using namespace QMakeInternal; // for IoUtils |
46 | |
47 | #define fL1S(s) QString::fromLatin1(s) |
48 | |
49 | QMakeGlobals::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 | |
65 | QMakeGlobals::~QMakeGlobals() |
66 | { |
67 | qDeleteAll(c: baseEnvs); |
68 | } |
69 | |
70 | QString 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 | */ |
88 | QMakeGlobals::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 | |
167 | void QMakeGlobals::commitCommandLineArguments(QMakeCmdLineParserState &state) |
168 | { |
169 | if (!state.extraargs.isEmpty()) { |
170 | QString = 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 | |
185 | void 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 | |
196 | void 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 | |
207 | void 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 | |
228 | QString 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 | |
240 | QStringList 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 | |
253 | QString 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 | |
262 | QStringList QMakeGlobals::getPathListEnv(const QString &var) const |
263 | { |
264 | return splitPathList(val: getEnv(var)); |
265 | } |
266 | |
267 | QString 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 |
293 | bool 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 | |
316 | void 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 | |
374 | QT_END_NAMESPACE |
375 | |