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 的改进 …...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...
CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...
Monorepo架构: Nx Cloud 扩展能力与缓存加速
借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一:HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二:Floyd 快慢指针法(…...
