/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the Qt Linguist of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the either Technology Preview License Agreement or the ** Beta Release License Agreement. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://www.qtsoftware.com/contact. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "profileevaluator.h" #include "proparserutils.h" #include "proitems.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN32 #define QT_POPEN _popen #else #define QT_POPEN popen #endif QT_BEGIN_NAMESPACE /////////////////////////////////////////////////////////////////////// // // ProFileEvaluator::Private // /////////////////////////////////////////////////////////////////////// class ProFileEvaluator::Private : public AbstractProItemVisitor { public: Private(ProFileEvaluator *q_); bool read(ProFile *pro); ProBlock *currentBlock(); void updateItem(); bool parseLine(const QString &line); void insertVariable(const QString &line, int *i); void insertOperator(const char op); void insertComment(const QString &comment); void enterScope(bool multiLine); void leaveScope(); void finalizeBlock(); // implementation of AbstractProItemVisitor bool visitBeginProBlock(ProBlock *block); bool visitEndProBlock(ProBlock *block); bool visitBeginProVariable(ProVariable *variable); bool visitEndProVariable(ProVariable *variable); bool visitBeginProFile(ProFile *value); bool visitEndProFile(ProFile *value); bool visitProValue(ProValue *value); bool visitProFunction(ProFunction *function); bool visitProOperator(ProOperator *oper); bool visitProCondition(ProCondition *condition); QStringList valuesDirect(const QString &variableName) const { return m_valuemap[variableName]; } QStringList values(const QString &variableName) const; QStringList values(const QString &variableName, const ProFile *pro) const; QString propertyValue(const QString &val) const; bool isActiveConfig(const QString &config, bool regex = false); QStringList expandPattern(const QString &pattern); void expandPatternHelper(const QString &relName, const QString &absName, QStringList &sources_out); QStringList expandVariableReferences(const QString &value); QStringList evaluateExpandFunction(const QString &function, const QString &arguments); QString format(const char *format) const; QString currentFileName() const; QString getcwd() const; ProFile *currentProFile() const; bool evaluateConditionalFunction(const QString &function, const QString &arguments, bool *result); bool evaluateFile(const QString &fileName, bool *result); bool evaluateFeatureFile(const QString &fileName, bool *result); QStringList qmakeFeaturePaths(); ProFileEvaluator *q; QStack m_blockstack; ProBlock *m_block; ProItem *m_commentItem; QString m_proitem; QString m_pendingComment; bool m_syntaxError; bool m_contNextLine; bool m_condition; bool m_invertNext; QString m_lastVarName; ProVariable::VariableOperator m_variableOperator; int m_lineNo; // Error reporting QString m_oldPath; // To restore the current path to the path QStack m_profileStack; // To handle 'include(a.pri), so we can track back to 'a.pro' when finished with 'a.pri' QHash m_valuemap; // VariableName must be us-ascii, the content however can be non-us-ascii. QHash > m_filevaluemap; // Variables per include file QHash m_properties; QString m_origfile; int m_prevLineNo; // Checking whether we're assigning the same TARGET ProFile *m_prevProFile; // See m_prevLineNo bool m_verbose; }; ProFileEvaluator::Private::Private(ProFileEvaluator *q_) : q(q_) { m_prevLineNo = 0; m_prevProFile = 0; m_verbose = true; } bool ProFileEvaluator::Private::read(ProFile *pro) { QFile file(pro->fileName()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { q->errorMessage(format("%1 not readable.").arg(pro->fileName())); return false; } m_block = 0; m_commentItem = 0; m_contNextLine = false; m_syntaxError = false; m_lineNo = 1; m_blockstack.clear(); m_blockstack.push(pro); QTextStream ts(&file); while (!ts.atEnd()) { QString line = ts.readLine(); if (!parseLine(line)) { q->errorMessage(format(".pro parse failure.")); return false; } ++m_lineNo; } return true; } bool ProFileEvaluator::Private::parseLine(const QString &line0) { if (m_blockstack.isEmpty()) return false; ushort quote = 0; int parens = 0; bool contNextLine = false; QString line = line0.simplified(); for (int i = 0; !m_syntaxError && i < line.length(); ++i) { ushort c = line.at(i).unicode(); if (quote && c == quote) quote = 0; else if (c == '(') ++parens; else if (c == ')') --parens; else if (c == '"' && (i == 0 || line.at(i - 1).unicode() != '\\')) quote = c; else if (!parens && !quote) { if (c == '#') { insertComment(line.mid(i + 1)); contNextLine = m_contNextLine; break; } if (c == '\\' && i >= line.count() - 1) { updateItem(); contNextLine = true; continue; } if (m_block && (m_block->blockKind() & ProBlock::VariableKind)) { if (c == ' ') updateItem(); else m_proitem += c; continue; } if (c == ':') { enterScope(false); continue; } if (c == '{') { enterScope(true); continue; } if (c == '}') { leaveScope(); continue; } if (c == '=') { insertVariable(line, &i); continue; } if (c == '|' || c == '!') { insertOperator(c); continue; } } m_proitem += c; } m_contNextLine = contNextLine; if (!m_syntaxError) { updateItem(); if (!m_contNextLine) finalizeBlock(); } return !m_syntaxError; } void ProFileEvaluator::Private::finalizeBlock() { if (m_blockstack.isEmpty()) { m_syntaxError = true; } else { if (m_blockstack.top()->blockKind() & ProBlock::SingleLine) leaveScope(); m_block = 0; m_commentItem = 0; } } void ProFileEvaluator::Private::insertVariable(const QString &line, int *i) { ProVariable::VariableOperator opkind; if (m_proitem.isEmpty()) // Line starting with '=', like a conflict marker return; switch (m_proitem.at(m_proitem.length() - 1).unicode()) { case '+': m_proitem.chop(1); opkind = ProVariable::AddOperator; break; case '-': m_proitem.chop(1); opkind = ProVariable::RemoveOperator; break; case '*': m_proitem.chop(1); opkind = ProVariable::UniqueAddOperator; break; case '~': m_proitem.chop(1); opkind = ProVariable::ReplaceOperator; break; default: opkind = ProVariable::SetOperator; } ProBlock *block = m_blockstack.top(); m_proitem = m_proitem.trimmed(); ProVariable *variable = new ProVariable(m_proitem, block); variable->setLineNumber(m_lineNo); variable->setVariableOperator(opkind); block->appendItem(variable); m_block = variable; if (!m_pendingComment.isEmpty()) { m_block->setComment(m_pendingComment); m_pendingComment.clear(); } m_commentItem = variable; m_proitem.clear(); if (opkind == ProVariable::ReplaceOperator) { // skip util end of line or comment while (1) { ++(*i); // end of line? if (*i >= line.count()) break; // comment? if (line.at(*i).unicode() == '#') { --(*i); break; } m_proitem += line.at(*i); } m_proitem = m_proitem.trimmed(); } } void ProFileEvaluator::Private::insertOperator(const char op) { updateItem(); ProOperator::OperatorKind opkind; switch(op) { case '!': opkind = ProOperator::NotOperator; break; case '|': opkind = ProOperator::OrOperator; break; default: opkind = ProOperator::OrOperator; } ProBlock * const block = currentBlock(); ProOperator * const proOp = new ProOperator(opkind); proOp->setLineNumber(m_lineNo); block->appendItem(proOp); m_commentItem = proOp; } void ProFileEvaluator::Private::insertComment(const QString &comment) { updateItem(); QString strComment; if (!m_commentItem) strComment = m_pendingComment; else strComment = m_commentItem->comment(); if (strComment.isEmpty()) strComment = comment; else { strComment += QLatin1Char('\n'); strComment += comment.trimmed(); } strComment = strComment.trimmed(); if (!m_commentItem) m_pendingComment = strComment; else m_commentItem->setComment(strComment); } void ProFileEvaluator::Private::enterScope(bool multiLine) { updateItem(); ProBlock *parent = currentBlock(); ProBlock *block = new ProBlock(parent); block->setLineNumber(m_lineNo); parent->setBlockKind(ProBlock::ScopeKind); parent->appendItem(block); if (multiLine) block->setBlockKind(ProBlock::ScopeContentsKind); else block->setBlockKind(ProBlock::ScopeContentsKind|ProBlock::SingleLine); m_blockstack.push(block); m_block = 0; } void ProFileEvaluator::Private::leaveScope() { updateItem(); m_blockstack.pop(); finalizeBlock(); } ProBlock *ProFileEvaluator::Private::currentBlock() { if (m_block) return m_block; ProBlock *parent = m_blockstack.top(); m_block = new ProBlock(parent); m_block->setLineNumber(m_lineNo); parent->appendItem(m_block); if (!m_pendingComment.isEmpty()) { m_block->setComment(m_pendingComment); m_pendingComment.clear(); } m_commentItem = m_block; return m_block; } void ProFileEvaluator::Private::updateItem() { m_proitem = m_proitem.trimmed(); if (m_proitem.isEmpty()) return; ProBlock *block = currentBlock(); if (block->blockKind() & ProBlock::VariableKind) { m_commentItem = new ProValue(m_proitem, static_cast(block)); } else if (m_proitem.endsWith(QLatin1Char(')'))) { m_commentItem = new ProFunction(m_proitem); } else { m_commentItem = new ProCondition(m_proitem); } m_commentItem->setLineNumber(m_lineNo); block->appendItem(m_commentItem); m_proitem.clear(); } bool ProFileEvaluator::Private::visitBeginProBlock(ProBlock *block) { if (block->blockKind() == ProBlock::ScopeKind) { m_invertNext = false; m_condition = false; } return true; } bool ProFileEvaluator::Private::visitEndProBlock(ProBlock *block) { Q_UNUSED(block); return true; } bool ProFileEvaluator::Private::visitBeginProVariable(ProVariable *variable) { m_lastVarName = variable->variable(); m_variableOperator = variable->variableOperator(); return true; } bool ProFileEvaluator::Private::visitEndProVariable(ProVariable *variable) { Q_UNUSED(variable); m_lastVarName.clear(); return true; } bool ProFileEvaluator::Private::visitProOperator(ProOperator *oper) { m_invertNext = (oper->operatorKind() == ProOperator::NotOperator); return true; } bool ProFileEvaluator::Private::visitProCondition(ProCondition *cond) { if (!m_condition) { if (m_invertNext) m_condition |= !isActiveConfig(cond->text(), true); else m_condition |= isActiveConfig(cond->text(), true); } return true; } bool ProFileEvaluator::Private::visitBeginProFile(ProFile * pro) { PRE(pro); bool ok = true; m_lineNo = pro->lineNumber(); if (m_oldPath.isEmpty()) { // change the working directory for the initial profile we visit, since // that is *the* profile. All the other times we reach this function will be due to // include(file) or load(file) m_oldPath = QDir::currentPath(); m_profileStack.push(pro); ok = QDir::setCurrent(pro->directoryName()); } if (m_origfile.isEmpty()) m_origfile = pro->fileName(); return ok; } bool ProFileEvaluator::Private::visitEndProFile(ProFile * pro) { PRE(pro); bool ok = true; m_lineNo = pro->lineNumber(); if (m_profileStack.count() == 1 && !m_oldPath.isEmpty()) { m_profileStack.pop(); ok = QDir::setCurrent(m_oldPath); } return ok; } bool ProFileEvaluator::Private::visitProValue(ProValue *value) { PRE(value); m_lineNo = value->lineNumber(); QString val = value->value(); QString varName = m_lastVarName; QStringList v = expandVariableReferences(val); // Since qmake combines different values for the TARGET variable, we join // TARGET values that are on the same line. We can't do this later with all // values because this parser isn't scope-aware, so we'd risk joining // scope-specific targets together. if (varName == QLatin1String("TARGET") && m_lineNo == m_prevLineNo && currentProFile() == m_prevProFile) { QStringList targets = m_valuemap.value(QLatin1String("TARGET")); m_valuemap.remove(QLatin1String("TARGET")); QStringList lastTarget(targets.takeLast()); lastTarget << v.join(QLatin1String(" ")); targets.push_back(lastTarget.join(QLatin1String(" "))); v = targets; } m_prevLineNo = m_lineNo; m_prevProFile = currentProFile(); // The following two blocks fix bug 180128 by making all "interesting" // file name absolute in each .pro file, not just the top most one if (varName == QLatin1String("SOURCES") || varName == QLatin1String("HEADERS") || varName == QLatin1String("INTERFACES") || varName == QLatin1String("FORMS") || varName == QLatin1String("FORMS3") || varName == QLatin1String("RESOURCES")) { // matches only existent files, expand certain(?) patterns QStringList vv; for (int i = v.count(); --i >= 0; ) vv << expandPattern(v[i]); v = vv; } if (varName == QLatin1String("TRANSLATIONS")) { // also matches non-existent files, but does not expand pattern QString dir = QFileInfo(currentFileName()).absolutePath(); dir += QLatin1Char('/'); for (int i = v.count(); --i >= 0; ) v[i] = QFileInfo(dir, v[i]).absoluteFilePath(); } switch (m_variableOperator) { case ProVariable::UniqueAddOperator: // * insertUnique(&m_valuemap, varName, v, true); insertUnique(&m_filevaluemap[currentProFile()], varName, v, true); break; case ProVariable::SetOperator: // = case ProVariable::AddOperator: // + insertUnique(&m_valuemap, varName, v, false); insertUnique(&m_filevaluemap[currentProFile()], varName, v, false); break; case ProVariable::RemoveOperator: // - // fix me: interaction between AddOperator and RemoveOperator insertUnique(&m_valuemap, varName.prepend(QLatin1Char('-')), v, false); insertUnique(&m_filevaluemap[currentProFile()], varName.prepend(QLatin1Char('-')), v, false); break; case ProVariable::ReplaceOperator: // ~ { // DEFINES ~= s/a/b/?[gqi] /* Create a superset by executing replacement + adding items that have changed to original list. We're not sure if this is really the right approach, so for the time being we will just do nothing ... QChar sep = val.at(1); QStringList func = val.split(sep); if (func.count() < 3 || func.count() > 4) { q->logMessage(format("'~= operator '(function s///) expects 3 or 4 arguments.")); return false; } if (func[0] != QLatin1String("s")) { q->logMessage(format("~= operator can only handle s/// function.")); return false; } bool global = false, quote = false, case_sense = false; if (func.count() == 4) { global = func[3].indexOf(QLatin1Char('g')) != -1; case_sense = func[3].indexOf(QLatin1Char('i')) == -1; quote = func[3].indexOf(QLatin1Char('q')) != -1; } QString pattern = func[1]; QString replace = func[2]; if (quote) pattern = QRegExp::escape(pattern); QRegExp regexp(pattern, case_sense ? Qt::CaseSensitive : Qt::CaseInsensitive); QStringList replaceList = replaceInList(m_valuemap.value(varName), regexp, replace, global); // Add changed entries to list foreach (const QString &entry, replaceList) if (!m_valuemap.value(varName).contains(entry)) insertUnique(&m_valuemap, varName, QStringList() << entry, false); replaceList = replaceInList(m_filevaluemap[currentProFile()].value(varName), regexp, replace, global); foreach (const QString &entry, replaceList) if (!m_filevaluemap[currentProFile()].value(varName).contains(entry)) insertUnique(&m_filevaluemap[currentProFile()], varName, QStringList() << entry, false); */ } break; } return true; } bool ProFileEvaluator::Private::visitProFunction(ProFunction *func) { m_lineNo = func->lineNumber(); bool result = true; bool ok = true; QString text = func->text(); int lparen = text.indexOf(QLatin1Char('(')); int rparen = text.lastIndexOf(QLatin1Char(')')); Q_ASSERT(lparen < rparen); QString arguments = text.mid(lparen + 1, rparen - lparen - 1); QString funcName = text.left(lparen); ok &= evaluateConditionalFunction(funcName.trimmed(), arguments, &result); return ok; } QStringList ProFileEvaluator::Private::qmakeFeaturePaths() { QStringList concat; { const QString base_concat = QDir::separator() + QString(QLatin1String("features")); concat << base_concat + QDir::separator() + QLatin1String("mac"); concat << base_concat + QDir::separator() + QLatin1String("macx"); concat << base_concat + QDir::separator() + QLatin1String("unix"); concat << base_concat + QDir::separator() + QLatin1String("win32"); concat << base_concat + QDir::separator() + QLatin1String("mac9"); concat << base_concat + QDir::separator() + QLatin1String("qnx6"); concat << base_concat; } const QString mkspecs_concat = QDir::separator() + QString(QLatin1String("mkspecs")); QStringList feature_roots; QByteArray mkspec_path = qgetenv("QMAKEFEATURES"); if (!mkspec_path.isNull()) feature_roots += splitPathList(QString::fromLocal8Bit(mkspec_path)); /* if (prop) feature_roots += splitPathList(prop->value("QMAKEFEATURES")); if (!Option::mkfile::cachefile.isEmpty()) { QString path; int last_slash = Option::mkfile::cachefile.lastIndexOf(Option::dir_sep); if (last_slash != -1) path = Option::fixPathToLocalOS(Option::mkfile::cachefile.left(last_slash)); foreach (const QString &concat_it, concat) feature_roots << (path + concat_it); } */ QByteArray qmakepath = qgetenv("QMAKEPATH"); if (!qmakepath.isNull()) { const QStringList lst = splitPathList(QString::fromLocal8Bit(qmakepath)); foreach (const QString &item, lst) { foreach (const QString &concat_it, concat) feature_roots << (item + mkspecs_concat + concat_it); } } //if (!Option::mkfile::qmakespec.isEmpty()) // feature_roots << Option::mkfile::qmakespec + QDir::separator() + "features"; //if (!Option::mkfile::qmakespec.isEmpty()) { // QFileInfo specfi(Option::mkfile::qmakespec); // QDir specdir(specfi.absoluteFilePath()); // while (!specdir.isRoot()) { // if (!specdir.cdUp() || specdir.isRoot()) // break; // if (QFile::exists(specdir.path() + QDir::separator() + "features")) { // foreach (const QString &concat_it, concat) // feature_roots << (specdir.path() + concat_it); // break; // } // } //} foreach (const QString &concat_it, concat) feature_roots << (propertyValue(QLatin1String("QT_INSTALL_PREFIX")) + mkspecs_concat + concat_it); foreach (const QString &concat_it, concat) feature_roots << (propertyValue(QLatin1String("QT_INSTALL_DATA")) + mkspecs_concat + concat_it); return feature_roots; } QString ProFileEvaluator::Private::propertyValue(const QString &name) const { if (m_properties.contains(name)) return m_properties.value(name); if (name == QLatin1String("QT_INSTALL_PREFIX")) return QLibraryInfo::location(QLibraryInfo::PrefixPath); if (name == QLatin1String("QT_INSTALL_DATA")) return QLibraryInfo::location(QLibraryInfo::DataPath); if (name == QLatin1String("QT_INSTALL_DOCS")) return QLibraryInfo::location(QLibraryInfo::DocumentationPath); if (name == QLatin1String("QT_INSTALL_HEADERS")) return QLibraryInfo::location(QLibraryInfo::HeadersPath); if (name == QLatin1String("QT_INSTALL_LIBS")) return QLibraryInfo::location(QLibraryInfo::LibrariesPath); if (name == QLatin1String("QT_INSTALL_BINS")) return QLibraryInfo::location(QLibraryInfo::BinariesPath); if (name == QLatin1String("QT_INSTALL_PLUGINS")) return QLibraryInfo::location(QLibraryInfo::PluginsPath); if (name == QLatin1String("QT_INSTALL_TRANSLATIONS")) return QLibraryInfo::location(QLibraryInfo::TranslationsPath); if (name == QLatin1String("QT_INSTALL_CONFIGURATION")) return QLibraryInfo::location(QLibraryInfo::SettingsPath); if (name == QLatin1String("QT_INSTALL_EXAMPLES")) return QLibraryInfo::location(QLibraryInfo::ExamplesPath); if (name == QLatin1String("QT_INSTALL_DEMOS")) return QLibraryInfo::location(QLibraryInfo::DemosPath); if (name == QLatin1String("QMAKE_MKSPECS")) return qmake_mkspec_paths().join(Option::dirlist_sep); if (name == QLatin1String("QMAKE_VERSION")) return QLatin1String("1.0"); //### FIXME //return qmake_version(); #ifdef QT_VERSION_STR if (name == QLatin1String("QT_VERSION")) return QLatin1String(QT_VERSION_STR); #endif return QLatin1String("UNKNOWN"); //### } ProFile *ProFileEvaluator::Private::currentProFile() const { if (m_profileStack.count() > 0) return m_profileStack.top(); return 0; } QString ProFileEvaluator::Private::currentFileName() const { ProFile *pro = currentProFile(); if (pro) return pro->fileName(); return QString(); } QString ProFileEvaluator::Private::getcwd() const { ProFile *cur = m_profileStack.top(); return cur->directoryName(); } QStringList ProFileEvaluator::Private::expandVariableReferences(const QString &str) { QStringList ret; // if (ok) // *ok = true; if (str.isEmpty()) return ret; const ushort LSQUARE = '['; const ushort RSQUARE = ']'; const ushort LCURLY = '{'; const ushort RCURLY = '}'; const ushort LPAREN = '('; const ushort RPAREN = ')'; const ushort DOLLAR = '$'; const ushort BACKSLASH = '\\'; const ushort UNDERSCORE = '_'; const ushort DOT = '.'; const ushort SPACE = ' '; const ushort TAB = '\t'; const ushort SINGLEQUOTE = '\''; const ushort DOUBLEQUOTE = '"'; ushort unicode, quote = 0; const QChar *str_data = str.data(); const int str_len = str.length(); ushort term; QString var, args; int replaced = 0; QString current; for (int i = 0; i < str_len; ++i) { unicode = str_data[i].unicode(); const int start_var = i; if (unicode == DOLLAR && str_len > i+2) { unicode = str_data[++i].unicode(); if (unicode == DOLLAR) { term = 0; var.clear(); args.clear(); enum { VAR, ENVIRON, FUNCTION, PROPERTY } var_type = VAR; unicode = str_data[++i].unicode(); if (unicode == LSQUARE) { unicode = str_data[++i].unicode(); term = RSQUARE; var_type = PROPERTY; } else if (unicode == LCURLY) { unicode = str_data[++i].unicode(); var_type = VAR; term = RCURLY; } else if (unicode == LPAREN) { unicode = str_data[++i].unicode(); var_type = ENVIRON; term = RPAREN; } forever { if (!(unicode & (0xFF<<8)) && unicode != DOT && unicode != UNDERSCORE && //unicode != SINGLEQUOTE && unicode != DOUBLEQUOTE && (unicode < 'a' || unicode > 'z') && (unicode < 'A' || unicode > 'Z') && (unicode < '0' || unicode > '9')) break; var.append(QChar(unicode)); if (++i == str_len) break; unicode = str_data[i].unicode(); // at this point, i points to either the 'term' or 'next' character (which is in unicode) } if (var_type == VAR && unicode == LPAREN) { var_type = FUNCTION; int depth = 0; forever { if (++i == str_len) break; unicode = str_data[i].unicode(); if (unicode == LPAREN) { depth++; } else if (unicode == RPAREN) { if (!depth) break; --depth; } args.append(QChar(unicode)); } if (++i < str_len) unicode = str_data[i].unicode(); else unicode = 0; // at this point i is pointing to the 'next' character (which is in unicode) // this might actually be a term character since you can do $${func()} } if (term) { if (unicode != term) { q->logMessage(format("Missing %1 terminator [found %2]") .arg(QChar(term)) .arg(unicode ? QString(unicode) : QString::fromLatin1(("end-of-line")))); // if (ok) // *ok = false; return QStringList(); } } else { // move the 'cursor' back to the last char of the thing we were looking at --i; } // since i never points to the 'next' character, there is no reason for this to be set unicode = 0; QStringList replacement; if (var_type == ENVIRON) { replacement = split_value_list(QString::fromLocal8Bit(qgetenv(var.toLatin1().constData()))); } else if (var_type == PROPERTY) { replacement << propertyValue(var); } else if (var_type == FUNCTION) { replacement << evaluateExpandFunction(var, args); } else if (var_type == VAR) { replacement = values(var); } if (!(replaced++) && start_var) current = str.left(start_var); if (!replacement.isEmpty()) { if (quote) { current += replacement.join(QString(Option::field_sep)); } else { current += replacement.takeFirst(); if (!replacement.isEmpty()) { if (!current.isEmpty()) ret.append(current); current = replacement.takeLast(); if (!replacement.isEmpty()) ret += replacement; } } } } else { if (replaced) current.append(QLatin1Char('$')); } } if (quote && unicode == quote) { unicode = 0; quote = 0; } else if (unicode == BACKSLASH) { bool escape = false; const char *symbols = "[]{}()$\\'\""; for (const char *s = symbols; *s; ++s) { if (str_data[i+1].unicode() == (ushort)*s) { i++; escape = true; if (!(replaced++)) current = str.left(start_var); current.append(str.at(i)); break; } } if (escape || !replaced) unicode =0; } else if (!quote && (unicode == SINGLEQUOTE || unicode == DOUBLEQUOTE)) { quote = unicode; unicode = 0; if (!(replaced++) && i) current = str.left(i); } else if (!quote && (unicode == SPACE || unicode == TAB)) { unicode = 0; if (!current.isEmpty()) { ret.append(current); current.clear(); } } if (replaced && unicode) current.append(QChar(unicode)); } if (!replaced) ret = QStringList(str); else if (!current.isEmpty()) ret.append(current); return ret; } bool ProFileEvaluator::Private::isActiveConfig(const QString &config, bool regex) { // magic types for easy flipping if (config == QLatin1String("true")) return true; if (config == QLatin1String("false")) return false; // mkspecs if ((Option::target_mode == Option::TARG_MACX_MODE || Option::target_mode == Option::TARG_QNX6_MODE || Option::target_mode == Option::TARG_UNIX_MODE) && config == QLatin1String("unix")) return true; if (Option::target_mode == Option::TARG_MACX_MODE && config == QLatin1String("macx")) return true; if (Option::target_mode == Option::TARG_QNX6_MODE && config == QLatin1String("qnx6")) return true; if (Option::target_mode == Option::TARG_MAC9_MODE && config == QLatin1String("mac9")) return true; if ((Option::target_mode == Option::TARG_MAC9_MODE || Option::target_mode == Option::TARG_MACX_MODE) && config == QLatin1String("mac")) return true; if (Option::target_mode == Option::TARG_WIN_MODE && config == QLatin1String("win32")) return true; QRegExp re(config, Qt::CaseSensitive, QRegExp::Wildcard); QString spec = Option::qmakespec; if ((regex && re.exactMatch(spec)) || (!regex && spec == config)) return true; return false; } QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &func, const QString &arguments) { QStringList argumentsList = split_arg_list(arguments); QStringList args; for (int i = 0; i < argumentsList.count(); ++i) args += expandVariableReferences(argumentsList[i]).join(Option::field_sep); enum ExpandFunc { E_MEMBER=1, E_FIRST, E_LAST, E_CAT, E_FROMFILE, E_EVAL, E_LIST, E_SPRINTF, E_JOIN, E_SPLIT, E_BASENAME, E_DIRNAME, E_SECTION, E_FIND, E_SYSTEM, E_UNIQUE, E_QUOTE, E_ESCAPE_EXPAND, E_UPPER, E_LOWER, E_FILES, E_PROMPT, E_RE_ESCAPE, E_REPLACE }; static QHash *expands = 0; if (!expands) { expands = new QHash; expands->insert(QLatin1String("member"), E_MEMBER); //v (implemented) expands->insert(QLatin1String("first"), E_FIRST); //v expands->insert(QLatin1String("last"), E_LAST); //v expands->insert(QLatin1String("cat"), E_CAT); expands->insert(QLatin1String("fromfile"), E_FROMFILE); expands->insert(QLatin1String("eval"), E_EVAL); expands->insert(QLatin1String("list"), E_LIST); expands->insert(QLatin1String("sprintf"), E_SPRINTF); expands->insert(QLatin1String("join"), E_JOIN); //v expands->insert(QLatin1String("split"), E_SPLIT); //v expands->insert(QLatin1String("basename"), E_BASENAME); //v expands->insert(QLatin1String("dirname"), E_DIRNAME); //v expands->insert(QLatin1String("section"), E_SECTION); expands->insert(QLatin1String("find"), E_FIND); expands->insert(QLatin1String("system"), E_SYSTEM); //v expands->insert(QLatin1String("unique"), E_UNIQUE); expands->insert(QLatin1String("quote"), E_QUOTE); //v expands->insert(QLatin1String("escape_expand"), E_ESCAPE_EXPAND); expands->insert(QLatin1String("upper"), E_UPPER); expands->insert(QLatin1String("lower"), E_LOWER); expands->insert(QLatin1String("re_escape"), E_RE_ESCAPE); expands->insert(QLatin1String("files"), E_FILES); expands->insert(QLatin1String("prompt"), E_PROMPT); expands->insert(QLatin1String("replace"), E_REPLACE); } ExpandFunc func_t = ExpandFunc(expands->value(func.toLower())); QStringList ret; switch (func_t) { case E_BASENAME: case E_DIRNAME: case E_SECTION: { bool regexp = false; QString sep, var; int beg = 0; int end = -1; if (func_t == E_SECTION) { if (args.count() != 3 && args.count() != 4) { q->logMessage(format("%1(var) section(var, sep, begin, end) " "requires three or four arguments.").arg(func)); } else { var = args[0]; sep = args[1]; beg = args[2].toInt(); if (args.count() == 4) end = args[3].toInt(); } } else { if (args.count() != 1) { q->logMessage(format("%1(var) requires one argument.").arg(func)); } else { var = args[0]; regexp = true; sep = QLatin1String("[\\\\/]"); if (func_t == E_DIRNAME) end = -2; else beg = -1; } } if (!var.isNull()) { foreach (const QString str, values(var)) { if (regexp) ret += str.section(QRegExp(sep), beg, end); else ret += str.section(sep, beg, end); } } break; } case E_JOIN: { if (args.count() < 1 || args.count() > 4) { q->logMessage(format("join(var, glue, before, after) requires one to four arguments.")); } else { QString glue, before, after; if (args.count() >= 2) glue = args[1]; if (args.count() >= 3) before = args[2]; if (args.count() == 4) after = args[3]; const QStringList &var = values(args.first()); if (!var.isEmpty()) ret.append(before + var.join(glue) + after); } break; } case E_SPLIT: { if (args.count() != 2) { q->logMessage(format("split(var, sep) requires two arguments")); } else { QString sep = args.at(1); foreach (const QString &var, values(args.first())) foreach (const QString &splt, var.split(sep)) ret.append(splt); } break; } case E_MEMBER: { if (args.count() < 1 || args.count() > 3) { q->logMessage(format("member(var, start, end) requires one to three arguments.")); } else { bool ok = true; const QStringList var = values(args.first()); int start = 0, end = 0; if (args.count() >= 2) { QString start_str = args[1]; start = start_str.toInt(&ok); if (!ok) { if (args.count() == 2) { int dotdot = start_str.indexOf(QLatin1String("..")); if (dotdot != -1) { start = start_str.left(dotdot).toInt(&ok); if (ok) end = start_str.mid(dotdot+2).toInt(&ok); } } if (!ok) q->logMessage(format("member() argument 2 (start) '%2' invalid.") .arg(start_str)); } else { end = start; if (args.count() == 3) end = args[2].toInt(&ok); if (!ok) q->logMessage(format("member() argument 3 (end) '%2' invalid.\n") .arg(args[2])); } } if (ok) { if (start < 0) start += var.count(); if (end < 0) end += var.count(); if (start < 0 || start >= var.count() || end < 0 || end >= var.count()) { //nothing } else if (start < end) { for (int i = start; i <= end && var.count() >= i; i++) ret.append(var[i]); } else { for (int i = start; i >= end && var.count() >= i && i >= 0; i--) ret += var[i]; } } } break; } case E_FIRST: case E_LAST: { if (args.count() != 1) { q->logMessage(format("%1(var) requires one argument.").arg(func)); } else { const QStringList var = values(args.first()); if (!var.isEmpty()) { if (func_t == E_FIRST) ret.append(var[0]); else ret.append(var.last()); } } break; } case E_SYSTEM: { if (m_condition) { if (args.count() < 1 || args.count() > 2) { q->logMessage(format("system(execute) requires one or two arguments.")); } else { char buff[256]; FILE *proc = QT_POPEN(args[0].toLatin1(), "r"); bool singleLine = true; if (args.count() > 1) singleLine = (args[1].toLower() == QLatin1String("true")); QString output; while (proc && !feof(proc)) { int read_in = int(fread(buff, 1, 255, proc)); if (!read_in) break; for (int i = 0; i < read_in; i++) { if ((singleLine && buff[i] == '\n') || buff[i] == '\t') buff[i] = ' '; } buff[read_in] = '\0'; output += QLatin1String(buff); } ret += split_value_list(output); } } break; } case E_QUOTE: for (int i = 0; i < args.count(); ++i) ret += QStringList(args.at(i)); break; case 0: q->logMessage(format("'%1' is not a function").arg(func)); break; default: q->logMessage(format("Function '%1' is not implemented").arg(func)); break; } return ret; } bool ProFileEvaluator::Private::evaluateConditionalFunction(const QString &function, const QString &arguments, bool *result) { QStringList argumentsList = split_arg_list(arguments); QString sep; sep.append(Option::field_sep); QStringList args; for (int i = 0; i < argumentsList.count(); ++i) args += expandVariableReferences(argumentsList[i]).join(sep); enum ConditionFunc { CF_CONFIG = 1, CF_CONTAINS, CF_COUNT, CF_EXISTS, CF_INCLUDE, CF_LOAD, CF_ISEMPTY, CF_SYSTEM, CF_MESSAGE}; static QHash *functions = 0; if (!functions) { functions = new QHash; functions->insert(QLatin1String("load"), CF_LOAD); //v functions->insert(QLatin1String("include"), CF_INCLUDE); //v functions->insert(QLatin1String("message"), CF_MESSAGE); //v functions->insert(QLatin1String("warning"), CF_MESSAGE); //v functions->insert(QLatin1String("error"), CF_MESSAGE); //v } bool cond = false; bool ok = true; ConditionFunc func_t = (ConditionFunc)functions->value(function); switch (func_t) { case CF_CONFIG: { if (args.count() < 1 || args.count() > 2) { q->logMessage(format("CONFIG(config) requires one or two arguments.")); ok = false; break; } if (args.count() == 1) { //cond = isActiveConfig(args.first()); XXX break; } const QStringList mutuals = args[1].split(QLatin1Char('|')); const QStringList &configs = valuesDirect(QLatin1String("CONFIG")); for (int i = configs.size() - 1 && ok; i >= 0; i--) { for (int mut = 0; mut < mutuals.count(); mut++) { if (configs[i] == mutuals[mut].trimmed()) { cond = (configs[i] == args[0]); goto done_T_CONFIG; } } } done_T_CONFIG: break; } case CF_CONTAINS: { if (args.count() < 2 || args.count() > 3) { q->logMessage(format("contains(var, val) requires two or three arguments.")); ok = false; break; } QRegExp regx(args[1]); const QStringList &l = values(args.first()); if (args.count() == 2) { for (int i = 0; i < l.size(); ++i) { const QString val = l[i]; if (regx.exactMatch(val) || val == args[1]) { cond = true; break; } } } else { const QStringList mutuals = args[2].split(QLatin1Char('|')); for (int i = l.size() - 1; i >= 0; i--) { const QString val = l[i]; for (int mut = 0; mut < mutuals.count(); mut++) { if (val == mutuals[mut].trimmed()) { cond = (regx.exactMatch(val) || val == args[1]); goto done_T_CONTAINS; } } } } done_T_CONTAINS: break; } case CF_COUNT: { if (args.count() != 2 && args.count() != 3) { q->logMessage(format("count(var, count) requires two or three arguments.")); ok = false; break; } if (args.count() == 3) { QString comp = args[2]; if (comp == QLatin1String(">") || comp == QLatin1String("greaterThan")) { cond = values(args.first()).count() > args[1].toInt(); } else if (comp == QLatin1String(">=")) { cond = values(args.first()).count() >= args[1].toInt(); } else if (comp == QLatin1String("<") || comp == QLatin1String("lessThan")) { cond = values(args.first()).count() < args[1].toInt(); } else if (comp == QLatin1String("<=")) { cond = values(args.first()).count() <= args[1].toInt(); } else if (comp == QLatin1String("equals") || comp == QLatin1String("isEqual") || comp == QLatin1String("=") || comp == QLatin1String("==")) { cond = values(args.first()).count() == args[1].toInt(); } else { ok = false; q->logMessage(format("unexpected modifier to count(%2)").arg(comp)); } break; } cond = values(args.first()).count() == args[1].toInt(); break; } case CF_INCLUDE: { QString parseInto; if (args.count() == 2) { parseInto = args[1]; } else if (args.count() != 1) { q->logMessage(format("include(file) requires one or two arguments.")); ok = false; break; } QString fileName = args.first(); // ### this breaks if we have include(c:/reallystupid.pri) but IMHO that's really bad style. QDir currentProPath(getcwd()); fileName = QDir::cleanPath(currentProPath.absoluteFilePath(fileName)); ok = evaluateFile(fileName, &ok); break; } case CF_LOAD: { QString parseInto; bool ignore_error = false; if (args.count() == 2) { QString sarg = args[1]; ignore_error = (sarg.toLower() == QLatin1String("true") || sarg.toInt()); } else if (args.count() != 1) { q->logMessage(format("load(feature) requires one or two arguments.")); ok = false; break; } ok = evaluateFeatureFile( args.first(), &cond); break; } case CF_MESSAGE: { if (args.count() != 1) { q->logMessage(format("%1(message) requires one argument.").arg(function)); ok = false; break; } QString msg = args.first(); if (function == QLatin1String("error")) { QStringList parents; foreach (ProFile *proFile, m_profileStack) parents.append(proFile->fileName()); if (!parents.isEmpty()) parents.takeLast(); if (parents.isEmpty()) q->fileMessage(format("Project ERROR: %1").arg(msg)); else q->fileMessage(format("Project ERROR: %1. File was included from: '%2'") .arg(msg).arg(parents.join(QLatin1String("', '")))); } else { q->fileMessage(format("Project MESSAGE: %1").arg(msg)); } break; } case CF_SYSTEM: { if (args.count() != 1) { q->logMessage(format("system(exec) requires one argument.")); ok = false; break; } ok = system(args.first().toLatin1().constData()) == 0; break; } case CF_ISEMPTY: { if (args.count() != 1) { q->logMessage(format("isEmpty(var) requires one argument.")); ok = false; break; } QStringList sl = values(args.first()); if (sl.count() == 0) { cond = true; } else if (sl.count() > 0) { QString var = sl.first(); cond = (var.isEmpty()); } break; } case CF_EXISTS: { if (args.count() != 1) { q->logMessage(format("exists(file) requires one argument.")); ok = false; break; } QString file = args.first(); file = QDir::cleanPath(file); if (QFile::exists(file)) { cond = true; break; } //regular expression I guess QString dirstr = getcwd(); int slsh = file.lastIndexOf(Option::dir_sep); if (slsh != -1) { dirstr = file.left(slsh+1); file = file.right(file.length() - slsh - 1); } cond = QDir(dirstr).entryList(QStringList(file)).count(); break; } } if (result) *result = cond; return ok; } QStringList ProFileEvaluator::Private::values(const QString &variableName) const { if (variableName == QLatin1String("TARGET")) { QStringList list = m_valuemap.value(variableName); if (!m_origfile.isEmpty()) list.append(QFileInfo(m_origfile).baseName()); return list; } if (variableName == QLatin1String("PWD")) { return QStringList(getcwd()); } return m_valuemap.value(variableName); } QStringList ProFileEvaluator::Private::values(const QString &variableName, const ProFile *pro) const { if (variableName == QLatin1String("TARGET")) { QStringList list = m_filevaluemap[pro].value(variableName); if (!m_origfile.isEmpty()) list.append(QFileInfo(m_origfile).baseName()); return list; } if (variableName == QLatin1String("PWD")) { return QStringList(QFileInfo(pro->fileName()).absoluteFilePath()); } return m_filevaluemap[pro].value(variableName); } ProFile *ProFileEvaluator::parsedProFile(const QString &fileName) { QFileInfo fi(fileName); if (fi.exists()) { QString fn = QDir::cleanPath(fi.absoluteFilePath()); foreach (const ProFile *pf, d->m_profileStack) if (pf->fileName() == fn) { errorMessage(d->format("circular inclusion of %1").arg(fn)); return 0; } ProFile *pro = new ProFile(fn); if (d->read(pro)) return pro; delete pro; } return 0; } void ProFileEvaluator::releaseParsedProFile(ProFile *proFile) { delete proFile; } bool ProFileEvaluator::Private::evaluateFile(const QString &fileName, bool *result) { bool ok = true; ProFile *pro = q->parsedProFile(fileName); if (pro) { m_profileStack.push(pro); ok = (currentProFile() ? pro->Accept(this) : false); m_profileStack.pop(); q->releaseParsedProFile(pro); if (result) *result = true; } else { if (result) *result = false; } return ok; } bool ProFileEvaluator::Private::evaluateFeatureFile(const QString &fileName, bool *result) { QString fn; foreach (const QString &path, qmakeFeaturePaths()) { QString fname = path + QLatin1Char('/') + fileName; if (QFileInfo(fname).exists()) { fn = fname; break; } fname += QLatin1String(".prf"); if (QFileInfo(fname).exists()) { fn = fname; break; } } return fn.isEmpty() ? false : evaluateFile(fn, result); } void ProFileEvaluator::Private::expandPatternHelper(const QString &relName, const QString &absName, QStringList &sources_out) { const QStringList vpaths = values(QLatin1String("VPATH")) + values(QLatin1String("QMAKE_ABSOLUTE_SOURCE_PATH")) + values(QLatin1String("DEPENDPATH")) + values(QLatin1String("VPATH_SOURCES")); QFileInfo fi(absName); bool found = fi.exists(); // Search in all vpaths if (!found) { foreach (const QString &vpath, vpaths) { fi.setFile(vpath + QDir::separator() + relName); if (fi.exists()) { found = true; break; } } } if (found) { sources_out += fi.absoluteFilePath(); // Not resolving symlinks } else { QString val = relName; QString dir; QString wildcard = val; QString real_dir; if (wildcard.lastIndexOf(QLatin1Char('/')) != -1) { dir = wildcard.left(wildcard.lastIndexOf(QLatin1Char('/')) + 1); real_dir = dir; wildcard = wildcard.right(wildcard.length() - dir.length()); } if (real_dir.isEmpty() || QFileInfo(real_dir).exists()) { QStringList files = QDir(real_dir).entryList(QStringList(wildcard)); if (files.isEmpty()) { q->logMessage(format("Failure to find %1").arg(val)); } else { QString a; for (int i = files.count() - 1; i >= 0; --i) { if (files[i] == QLatin1String(".") || files[i] == QLatin1String("..")) continue; a = dir + files[i]; sources_out += a; } } } else { q->logMessage(format("Cannot match %1/%2, as %3 does not exist.") .arg(real_dir).arg(wildcard).arg(real_dir)); } } } /* * Lookup of files are done in this order: * 1. look in pwd * 2. look in vpaths * 3. expand wild card files relative from the profiles folder **/ // FIXME: This code supports something that I'd consider a flaw in .pro file syntax // which is not even documented. So arguably this can be ditched completely... QStringList ProFileEvaluator::Private::expandPattern(const QString& pattern) { if (!currentProFile()) return QStringList(); QStringList sources_out; const QString absName = QDir::cleanPath(QDir::current().absoluteFilePath(pattern)); expandPatternHelper(pattern, absName, sources_out); return sources_out; } QString ProFileEvaluator::Private::format(const char *fmt) const { ProFile *pro = currentProFile(); QString fileName = pro ? pro->fileName() : QLatin1String("Not a file"); int lineNumber = pro ? m_lineNo : 0; return QString::fromLatin1("%1(%2):").arg(fileName).arg(lineNumber) + QString::fromAscii(fmt); } /////////////////////////////////////////////////////////////////////// // // ProFileEvaluator // /////////////////////////////////////////////////////////////////////// ProFileEvaluator::ProFileEvaluator() : d(new Private(this)) { Option::init(); } ProFileEvaluator::~ProFileEvaluator() { delete d; } bool ProFileEvaluator::contains(const QString &variableName) const { return d->m_valuemap.contains(variableName); } QStringList ProFileEvaluator::values(const QString &variableName) const { return d->values(variableName); } QStringList ProFileEvaluator::values(const QString &variableName, const ProFile *pro) const { return d->values(variableName, pro); } ProFileEvaluator::TemplateType ProFileEvaluator::templateType() { QStringList templ = values(QLatin1String("TEMPLATE")); if (templ.count() >= 1) { QString t = templ.last().toLower(); if (t == QLatin1String("app")) return TT_Application; if (t == QLatin1String("lib")) return TT_Library; if (t == QLatin1String("subdirs")) return TT_Subdirs; } return TT_Unknown; } bool ProFileEvaluator::queryProFile(ProFile *pro) { return d->read(pro); } bool ProFileEvaluator::accept(ProFile *pro) { return pro->Accept(d); } QString ProFileEvaluator::propertyValue(const QString &name) const { return d->propertyValue(name); } namespace { template void insert(QHash *out, const QHash &in) { typename QHash::const_iterator i = in.begin(); while (i != in.end()) { out->insert(i.key(), i.value()); ++i; } } } // anon namespace void ProFileEvaluator::addVariables(const QHash &variables) { insert(&(d->m_valuemap), variables); } void ProFileEvaluator::addProperties(const QHash &properties) { insert(&(d->m_properties), properties); } void ProFileEvaluator::logMessage(const QString &message) { if (d->m_verbose) qWarning("%s", qPrintable(message)); } void ProFileEvaluator::fileMessage(const QString &message) { qWarning("%s", qPrintable(message)); } void ProFileEvaluator::errorMessage(const QString &message) { qWarning("%s", qPrintable(message)); } void ProFileEvaluator::setVerbose(bool on) { d->m_verbose = on; } void evaluateProFile(const ProFileEvaluator &visitor, QHash *varMap) { QStringList sourceFiles; QString codecForTr; QString codecForSource; QStringList tsFileNames; // app/lib template sourceFiles += visitor.values(QLatin1String("SOURCES")); sourceFiles += visitor.values(QLatin1String("HEADERS")); tsFileNames = visitor.values(QLatin1String("TRANSLATIONS")); QStringList trcodec = visitor.values(QLatin1String("CODEC")) + visitor.values(QLatin1String("DEFAULTCODEC")) + visitor.values(QLatin1String("CODECFORTR")); if (!trcodec.isEmpty()) codecForTr = trcodec.last(); QStringList srccodec = visitor.values(QLatin1String("CODECFORSRC")); if (!srccodec.isEmpty()) codecForSource = srccodec.last(); QStringList forms = visitor.values(QLatin1String("INTERFACES")) + visitor.values(QLatin1String("FORMS")) + visitor.values(QLatin1String("FORMS3")); sourceFiles << forms; sourceFiles.sort(); sourceFiles.removeDuplicates(); tsFileNames.sort(); tsFileNames.removeDuplicates(); varMap->insert("SOURCES", sourceFiles); varMap->insert("CODECFORTR", QStringList() << codecForTr); varMap->insert("CODECFORSRC", QStringList() << codecForSource); varMap->insert("TRANSLATIONS", tsFileNames); } bool evaluateProFile(const QString &fileName, bool verbose, QHash *varMap) { QFileInfo fi(fileName); if (!fi.exists()) return false; ProFile pro(fi.absoluteFilePath()); ProFileEvaluator visitor; visitor.setVerbose(verbose); if (!visitor.queryProFile(&pro)) return false; if (!visitor.accept(&pro)) return false; evaluateProFile(visitor, varMap); return true; } QT_END_NAMESPACE