• qmake source code 解读


    qmake 主要框架流程

    qmake的主要功能执行入口在main.cpp中的runQMake(int argc, char **argv)中。其主要框架流程如下:

    1. runQMake(int argc, char **argv){
    2. QMakeVfs vfs; //初始化qmake的文件系统。virtual file system。vfs会为每个文件赋予一个id,并提供根据id进行操作的函数。
    3. Option::vfs = &vfs;
    4. QMakeGlobals globals;
    5. Option::globals = &globals; globalst提供配置查询环境参数和操作环境变量相关的一些函数。
    6. .......
    7. Option::init(argc, argv); //初始化参数
    8. .....
    9. QMakeProperty prop; //初始化property。构造函数中调用QMakeProperty 的reload函数。
    10. ....
    11. QMakeParser parser(&proFileCache, &vfs, &Option::evalHandler);//创建parser对象,parser的主要作用是将脚本代码转换成ProToken类型的数据,并存放到Profile对象中的m_proitems中。
    12. Option::parser = &parser;
    13. ......
    14. QMakeProject project;//创建qmake工程,构造函数参数会使用到option::parser,并赋予成员变量其m_parser,结构中的成员最终用于辅助生成makefile
    15. //QMakeProject::QMakeProject(): QMakeEvaluator(Option::globals, Option::parser, Option::vfs, &Option::evalHandler){}
    16. .......
    17. project.read(fn) //加载和解析(词法语法语义)配置文件(.prl、pri、.conf、.prf),解析工程pro文件
    18. ......
    19. MetaMakefileGenerator *mkfile = MetaMakefileGenerator::createMetaGenerator(&project, QString(), false, &success); //创建makefile对象向,内部通过生成SubdirsMetaMakefileGenerator或BuildsMetaMakefileGenerator对象,初始化并返回。
    20. .....
    21. mkfile->write() //生成Makefile.Debug、Makefile.Release、Makefile三个文件。
    22. }

    QMakeProject是整体的架构,他继承与QMakeEvaluator,用于描述一个qt工程,除了完成处理语法和语义分析的QMakeEvaluator的功能,还会做一些工程相关的处理(比如路径、缓存文件)。一个QMakeProject对象含有一个用于处理词法分析的QMakeParser对象成员。QMakeProject做完解析后,作为参数传入MetaMakefileGenerator对象中,生成makefile文件。

    解析从QMakeProject的read函数开始,内部会调用QMakeEvaluator::evaluateFile()。开始进行词法和语法分析。

    词法分析

    QMakeParser对象进行语法分析。QMakeParser主要作用是将qmake language语法的代码进行标记化(tokenized),将原始代码全部解析成ProToken类型的数据,解析内容放到Profile对象的m_proItems成员中。主要操作在QMakeParser::read中,read的调用堆栈如下:

    1. //E:\workspace\QtWork\qmake\library\qmakeevaluator.cpp
    2. evaluateFile是执行脚本文件的入口函数
    3. QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateFile(
    4. const QString &fileName, QMakeHandler::EvalFileType type, LoadFlags flags)
    5. {
    6. QMakeParser::ParseFlags pflags = QMakeParser::ParseUseCache;
    7. if (!(flags & LoadSilent))
    8. pflags |= QMakeParser::ParseReportMissing;
    9. if (ProFile *pro = m_parser->parsedProFile(fileName, pflags)) {
    10. ProStringList &tiif = m_valuemapStack.first()[ProKey("QMAKE_VISIT_FILES")];
    11. ProString tifn(fileName);
    12. if (!tiif.contains(tifn))
    13. tiif << tifn;
    14. m_locationStack.push(m_current);
    15. VisitReturn ok = visitProFile(pro, type, flags);
    16. m_current = m_locationStack.pop();
    17. pro->deref();
    18. if (ok == ReturnTrue && !(flags & LoadHidden)) {
    19. ProStringList &iif = m_valuemapStack.first()[ProKey("QMAKE_INTERNAL_INCLUDED_FILES")];
    20. ProString ifn(fileName);
    21. if (!iif.contains(ifn))
    22. iif << ifn;
    23. }
    24. return ok;
    25. } else {
    26. debugMsgInternal(0, "failed evaluateing file %s,parse error", qPrintable(fileName));
    27. return ReturnFalse;
    28. }
    29. }
    30. --------------------------
    31. parsedProFile作用是判断文件是否已经被解析,未解析会调用readFile将文件内容读取到内存中,存放到content变量中,然后调用parsedProBlock解析读取到的代码块。
    32. ProFile *parsedProFile(const QString &fileName, ParseFlags flags)
    33. parsedProBlock创建profile对象,对具体的代码块进行解析,然后返回profile对象
    34. ProFile *parsedProBlock(const QStringRef &contents, int id, const QString &name, int line, SubGrammar grammar)
    35. {
    36. ProFile *pro = new ProFile(id, name);
    37. read(pro, contents, line, grammar);
    38. return pro;
    39. }
    40. parsedProBlock对具体的代码块进行解析,可以是一整个文件的代码,也可以是eval中传入的小片代码。
    41. 然后返回profile对象。词法分析阶段会进行字符转义操作,和预处理操作。
    42. void QMakeParser::read(ProFile *pro, const QStringRef &in, int line, SubGrammar grammar){.....}

    字符转义和部分特殊变量的预处理在这个阶段进行。引号是否缺乏以及括号是否缺乏会在这个阶段得出结果。

    语法语义分析

    对脚本文件的语法和语义分析用QMakeProject中的成员QMakeEvaluator对象进行处理,一个project对象只有一个QMakeEvaluator成员。在evaluateFile函数中,经过QMakeParser的解析出来的Profile传递给QMakeEaluator->visitprofile,开始对内容进行语法和语义分析。主要操作在visitProBlock中,调用堆栈如下。

     

    1. E:\workspace\QtWork\qmake\library\qmakeevaluator.cpp
    2. visitProfile的作用是在处理工程文件的时候预先加载背后的配置文件,以及处理不同时态(state)的命令行命令。
    3. 同时维护一个m_profileStack栈,用于存放parsed的ProFile。
    4. QMakeEvaluator::VisitReturn QMakeEvaluator::visitProFile(ProFile *pro, QMakeHandler::EvalFileType type, LoadFlags flags){}
    5. 这个visitProBlock调用下面的visitProBlock
    6. QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(ProFile *pro, const ushort *tokPtr)
    7. {
    8. m_current.pro = pro;
    9. m_current.line = 0;
    10. return visitProBlock(tokPtr);
    11. }
    12. 这个visitProBlock是进行词法和语法分析的主要逻辑。所有变量存入到m_valuemapStack中,这是一个栈和链表的功能兼有的数据结构类型的成员。
    13. QMakeEvaluator::VisitReturn QMakeEvaluator::visitProBlock(const ushort *tokPtr)

    QMakeEvaluator的几个重要数据成员

    1. typedef QHash<ProKey, ProStringList> ProValueMap;
    2. class ProValueMapStack : public QLinkedList<ProValueMap>{}
    3. struct ProFunctionDefs {
    4. QHash<ProKey, ProFunctionDef> testFunctions;
    5. QHash<ProKey, ProFunctionDef> replaceFunctions;
    6. };
    7. class QMakeEvaluator{
    8. QStack<ProFile *> m_profileStack; // Includes only
    9. ProValueMapStack m_valuemapStack;
    10. ProFunctionDefs m_functionDefs;
    11. QStack<Location> m_locationStack;
    12. }

    QMakeEvaluator::m_valuemapStack:是存放变量的键值的数据成员,他的类型如下,兼有链表和栈功能操作的数据结构类型的变量,元素类型为ProValueMap类型。ProValueMap是QHash 类型。QMakeEvaluator初始化时会往其中push一个ProValueMap对象,这个对象存放全局的变量和值键值对。
    一个ProValueMap中存放的是一个作用域内的所有变量和值的键值对。有子作用域时,会创建一个新的ProValueMap存放该域内的变量名和值。qmake只有两种作用于,一个是全局的,一个是函数内的。 (qmake language脚本语言的作用域与其他的语言的作用域有点差异,可以参考:qmake language
    QMakeEvaluator初始化时创建一个ProValueMap对象,用于存放全局变量和其值的键值对;每次调用自定义函数的时候会创建一个ProValueMap对象,存放该函数内的局部变量和键值对。如果函数内有调用的嵌套,会继续为嵌套的函数创建ProValueMap对象,新创建的ProValueMap会push到m_valuemapStack中,函数调用结束后再从其中删除(相应代码逻辑在QMakeEvaluator::evaluateFunction中)。函数内访问某个变量会先从该函数的ProValueMap中查找,未找到会到m_valuemapStack的下一层中寻找,直到找到变量或者遍历完所有变量名。
    ProKey 存放变量名,ProStringList存放变量的值(qmake language中只有字符串组的数据类型)。
    下面是qmake中访问一个变量的函数:

    1. //E:\workspace\QtWork\qmake\library\qmakeevaluator.cpp
    2. ProStringList QMakeEvaluator::values(const ProKey &variableName) const
    3. {
    4. ProValueMapStack::ConstIterator vmi = m_valuemapStack.constEnd();
    5. for (bool first = true; ; first = false) {
    6. --vmi;
    7. ProValueMap::ConstIterator it = (*vmi).constFind(variableName);
    8. if (it != (*vmi).constEnd()) {
    9. if (it->constBegin() == statics.fakeValue.constBegin())
    10. break;
    11. return *it;
    12. }
    13. if (vmi == m_valuemapStack.constBegin())
    14. break;
    15. if (first && isFunctParam(variableName))
    16. break;
    17. }
    18. return ProStringList();
    19. }


    QMakeEvaluator::m_functionDefs :存放自定义的replace函数和test函数。
    QStack m_locationStack:与m_valuemapStack同步使用,用于存放当前解析位置,方便进入子作用域退出后找到解析位置。
    statics.functions:存放内置的test函数。
    statics.expands:存放内置的replace函数。

    m_functionDefs.testFunctions类型是QHash,是函数名与函数定义的键值对。ProFunctionDef中也有一个ProFile数据成员对象,用于存放函数定义的Tokens内容,在解析函数的时候会将这个Profile取出来进行解析。

    脚本中调用内建函数cache()函数时qmake中的调用堆栈

    脚本中调用cache函数的调用堆栈
    脚本中调用自定义的replace函数的调用堆栈

      qmake访问replace函数和test函数逻辑。

    1. QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateExpandFunction( const ProKey &func, const ushort *&tokPtr, ProStringList *ret) 访问replace函数
    2. {
    3. auto isbuildinE = statics.expands.constFind(func);
    4. if(isbuildinE) evaluateBuiltinExpand(*adef, func, args, *ret); //expandVariableReferences(tokPtr, 5, &args, true);
    5. QHash<ProKey, ProFunctionDef>::ConstIterator it =m_functionDefs.replaceFunctions.constFind(func);
    6. if(it) evaluateFunction(*it, args, ret); //prepareFunctionArgs(tokPtr, &args);
    7. }
    8. QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateConditionalFunction(const ProKey &func, const ushort *&tokPtr) 访问test函数
    9. {
    10. auto isbuildinE = statics.functions.constFind(func);
    11. if(isbuildinT) evaluateBuiltinConditional(*adef, func, args);
    12. QHash<ProKey, ProFunctionDef>::ConstIterator it =m_functionDefs.testFunctions.constFind(func);
    13. if(it) evaluateBoolFunction(*it, args, func);
    14. }

    生成makefile

    qmake 默认生成三个makefile相关的文件BuildsMetaMakefileGenerator::init()函数中会创建debug和release的MakefileGenerator对象,分别用于生成Makefile.Debug、Makefile.Release文件,BuildsMetaMakefileGenerator对象本身则生成Makefile文件。
    windows下生成Makefile.Debug主要内容在writeStandardParts函数中:

    1. //E:\workspace\QtWork\qmake\generators\win32\winmakefile.cpp
    2. void Win32MakefileGenerator::writeStandardParts(QTextStream &t)
    3. {
    4. t << "####### Compiler, tools and options\n\n";
    5. ......
    6. t << "####### Output directory\n\n";
    7. ......
    8. t << "####### Files\n\n";
    9. ......
    10. t << "####### Build rules\n\n";
    11. writeBuildRulesPart(t);
    12. writeRcFilePart(t);
    13. writeMakeQmake(t);
    14. .......
    15. writeCleanParts(t);
    16. writeExtraTargets(t);
    17. writeExtraCompilerTargets(t);
    18. }

    下面是生成Makefile中,moc调用规则时的堆栈。 

    qmake组装moc_*.cpp生成规则调用堆栈


    参考:如何通过pro文件向moc传入参数--------qmake组装makefile中的moc_*.cpp生成规则

    编译使用到的宏开关

    QT_CONFIG(process)
    PROEVALUATOR_FULL 
    QT_BOOTSTRAPPED 
    QT_BUILD_QMAKE 
    QT_NO_FOREACH 
    PARSER_DEBUG

    qmake 与 配置文件_丘上人的博客-CSDN博客

  • 相关阅读:
    学习SpringSecurity这一篇就够了
    【Azure 环境】把OpenSSL生产的自签名证书导入到Azure Key Vault Certificate中报错
    5分钟学会 gRPC
    ChatGPT私有数据结合有什么效果?它难吗?
    实践分析丨AscendCL应用编译&运行案例
    Julia数组详解
    罗德里格斯公式
    使用python脚本的时间盲注完整步骤
    解决linux下mysql-server在root账户下无需密码就能登陆的问题
    行人属性识别二:添加新网络训练和自定义数据集训练
  • 原文地址:https://blog.csdn.net/qiushangren/article/details/127137995