File indexing completed on 2024-05-05 16:46:16
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 }