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