| 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(ch: 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(ch: 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(qmake_abslocation, 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, 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('\n'); |
| 319 | for (QByteArray line : lines) { |
| 320 | int off = line.indexOf(':'); |
| 321 | if (off < 0) // huh? |
| 322 | continue; |
| 323 | if (line.endsWith('\r')) |
| 324 | line.chop(1); |
| 325 | QString name = QString::fromLatin1(line.left(off)); |
| 326 | ProString value = ProString(QDir::fromNativeSeparators( |
| 327 | QString::fromLocal8Bit(line.mid(off + 1)))); |
| 328 | if (value.isNull()) |
| 329 | value = ProString("" ); // Make sure it is not null, to discern from missing keys |
| 330 | properties.insert(ProKey(name), value); |
| 331 | if (name.startsWith(QLatin1String("QT_" ))) { |
| 332 | enum { PropPut, PropRaw, PropGet } variant; |
| 333 | if (name.contains(QLatin1Char('/'))) { |
| 334 | if (name.endsWith(QLatin1String("/raw" ))) |
| 335 | variant = PropRaw; |
| 336 | else if (name.endsWith(QLatin1String("/get" ))) |
| 337 | variant = PropGet; |
| 338 | else // Nothing falls back on /src or /dev. |
| 339 | continue; |
| 340 | name.chop(4); |
| 341 | } else { |
| 342 | variant = PropPut; |
| 343 | } |
| 344 | if (name.startsWith(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(3, 7, QLatin1String("HOST" )); |
| 353 | properties.insert(ProKey(hname), value); |
| 354 | properties.insert(ProKey(hname + QLatin1String("/get" )), value); |
| 355 | properties.insert(ProKey(hname + QLatin1String("/src" )), value); |
| 356 | } |
| 357 | properties.insert(ProKey(name + QLatin1String("/raw" )), value); |
| 358 | } |
| 359 | if (variant <= PropRaw) |
| 360 | properties.insert(ProKey(name + QLatin1String("/dev" )), value); |
| 361 | } else if (!name.startsWith(QLatin1String("QT_HOST_" ))) { |
| 362 | continue; |
| 363 | } |
| 364 | if (variant != PropRaw) { |
| 365 | if (variant < PropGet) |
| 366 | properties.insert(ProKey(name + QLatin1String("/get" )), value); |
| 367 | properties.insert(ProKey(name + QLatin1String("/src" )), value); |
| 368 | } |
| 369 | } |
| 370 | } |
| 371 | } |
| 372 | #endif // QT_BUILD_QMAKE |
| 373 | |
| 374 | QT_END_NAMESPACE |
| 375 | |