File indexing completed on 2024-05-05 04:40:21

0001 /*
0002     SPDX-FileCopyrightText: 2010 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "qmakefilevisitor.h"
0008 
0009 #include "qmakefile.h"
0010 #include "qmakeincludefile.h"
0011 
0012 #include "parser/ast.h"
0013 
0014 #include <QStringList>
0015 #include <QFileInfo>
0016 #include <QProcessEnvironment>
0017 
0018 #include <debug.h>
0019 #define ifDebug(x)
0020 
0021 // BEGIN QMakeFileVisitor
0022 
0023 QMakeFileVisitor::QMakeFileVisitor(const QMakeVariableResolver* resolver, QMakeFile* baseFile)
0024     : m_resolver(resolver)
0025     , m_baseFile(baseFile)
0026 {
0027 }
0028 
0029 QMakeFileVisitor::~QMakeFileVisitor()
0030 {
0031 }
0032 
0033 void QMakeFileVisitor::setVariables(const VariableMap& vars)
0034 {
0035     m_variableValues = vars;
0036 }
0037 
0038 QMakeVariableResolver::VariableMap QMakeFileVisitor::visitFile(QMake::ProjectAST* node)
0039 {
0040     visitProject(node);
0041     return m_variableValues;
0042 }
0043 
0044 QStringList QMakeFileVisitor::visitMacro(QMake::ScopeBodyAST* node, const QStringList& arguments)
0045 {
0046     m_arguments = arguments;
0047     visitScopeBody(node);
0048     return m_lastReturn;
0049 }
0050 
0051 QStringList QMakeFileVisitor::resolveVariable(const QString& variable, VariableInfo::VariableType type) const
0052 {
0053     if (type == VariableInfo::QMakeVariable) {
0054         const auto variableValueIt = m_variableValues.find(variable);
0055         if (variableValueIt != m_variableValues.end()) {
0056             return *variableValueIt;
0057         }
0058     }
0059 
0060     return m_resolver->resolveVariable(variable, type);
0061 }
0062 
0063 QStringList QMakeFileVisitor::getValueList(const QList<QMake::ValueAST*>& list) const
0064 {
0065     QStringList result;
0066     for (QMake::ValueAST* v : list) {
0067         result += resolveVariables(v->value);
0068     }
0069     return result;
0070 }
0071 
0072 void QMakeFileVisitor::visitFunctionCall(QMake::FunctionCallAST* node)
0073 {
0074     if (node->identifier->value == QLatin1String("include") || node->identifier->value == QLatin1String("!include")) {
0075         if (node->args.isEmpty())
0076             return;
0077         QStringList arguments = getValueList(node->args);
0078 
0079         ifDebug(qCDebug(KDEV_QMAKE) << "found include" << node->identifier->value << arguments;)
0080         QString argument = arguments.join(QString()).trimmed();
0081         if (!argument.isEmpty() && QFileInfo(argument).isRelative()) {
0082             argument = QFileInfo(m_baseFile->absoluteDir() + QLatin1Char('/') + argument).canonicalFilePath();
0083         }
0084         if (argument.isEmpty()) {
0085             qCWarning(KDEV_QMAKE) << "empty include file detected in line" << node->startLine;
0086             if (node->identifier->value.startsWith(QLatin1Char('!'))) {
0087                 visitNode(node->body);
0088             }
0089             return;
0090         }
0091         ifDebug(qCDebug(KDEV_QMAKE) << "Reading Include file:" << argument;)
0092             QMakeIncludeFile includefile(argument, m_baseFile, m_variableValues);
0093         bool read = includefile.read();
0094         ifDebug(qCDebug(KDEV_QMAKE) << "successfully read:" << read;) if (read)
0095         {
0096             // TODO: optimize by using variableMap and iterator, don't compare values
0097             const auto vars = includefile.variables();
0098             for (const auto& var : vars) {
0099                 if (m_variableValues.value(var) != includefile.variableValues(var)) {
0100                     m_variableValues[var] = includefile.variableValues(var);
0101                 }
0102             }
0103             if (!node->identifier->value.startsWith(QLatin1Char('!'))) {
0104                 visitNode(node->body);
0105             }
0106         }
0107         else if (node->identifier->value.startsWith(QLatin1Char('!'))) { visitNode(node->body); }
0108     } else if (node->body && (node->identifier->value == QLatin1String("defineReplace")
0109                               || node->identifier->value
0110                                   == QLatin1String("defineTest"))) { // TODO: differentiate between replace and test functions?
0111         QStringList args = getValueList(node->args);
0112         if (!args.isEmpty()) {
0113             m_userMacros[args.first()] = node->body;
0114         } // TODO: else return error
0115     } else if (node->identifier->value == QLatin1String("return")) {
0116         m_lastReturn = getValueList(node->args);
0117     } else { // TODO: only visit when test function returned true?
0118         qCWarning(KDEV_QMAKE) << "unhandled function call" << node->identifier->value;
0119         visitNode(node->body);
0120     }
0121 }
0122 
0123 void QMakeFileVisitor::visitAssignment(QMake::AssignmentAST* node)
0124 {
0125     QString op = node->op->value;
0126     const QStringList values = getValueList(node->values);
0127     if (op == QLatin1String("=")) {
0128         m_variableValues[node->identifier->value] = values;
0129     } else if (op == QLatin1String("+=")) {
0130         m_variableValues[node->identifier->value] += values;
0131     } else if (op == QLatin1String("-=")) {
0132         for (const QString& value : values) {
0133             m_variableValues[node->identifier->value].removeAll(value);
0134         }
0135     } else if (op == QLatin1String("*=")) {
0136         for (const QString& value : values) {
0137             if (!m_variableValues.value(node->identifier->value).contains(value)) {
0138                 m_variableValues[node->identifier->value].append(value);
0139             }
0140         }
0141     } else if (op == QLatin1String("~=")) {
0142         if (values.isEmpty())
0143             return;
0144         QString value = values.first().trimmed();
0145         QString regex = value.mid(2, value.indexOf(QLatin1Char('/'), 2));
0146         QString replacement = value.mid(value.indexOf(QLatin1Char('/'), 2) + 1, value.lastIndexOf(QLatin1Char('/')));
0147         m_variableValues[node->identifier->value].replaceInStrings(QRegExp(regex), replacement);
0148     }
0149 }
0150 
0151 /// return 1-n for numeric variable, 0 otherwise
0152 int functionArgument(const QString& var)
0153 {
0154     bool ok;
0155     int arg = var.toInt(&ok);
0156     if (!ok) {
0157         return 0;
0158     } else {
0159         return arg;
0160     }
0161 }
0162 
0163 QStringList QMakeFileVisitor::resolveVariables(const QString& var) const
0164 {
0165     VariableReferenceParser parser;
0166     parser.setContent(var);
0167     if (!parser.parse()) {
0168         qCWarning(KDEV_QMAKE) << "Couldn't parse" << var << "to replace variables in it";
0169         return QStringList() << var;
0170     }
0171     const auto variableReferences = parser.variableReferences();
0172     if (variableReferences.isEmpty()) {
0173         return QStringList() << var;
0174     }
0175 
0176     /// TODO: multiple vars in one place will make the offsets go bonkers
0177     QString value = var;
0178     for (const auto& variable : variableReferences) {
0179         const auto vi = parser.variableInfo(variable);
0180         QString varValue;
0181 
0182         switch (vi.type) {
0183         case VariableInfo::QMakeVariable:
0184             if (int arg = functionArgument(variable)) {
0185                 if (arg > 0 && arg <= m_arguments.size()) {
0186                     varValue = m_arguments.at(arg - 1);
0187                 } else {
0188                     qCWarning(KDEV_QMAKE) << "undefined macro argument:" << variable;
0189                 }
0190             } else {
0191                 varValue = resolveVariable(variable, vi.type).join(QLatin1Char(' '));
0192             }
0193             break;
0194         case VariableInfo::ShellVariableResolveQMake:
0195         case VariableInfo::ShellVariableResolveMake:
0196             /// TODO: make vs qmake time
0197             varValue = QProcessEnvironment::systemEnvironment().value(variable);
0198             break;
0199         case VariableInfo::QtConfigVariable:
0200             varValue = resolveVariable(variable, vi.type).join(QLatin1Char(' '));
0201             break;
0202         case VariableInfo::FunctionCall: {
0203             QStringList arguments;
0204             arguments.reserve(vi.positions.size());
0205             for (const auto& pos : vi.positions) {
0206                 int start = pos.start + 3 + variable.length();
0207                 QString args = value.mid(start, pos.end - start);
0208                 varValue = resolveVariables(args).join(QLatin1Char(' '));
0209                 arguments << varValue;
0210             }
0211             varValue = evaluateMacro(variable, arguments).join(QLatin1Char(' '));
0212             break;
0213         }
0214         case VariableInfo::Invalid:
0215             qCWarning(KDEV_QMAKE) << "invalid qmake variable:" << variable;
0216             continue;
0217         }
0218 
0219         for (const auto& pos : vi.positions) {
0220             value.replace(pos.start, pos.end - pos.start + 1, varValue);
0221         }
0222     }
0223 
0224     const QStringList ret = value.split(QLatin1Char(' '), Qt::SkipEmptyParts);
0225     ifDebug(qCDebug(KDEV_QMAKE) << "resolved variable" << var << "to" << ret;) return ret;
0226 }
0227 
0228 QStringList QMakeFileVisitor::evaluateMacro(const QString& function, const QStringList& arguments) const
0229 {
0230     if (function == QLatin1String("qtLibraryTarget")) {
0231         return QStringList() << arguments.first();
0232     } ///  TODO: support more built-in qmake functions
0233 
0234     QHash<QString, QMake::ScopeBodyAST*>::const_iterator it = m_userMacros.find(function);
0235     if (it != m_userMacros.constEnd()) {
0236         qCDebug(KDEV_QMAKE) << "calling user macro:" << function << arguments;
0237         QMakeFileVisitor visitor(this, m_baseFile);
0238         return visitor.visitMacro(it.value(), arguments);
0239     } else {
0240         qCWarning(KDEV_QMAKE) << "unhandled macro call:" << function << arguments;
0241     }
0242     return QStringList();
0243 }