Qt:愚蠢的qmake
博主参与了一个使用qmake
构建的项目,包含几百个源文件,最近遇到一个恼人的问题:有时仅仅修改了一个.cpp
文件,构建项目时就有可能触发全编译。但是编译时又会命中ccache
的缓存,这说明源代码实际上内容并没有发生变化。即使命中了ccache
缓存,几百个源文件编译下来还是要耗一小会儿时间的,博主对此不能熟视无睹。
本文中使用了一个demo项目stupid_qmake
来复现和分析该问题,其结构非常简单:
stupid_qmake/
├── main.cpp
├── stupid_qmake.pro
└── utility├── foo.cpp└── foo.h
main.cpp
文件内容如下,调用了std::swap
以及标准输出流:
#include <iostream>
#include <utility>int main() {int a = 1;int b = 2;std::swap(a, b);std::cout << a << " " << b << std::endl;return 0;
}
正常来说,如果我们修改了一个.h
文件,那么所有依赖这个.h
文件的.cpp
文件都需要重新编译,无论是直接include还是间接include;而修改一个.cpp
文件,则重新编译这个.cpp
就足够了。
这些重新编译的触发依赖于构建系统,以下面的Makefile
为例:
CXX = g++
CXXFLAGS = -Wall -g
TARGET = my_programSRCS = main.cpp func.cpp
OBJS = main.o func.oall: $(TARGET)$(TARGET): $(OBJS)$(CXX) $(CXXFLAGS) -o $@ $^main.o: main.cpp func.h$(CXX) $(CXXFLAGS) -c main.cpp -o main.ofunc.o: func.cpp func.h$(CXX) $(CXXFLAGS) -c func.cpp -o func.oclean:rm -f $(OBJS) $(TARGET).PHONY: all clean
不算伪目标,共有3个target:my_program
、main.o
和func.o
。target和target之间的依赖,以及target对源文件的依赖,如下图所示:
在执行make
时,make
不会也不可能真的去检查源文件内容是否发生了变化,而是会根据源文件和target的最后修改时间(mtime
)以及target之间的依赖关系,来决定哪些target需要重新生成:如果源文件的mtime
比target的mtime
大,说明源文件有更新,这个target需要重新生成,同时所有依赖这个target的其他target也需要重新生成。
在我们的demo项目中,从main.cpp
的内容来看,修改foo.h
/foo.cpp
不应当导致main.cpp
重新编译,但实际情况并非如此:每当使用qtcreator
编辑foo.cpp
后,总会触发main.cpp
的重新编译。
这个问题分析起来的入手点就是看看qmake
到底为我们生成了一个怎样的Makefile
—— 如果你在命令行中编译过qmake
项目,你应该知道,运行qmake
命令时会在构建目录中生成一个Makefile
文件,然后再运行make
命令才会正式开始项目的编译。
stupid_qmake.pro
文件生成的Makefile
中,main.o
和foo.o
两个target的生成规则如下:
main.o: ../main.cpp ../utility$(CXX) -c $(CXXFLAGS) $(INCPATH) -o main.o ../main.cppfoo.o: ../utility/foo.cpp ../utility/foo.h$(CXX) -c $(CXXFLAGS) $(INCPATH) -o foo.o ../utility/foo.cpp
foo.o
的生成规则没什么问题,main.o
的生成规则看起来有点奇怪:在依赖项中竟然有一个utility
目录。它为什么会在依赖项中呢?联想到main.cpp
源文件中依赖了utility
头文件,我们可以猜测,qmake
在生成依赖规则时,utility
目录被错误地视为了utility
头文件被添加到了main.cpp
的依赖项中。为了验证这个猜测,我们把main.cpp
中对utility
头文件的依赖去掉,然后重新运行qmake
,main.cpp
的生成规则就变成了:
main.o: ../main.cpp $(CXX) -c $(CXXFLAGS) $(INCPATH) -o main.o ../main.cpp
没有那条对utility
目录的依赖了,所以我们的推测是正确的,是qmake
混淆了utility
目录和utility
头文件。
依赖项列表中有一项是目录会产生什么后果呢?make
似乎并不介意这件事,仍然会机械地扫描依赖项的mtime
,以此决定哪些target需要重新生成。所以现在需要探讨的问题是:目录的mtime
在什么情况下会更新?关于这个问题,我在另外一篇博客Linux:使用vim编辑文件为什么会影响目录的mtime中分析过,在那篇博客中我是以vim
为例来分析的,实际上qtcreator
也有相同的效果:当你使用qtcreator
编辑了某个目录下的文件后,这个目录的mtime
就会更新。所以说,在我们的demo项目中,如果你编辑了utility
目录下的文件,utility
目录的mtime
就会更新,进而引起main.cpp
的重新编译。
回到博文开头提到的那个项目,里面恰好有一个utility
目录,而且这个目录下的源文件的修改也比较频繁。那么现在我们可以还原出整个问题的全貌:C++
标准库中的utility
头文件是一个被广泛包含的头文件,整个项目中的大部分源文件都对它有直接或者间接的依赖。而qmake
错误地将项目中一个名为utility
的目录当成了utility
头文件这件事,就会导致utility
目录被添加到了大部分.o
文件的依赖项中。一旦我们编辑了utility
目录下的文件,utility
目录的mtime
就会被更新,这将会导致大部分源文件重新编译。
问题修改起来也简单:在将utility
目录重命名为util后,恼人的问题就消失了。
(按理说,遇到这种情况时,qmake
应当优先匹配系统目录下的头文件,有时间了研究一下qmake
的源码看看它为什么没有这么做)
相关文章:
Qt:愚蠢的qmake
博主参与了一个使用qmake构建的项目,包含几百个源文件,最近遇到一个恼人的问题:有时仅仅修改了一个.cpp文件,构建项目时就有可能触发全编译。但是编译时又会命中ccache的缓存,这说明源代码实际上内容并没有发生变化。即…...
Apache Dubbo:分布式服务框架的深度解析
文章目录 引言官网链接Dubbo 原理架构概览通信协议负载均衡 基础使用1. 引入依赖2. 配置服务提供者3. 配置服务消费者4. 配置注册中心 高级使用1. 集群容错2. 泛化引用3. 异步调用 优缺点优点缺点 结论 引言 Apache Dubbo 是一个高性能、轻量级的开源 Java RPC 框架。它提供了…...

【前端学习】CSS三大特性
CSS三大特性 CSS的三大特性是为了化简代码、定位问题并且解决问题 继承性 继承性特点: 子级默认继承父级的文字控制属性。注意:如果标签自己有样式则生效自己的样式,不继承。 <!DOCTYPE html> <html lang"en"><…...

了解网络是如何运作
“Web 的工作原理”提供了一个简化的视图,用于了解在计算机或手机上的 Web 浏览器中查看网页时发生的情况。 这个理论对于短期内编写 Web 代码来说并不是必需的,但不久之后,你就会真正开始从理解后台发生的事情中受益。 客户端和服务器 连接到 Internet 的计算机称为客户端和…...

传输层协议——TCP
TCP协议 TCP全称为“传输控制协议”,要对数据的传输进行一个详细的控制。 特点 面向连接的可靠性字节流 TCP的协议段格式 源/目的端口:表示数据从哪个进程来,到哪个进程4位首部长度:表示该TCP头部有多少字节(注意它…...

C++相关概念和易错语法(23)(set、仿函数的应用、pair、multiset)
1.set和map存在的意义 (1)set和map的底层都是二叉搜索树,可以达到快速排序(当我们按照迭代器的顺序来遍历set和map,其实是按照中序来遍历的,是排过序的)、去重、搜索的目的。 (2&a…...

netty入门-3 EventLoop和EventLoopGroup,简单的服务器实现
文章目录 EventLoop和EventLoopGroup服务器与客户端基本使用增加非NIO工人NioEventLoop 处理普通任务与定时任务 结语 EventLoop和EventLoopGroup 二者大概是什么这里不再赘述,前一篇已简述过。 不理解也没关系。 下面会简单使用,看了就能明白是什么 这…...

通信原理-思科实验五:家庭终端以太网接入Internet实验
实验五 家庭终端以太网接入Internet实验 一实验内容 二实验目的 三实验原理 四实验步骤 1.按照上图选择对应的设备,并连接起来 为路由器R0两个端口配置IP 为路由器R1端口配置IP 为路由器设备增加RIP,配置接入互联网的IP的动态路由项 5.为路由器R1配置静…...
【Vue】vue概述
1、简介 Vue.js(简称Vue)是一款用于构建用户界面的渐进式JavaScript框架。由前Google高级软件工程师尤雨溪(Evan You)于2014年创建,是一个独立且社区驱动的开源项目。Vue.js基于标准的HTML、CSS和JavaScriptÿ…...
Docker use experience
#docker command docker load -i <镜像文件.tar> docker run -it -d --name 容器名 -p 宿主机端口:容器端口 -v 宿主机文件存储位置:容器内文位置 镜像名:Tag /bin/bash docker commit -m"提交的描述信息" -a"作者" 容器ID 要…...

Android平台RTSP|RTMP直播播放器技术接入说明
技术背景 大牛直播SDK自2015年发布RTSP、RTMP直播播放模块,迭代从未停止,SmartPlayer功能强大、性能强劲、高稳定、超低延迟、超低资源占用。无需赘述,全自研内核,行业内一致认可的跨平台RTSP、RTMP直播播放器。本文以Android平台…...

数据结构——栈(顺序结构)
一、栈的定义 栈是一种数据结构,它是一种只能在一端进行插入和删除操作的特殊线性表。这一端被称为栈顶,另一端被称为栈底。栈按照后进先出(LIFO)的原则进行操作(类似与手枪装弹后射出子弹的顺序)。在计算…...
速盾:cdn能防御ddos吗?
CDN(内容分发网络)是一种广泛应用于互联网中的技术,它通过将内容分发到全球各地的服务器上,以提高用户在访问网站时的加载速度和稳定性。然而,CDN是否能够有效防御DDoS(分布式拒绝服务)攻击是一…...

分享 2 个 .NET EF 6 只更新某些字段的方法
前言 EF 更新数据时,通常情况下,是更新全部字段的,但实际业务中,更新全部字段的情况其实很少,一般都是修改其中某些字段,所以为了实现这个目标,很多程序员通常会这样作: 先从数据库…...

vs code解决报错 (c/c++的配置环境 远端机器为Linux ubuntu)
参考链接:https://blog.csdn.net/fightfightfight/article/details/82857397 https://blog.csdn.net/m0_38055352/article/details/105375367 可以按照步骤确定那一步不对,如果一个可以就不用往下看了 目录 一、检查一下文件扩展名 二、安装扩展包并…...

08 字符串和字节串
使用单引号、双引号、三单引号、三双引号作为定界符(delimiter)来表示字符串,并且不同的定界符之间可以相互嵌套。 很多内置函数和标准库对象也都支持对字符串的操作。 x hello world y Python is a great language z Tom said, "Le…...
vue使用mavonEditor(流程图、时序图、甘特图实现)
mavonEditor 安装mavonEditor $ npm install mavon-editor --save使用 // 全局注册import Vue from vueimport mavonEditor from mavon-editorimport mavon-editor/dist/css/index.css// useVue.use(mavonEditor)new Vue({el: #main,data() {return { value: }}})//局部使用…...

Java实现短信验证码服务
1.首先这里使用的是阿里云的短信服务。 package com.wzy.util;; import cn.hutool.captcha.generator.RandomGenerator; import com.aliyun.dysmsapi20170525.Client; import com.wzy.entity.Ali; import org.springframework.stereotype.Component;/*** Author: 顾安* Descri…...
python中的线程
线程 线程概念 线程 在一个进程的内部, 要同时干多件事, 就需要同时运行多个"子任务", 我们把进程内的这些"子任务"叫做线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中, 是进程的实际运作单位。一条线程指的是进程中一个单一顺序的控制流…...
hcip学习 多实例生成树,VRRP工作原理
一、STP 和 RSTP 解决了什么问题 1、STP:解决了在冗余的二层网络中所出现的环路问题 2、RSTP:在 STP 的基础上,解决了 STP 收敛速度慢的问题,引入了一些 STP 保护机制,使其网络更加稳定 二、MSTP 针对 RSTP 的改进 …...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...