| 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 | |
| 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 | |
| 90 | QMakeGlobals::~QMakeGlobals() |
| 91 | { |
| 92 | qDeleteAll(c: baseEnvs); |
| 93 | } |
| 94 | |
| 95 | QString QMakeGlobals::cleanSpec(QMakeCmdLineParserState &state, const QString &spec) |
| 96 | { |
| 97 | QString ret = QDir::cleanPath(path: spec); |
| 98 | if (ret.contains(c: QLatin1Char('/'))) { |
| 99 | QString absRet = IoUtils::resolvePath(baseDir: state.pwd, fileName: ret); |
| 100 | if (QFile::exists(fileName: absRet)) |
| 101 | ret = absRet; |
| 102 | } |
| 103 | return ret; |
| 104 | } |
| 105 | |
| 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(i: *pos); |
| 112 | switch (argState) { |
| 113 | case ArgConfig: |
| 114 | state.configs[state.phase] << arg; |
| 115 | break; |
| 116 | case ArgSpec: |
| 117 | qmakespec = args[*pos] = cleanSpec(state, spec: arg); |
| 118 | break; |
| 119 | case ArgXSpec: |
| 120 | xqmakespec = args[*pos] = cleanSpec(state, spec: 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(baseDir: state.pwd, fileName: arg); |
| 130 | break; |
| 131 | case ArgQtConf: |
| 132 | qtconf = args[*pos] = IoUtils::resolvePath(baseDir: state.pwd, fileName: arg); |
| 133 | break; |
| 134 | default: |
| 135 | if (arg.startsWith(c: QLatin1Char('-'))) { |
| 136 | if (arg == QLatin1String("--" )) { |
| 137 | state.extraargs = args.mid(pos: *pos + 1); |
| 138 | args.erase(afirst: args.begin() + *pos, alast: 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(c: 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 | |
| 185 | void QMakeGlobals::commitCommandLineArguments(QMakeCmdLineParserState &state) |
| 186 | { |
| 187 | if (!state.extraargs.isEmpty()) { |
| 188 | QString = fL1S("QMAKE_EXTRA_ARGS =" ); |
| 189 | for (const QString &ea : qAsConst(t&: state.extraargs)) |
| 190 | extra += QLatin1Char(' ') + QMakeEvaluator::quoteValue(val: 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(sep: QLatin1Char(' '))); |
| 196 | extra_cmds[p] = state.cmds[p].join(sep: QLatin1Char('\n')); |
| 197 | } |
| 198 | |
| 199 | if (xqmakespec.isEmpty()) |
| 200 | xqmakespec = qmakespec; |
| 201 | } |
| 202 | |
| 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 | |
| 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: &pos); |
| 221 | commitCommandLineArguments(state); |
| 222 | useEnvironment(); |
| 223 | } |
| 224 | |
| 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(c: QLatin1Char('/'))) |
| 230 | srcpath += QLatin1Char('/'); |
| 231 | QString dstpath = output_dir; |
| 232 | if (!dstpath.endsWith(c: 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(i: srcLen) == dstpath.at(i: dstLen)) |
| 239 | if (srcpath.at(i: srcLen) == QLatin1Char('/')) |
| 240 | lastSl = 0; |
| 241 | source_root = srcpath.left(n: srcLen + lastSl); |
| 242 | build_root = dstpath.left(n: dstLen + lastSl); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | QString QMakeGlobals::shadowedPath(const QString &fileName) const |
| 247 | { |
| 248 | if (source_root.isEmpty()) |
| 249 | return fileName; |
| 250 | if (fileName.startsWith(s: source_root) |
| 251 | && (fileName.length() == source_root.length() |
| 252 | || fileName.at(i: source_root.length()) == QLatin1Char('/'))) { |
| 253 | return build_root + fileName.mid(position: source_root.length()); |
| 254 | } |
| 255 | return QString(); |
| 256 | } |
| 257 | |
| 258 | 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(sep: dirlist_sep, behavior: Qt::SkipEmptyParts); |
| 264 | ret.reserve(alloc: vals.length()); |
| 265 | for (const QString &it : vals) |
| 266 | ret << IoUtils::resolvePath(baseDir: cwd, fileName: it); |
| 267 | } |
| 268 | return ret; |
| 269 | } |
| 270 | |
| 271 | QString QMakeGlobals::getEnv(const QString &var) const |
| 272 | { |
| 273 | #ifdef PROEVALUATOR_SETENV |
| 274 | return environment.value(name: var); |
| 275 | #else |
| 276 | return QString::fromLocal8Bit(qgetenv(var.toLocal8Bit().constData())); |
| 277 | #endif |
| 278 | } |
| 279 | |
| 280 | QStringList QMakeGlobals::getPathListEnv(const QString &var) const |
| 281 | { |
| 282 | return splitPathList(val: getEnv(var)); |
| 283 | } |
| 284 | |
| 285 | QString QMakeGlobals::expandEnvVars(const QString &str) const |
| 286 | { |
| 287 | QString string = str; |
| 288 | int startIndex = 0; |
| 289 | forever { |
| 290 | startIndex = string.indexOf(c: QLatin1Char('$'), from: startIndex); |
| 291 | if (startIndex < 0) |
| 292 | break; |
| 293 | if (string.length() < startIndex + 3) |
| 294 | break; |
| 295 | if (string.at(i: startIndex + 1) != QLatin1Char('(')) { |
| 296 | startIndex++; |
| 297 | continue; |
| 298 | } |
| 299 | int endIndex = string.indexOf(c: QLatin1Char(')'), from: startIndex + 2); |
| 300 | if (endIndex < 0) |
| 301 | break; |
| 302 | QString value = getEnv(var: string.mid(position: startIndex + 2, n: endIndex - startIndex - 2)); |
| 303 | string.replace(i: startIndex, len: endIndex - startIndex + 1, after: value); |
| 304 | startIndex += value.length(); |
| 305 | } |
| 306 | return string; |
| 307 | } |
| 308 | |
| 309 | #ifndef QT_BUILD_QMAKE |
| 310 | #ifdef PROEVALUATOR_INIT_PROPS |
| 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 | |
| 334 | void QMakeGlobals::parseProperties(const QByteArray &data, QHash<ProKey, ProString> &properties) |
| 335 | { |
| 336 | const auto lines = data.split(sep: '\n'); |
| 337 | for (QByteArray line : lines) { |
| 338 | int off = line.indexOf(c: ':'); |
| 339 | if (off < 0) // huh? |
| 340 | continue; |
| 341 | if (line.endsWith(c: '\r')) |
| 342 | line.chop(n: 1); |
| 343 | QString name = QString::fromLatin1(str: line.left(len: off)); |
| 344 | ProString value = ProString(QDir::fromNativeSeparators( |
| 345 | pathName: QString::fromLocal8Bit(str: line.mid(index: off + 1)))); |
| 346 | if (value.isNull()) |
| 347 | value = ProString("" ); // Make sure it is not null, to discern from missing keys |
| 348 | properties.insert(akey: ProKey(name), avalue: value); |
| 349 | if (name.startsWith(s: QLatin1String("QT_" ))) { |
| 350 | enum { PropPut, PropRaw, PropGet } variant; |
| 351 | if (name.contains(c: QLatin1Char('/'))) { |
| 352 | if (name.endsWith(s: QLatin1String("/raw" ))) |
| 353 | variant = PropRaw; |
| 354 | else if (name.endsWith(s: QLatin1String("/get" ))) |
| 355 | variant = PropGet; |
| 356 | else // Nothing falls back on /src or /dev. |
| 357 | continue; |
| 358 | name.chop(n: 4); |
| 359 | } else { |
| 360 | variant = PropPut; |
| 361 | } |
| 362 | if (name.startsWith(s: 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(i: 3, len: 7, after: QLatin1String("HOST" )); |
| 371 | properties.insert(akey: ProKey(hname), avalue: value); |
| 372 | properties.insert(akey: ProKey(hname + QLatin1String("/get" )), avalue: value); |
| 373 | properties.insert(akey: ProKey(hname + QLatin1String("/src" )), avalue: value); |
| 374 | } |
| 375 | properties.insert(akey: ProKey(name + QLatin1String("/raw" )), avalue: value); |
| 376 | } |
| 377 | if (variant <= PropRaw) |
| 378 | properties.insert(akey: ProKey(name + QLatin1String("/dev" )), avalue: value); |
| 379 | } else if (!name.startsWith(s: QLatin1String("QT_HOST_" ))) { |
| 380 | continue; |
| 381 | } |
| 382 | if (variant != PropRaw) { |
| 383 | if (variant < PropGet) |
| 384 | properties.insert(akey: ProKey(name + QLatin1String("/get" )), avalue: value); |
| 385 | properties.insert(akey: ProKey(name + QLatin1String("/src" )), avalue: value); |
| 386 | } |
| 387 | } |
| 388 | } |
| 389 | } |
| 390 | #endif // QT_BUILD_QMAKE |
| 391 | |
| 392 | QT_END_NAMESPACE |
| 393 | |