<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>DevOps on kenji.blog</title><link>http://kenji.blog/zh-cn/categories/devops/</link><description>Recent content in DevOps on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 09:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/zh-cn/categories/devops/index.xml" rel="self" type="application/rss+xml"/><item><title>【Git命令】rebase与merge的区别，以及在实际工作中的正确使用场景</title><link>http://kenji.blog/zh-cn/p/git-rebase-vs-merge-practical-guide/</link><pubDate>Sun, 13 Sep 2026 09:00:00 +0900</pubDate><guid>http://kenji.blog/zh-cn/p/git-rebase-vs-merge-practical-guide/</guid><description>&lt;img src="http://kenji.blog/p/git-rebase-vs-merge-practical-guide/img/eyecatch.jpg" alt="Featured image of post 【Git命令】rebase与merge的区别，以及在实际工作中的正确使用场景" />&lt;h1 id="1-前言为什么merge还是rebase是一个永恒的难题">1. 前言：为什么“merge还是rebase”是一个永恒的难题
&lt;/h1>&lt;p>Git是现代软件开发中不可或缺的版本控制系统。当多名开发者同时修改代码库时，Git强大的分支模型发挥了巨大的作用。然而，在团队开发中“应该使用 &lt;code>merge&lt;/code> 还是 &lt;code>rebase&lt;/code>”的争论，是无论初学者还是资深开发者都经常面临的话题。&lt;/p>
&lt;p>本文将从Git的内部结构（即DAG，有向无环图）以及提交哈希的数学性质出发，深入剖析 &lt;code>git merge&lt;/code> 和 &lt;code>git rebase&lt;/code> 的机制差异。并且，结合具体的实际工作流，彻底讲解在实际工作中应该如何区分使用它们。不仅仅是介绍命令，通过理解Git在后台进行怎样的计算，你将不再恐惧冲突，并能够构建出清晰且可追踪的历史记录。&lt;/p>
&lt;hr>
&lt;h1 id="2-git的内部结构提交哈希与对象模型">2. Git的内部结构：提交哈希与对象模型
&lt;/h1>&lt;p>为了理解Git是如何整合历史记录的，首先需要知道Git是如何保存数据的。Git并不是仅仅保存文件更改的差异（补丁），而是保存某个时间点整个文件系统的快照。&lt;/p>
&lt;h2 id="21-提交哈希的密码学性质">2.1 提交哈希的密码学性质
&lt;/h2>&lt;p>Git的每个提交（Commit）都通过SHA-1（安全哈希算法1）哈希函数计算出一个40位的十六进制字符串来唯一标识。一个提交对象由以下元素构成：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>指向Tree对象的指针&lt;/strong>: 此时的目录结构和文件（Blob）的快照&lt;/li>
&lt;li>&lt;strong>指向父提交的指针&lt;/strong>: 一个或多个父提交的哈希值（首次提交没有父提交，合并提交有两个或以上的父提交）&lt;/li>
&lt;li>&lt;strong>作者信息（Author）&lt;/strong>: 编写代码的人和时间&lt;/li>
&lt;li>&lt;strong>提交者信息（Committer）&lt;/strong>: 创建并应用该提交的人和时间&lt;/li>
&lt;li>&lt;strong>提交信息&lt;/strong>: 解释更改意图的文本&lt;/li>
&lt;/ol>
&lt;p>用数学语言表达，对于提交对象 $C$，其哈希值 $H(C)$ 定义如下：&lt;/p>
$$
H(C) = \text{SHA-1}( \text{tree} \parallel \text{parent} \parallel \text{author} \parallel \text{committer} \parallel \text{message} )
$$&lt;p>这里，$\parallel$ 表示数据的拼接。根据哈希函数的特性，即使提交信息只改变了一个字符，或者父提交不同，也会生成完全不同的哈希值。也就是说，&lt;strong>提交是不可变（Immutable）的&lt;/strong>。后文提到的 &lt;code>rebase&lt;/code> 被称为“重写历史”，实际上是因为“它创建了内容相似但哈希值不同的全新提交”。&lt;/p>
&lt;p>哈希空间的大小为 $2^{160}$，发生碰撞（不同的提交具有相同的哈希值）的概率 $P$，可以使用生日攻击（Birthday Paradox）的理论进行如下近似（$n$ 为提交数）：&lt;/p>
$$
P(\text{collision}) \approx 1 - \exp\left(-\frac{n^2}{2 \times 2^{160}}\right)
$$&lt;p>这个概率极低，在实际应用中Git的提交哈希几乎不可能发生碰撞。&lt;/p>
&lt;hr>
&lt;h1 id="3-图论与daggit历史的数学模型">3. 图论与DAG：Git历史的数学模型
&lt;/h1>&lt;p>Git的提交历史可以被建模为图论中的“有向无环图（Directed Acyclic Graph, DAG）”。&lt;/p>
&lt;h2 id="31-什么是dag有向无环图">3.1 什么是DAG（有向无环图）
&lt;/h2>&lt;p>在图 $G = (V, E)$ 中，$V$ 是提交的集合（顶点），$E$ 是表示提交之间父子关系的有向边的集合。在Git中，边的方向是“从子提交指向父提交”。这是因为新的提交持有着指向过去提交的指针。&lt;/p>
&lt;pre class="mermaid">
graph BT
A[&amp;#34;提交 A (初始)&amp;#34;]
B[&amp;#34;提交 B&amp;#34;]
C[&amp;#34;提交 C (Main)&amp;#34;]
D[&amp;#34;提交 D (Feature)&amp;#34;]
E[&amp;#34;提交 E (Merge)&amp;#34;]
B --&amp;gt; A
C --&amp;gt; B
D --&amp;gt; B
E --&amp;gt; C
E --&amp;gt; D
&lt;/pre>
&lt;p>DAG的最大特征是“不存在环（循环）”。正因如此，追溯提交历史的算法不会陷入死循环，并且必定能到达终点（初始提交）。&lt;/p>
&lt;h2 id="32-拓扑排序与历史顺序">3.2 拓扑排序与历史顺序
&lt;/h2>&lt;p>当使用 &lt;code>git log&lt;/code> 等命令显示历史记录时，DAG会通过拓扑排序（Topological Sort）算法被排序为一个一维列表。对于DAG中任意有向边 $u \to v$（$u$ 是 $v$ 的子节点），在列表中 $u$ 会被排列在 $v$ 的前面。&lt;/p>
&lt;hr>
&lt;h1 id="4-git-merge-的机制与种类">4. git merge 的机制与种类
&lt;/h1>&lt;p>整合分支更改最基本的命令是 &lt;code>git merge&lt;/code>。但是，根据当前的状态，Git会自动选择不同的合并策略。&lt;/p>
&lt;h2 id="41-fast-forward-合并--ff">4.1 Fast-Forward 合并（&amp;ndash;ff）
&lt;/h2>&lt;p>当目标分支（例：&lt;code>main&lt;/code>）是来源分支（例：&lt;code>feature&lt;/code>）的直接祖先时，Git会执行“Fast-Forward（快进）”合并。这不会创建新的提交，只是将分支的指针向前移动而已。&lt;/p>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;A&amp;#34;
commit id: &amp;#34;B&amp;#34;
branch feature
checkout feature
commit id: &amp;#34;C&amp;#34;
commit id: &amp;#34;D&amp;#34;
checkout main
merge feature
&lt;/pre>
&lt;p>Fast-Forward合并能保持历史记录为一条直线，但缺点是会丢失“哪些提交是作为一个功能开发（feature）组合在一起的”这一上下文信息。&lt;/p>
&lt;h2 id="42-non-fast-forward-合并--no-ff">4.2 Non-Fast-Forward 合并（&amp;ndash;no-ff）
&lt;/h2>&lt;p>如果显式指定 &lt;code>git merge --no-ff&lt;/code>，即使处于可以Fast-Forward的情况下，也必定会创建一个新的“合并提交（Merge Commit）”。合并提交是具有两个父提交的特殊提交。&lt;/p>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;A&amp;#34;
commit id: &amp;#34;B&amp;#34;
branch feature
checkout feature
commit id: &amp;#34;C&amp;#34;
commit id: &amp;#34;D&amp;#34;
checkout main
commit id: &amp;#34;Main Work 1&amp;#34;
merge feature type: NORMAL
&lt;/pre>
&lt;p>这种方法的优点是，功能分支的存在和历史会清晰地保留在DAG上。当发生问题时，可以通过执行 &lt;code>git revert -m 1 &amp;lt;合并提交的哈希值&amp;gt;&lt;/code>，一次性安全地撤销（revert）整个功能。&lt;/p>
&lt;h2 id="43-三方合并3-way-merge算法">4.3 三方合并（3-Way Merge）算法
&lt;/h2>&lt;p>如果目标分支和来源分支都各自拥有独有的提交，Git就会执行三方合并。此时，Git会遍历DAG，找出两个分支的“最近公共祖先（Lowest Common Ancestor, LCA）”。&lt;/p>
&lt;p>寻找LCA的算法的时间复杂度 $T_{\text{LCA}}$ 相对于顶点数 $|V|$ 和边数 $|E|$ 是线性时间：&lt;/p>
$$
T_{\text{LCA}} = \mathcal{O}(|V| + |E|)
$$&lt;p>Git会将“LCA的状态”、“当前分支的状态”和“对方分支的状态”这三者进行比较，如果没有冲突，就会自动生成一个合并提交。&lt;/p>
&lt;hr>
&lt;h1 id="5-git-rebase-的机制与历史的重构">5. git rebase 的机制与历史的重构
&lt;/h1>&lt;p>&lt;code>git merge&lt;/code> 是“整合”历史记录，而 &lt;code>git rebase&lt;/code> 则是“重构（变基）”历史记录。&lt;/p>
&lt;h2 id="51-rebase背后的运行机制">5.1 Rebase背后的运行机制
&lt;/h2>&lt;p>将 &lt;code>feature&lt;/code> 分支变基到 &lt;code>main&lt;/code> 分支（&lt;code>git rebase main&lt;/code>）时的内部动作如下：&lt;/p>
&lt;ol>
&lt;li>找到 &lt;code>feature&lt;/code> 分支和 &lt;code>main&lt;/code> 分支的最近公共祖先（LCA）。&lt;/li>
&lt;li>将从 LCA 到 &lt;code>feature&lt;/code> 分支最新提交之间的差异保存到临时区域。&lt;/li>
&lt;li>将 &lt;code>feature&lt;/code> 分支的指针移动到 &lt;code>main&lt;/code> 分支的最新提交。&lt;/li>
&lt;li>将保存的差异按照顺序，逐个应用（Cherry-Pick）到新的基底（&lt;code>main&lt;/code>的最新提交）上，并生成新的提交。&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;提交 A&amp;#34;] --&amp;gt; B[&amp;#34;提交 B&amp;#34;]
B --&amp;gt; C[&amp;#34;提交 C (Main)&amp;#34;]
B --&amp;gt; D[&amp;#34;提交 D (旧 Feature)&amp;#34;]
D -.-&amp;gt; E[&amp;#34;提交 D&amp;#39; (新 Feature)&amp;#34;]
C --&amp;gt; E
style D stroke-dasharray: 5 5, fill: #f9f9f9, color: #999
&lt;/pre>
&lt;p>这里的关键是，通过变基生成的新提交 $D'$ 与原提交 $D$ &lt;strong>因为父提交不同，所以拥有完全不同的哈希值&lt;/strong>（参考前文的哈希函数定义 $H(C)$）。&lt;/p>
&lt;h2 id="52-交互式变基interactive-rebase">5.2 交互式变基（Interactive Rebase）
&lt;/h2>&lt;p>使用 &lt;code>git rebase -i&lt;/code>（或 &lt;code>--interactive&lt;/code>），可以随心所欲地操作提交历史。这是用于整理本地历史的最强工具。&lt;/p>
&lt;ul>
&lt;li>&lt;code>pick&lt;/code> : 原样保留该提交&lt;/li>
&lt;li>&lt;code>reword&lt;/code> : 仅修改提交信息&lt;/li>
&lt;li>&lt;code>edit&lt;/code> : 暂停以便修改提交内容&lt;/li>
&lt;li>&lt;code>squash&lt;/code> : 将该提交融合到前一个提交中，并合并提交信息&lt;/li>
&lt;li>&lt;code>fixup&lt;/code> : 与 &lt;code>squash&lt;/code> 相同，但会丢弃该提交的提交信息&lt;/li>
&lt;li>&lt;code>drop&lt;/code> : 完全删除该提交&lt;/li>
&lt;/ul>
&lt;p>从数学角度来看，如果某个分支有 $N$ 个提交，通过重新排列变基顺序可以生成的线性历史的变化（排列组合） $P$ 如下：&lt;/p>
$$
P = N!
$$&lt;p>Git赋予了开发者 $N!$ 种自由，使其能够将历史记录保持在充满逻辑感和美感的状态。&lt;/p>
&lt;hr>
&lt;h1 id="6-变基的黄金法则the-golden-rule-of-rebase">6. 变基的黄金法则（The Golden Rule of Rebase）
&lt;/h1>&lt;p>&lt;code>rebase&lt;/code> 虽然非常强大，但有一个绝对的规则。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>“绝对不要对已公开的公共历史进行变基”&lt;/strong>
&lt;em>(Never rebase public history)&lt;/em>&lt;/p>
&lt;/blockquote>
&lt;h2 id="61-为什么不能对公共历史进行变基">6.1 为什么不能对公共历史进行变基？
&lt;/h2>&lt;p>Git是分布式的。你推送到 &lt;code>origin/main&lt;/code> 的提交，已经被克隆（复制）到了其他开发者的本地仓库中。如果你对已经推送的提交进行变基并重写了历史，然后使用 &lt;code>git push --force&lt;/code> 强制覆盖，会发生什么呢？&lt;/p>
&lt;p>其他开发者本地的DAG和远程的DAG会在根本上产生分歧。当其他开发者执行 &lt;code>git pull&lt;/code> 时，Git会强行尝试合并拥有不同历史的提交群，从而产生大量的冲突和重复提交（内容相同但哈希值不同的提交），导致仓库陷入混乱。&lt;/p>
&lt;p>变基的铁则是：&lt;strong>只能对“尚未与任何人共享的、本地的分支”进行操作&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h1 id="7-解决冲突与-git-rebase---continue">7. 解决冲突与 git rebase &amp;ndash;continue
&lt;/h1>&lt;p>当多人修改了同一个文件的同一个地方时，就会产生冲突。&lt;code>merge&lt;/code> 和 &lt;code>rebase&lt;/code> 解决冲突的过程是不同的。&lt;/p>
&lt;h2 id="71-merge中的冲突解决">7.1 Merge中的冲突解决
&lt;/h2>&lt;p>在使用 &lt;code>git merge&lt;/code> 时，冲突的解决&lt;strong>只会发生一次&lt;/strong>。在创建最终的合并提交之前，一次性修复所有的冲突处。&lt;/p>
&lt;h2 id="72-rebase中的冲突解决">7.2 Rebase中的冲突解决
&lt;/h2>&lt;p>在使用 &lt;code>git rebase&lt;/code> 时，由于其逐个重新应用提交的性质，&lt;strong>在每个提交处都有可能发生冲突&lt;/strong>。&lt;/p>
&lt;p>在变基过程中如果发生冲突，Git会暂停处理。解决的流程如下：&lt;/p>
&lt;ol>
&lt;li>打开编辑器或IDE（如VS Code），手动修复冲突标记（&lt;code>&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/code>、&lt;code>======&lt;/code> 和 &lt;code>&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code>）。&lt;/li>
&lt;li>将修复后的文件添加到暂存区：
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git add &amp;lt;修改后的文件&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>不要创建提交，直接继续变基流程：
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git rebase --continue
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>如果想中止变基并恢复到原来的状态，可以执行以下命令：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git rebase --abort
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>（※如果不需要解决冲突，想要直接跳过那个提交，可以使用 &lt;code>git rebase --skip&lt;/code>）&lt;/p>
&lt;hr>
&lt;h1 id="8-实际工作中的正确使用场景工作流实践">8. 实际工作中的正确使用场景（工作流实践）
&lt;/h1>&lt;p>那么，在实际的开发场景中，我们应该如何区分使用 &lt;code>merge&lt;/code> 和 &lt;code>rebase&lt;/code> 呢？这里介绍最标准且最安全的方法。&lt;/p>
&lt;h2 id="81-场景1整理本地开发历史使用-rebase">8.1 【场景1】整理本地开发历史（使用 Rebase）
&lt;/h2>&lt;p>假设在功能分支上开发时，积累了许多细碎的提交（比如“修复拼写错误”、“临时保存”等）。在发起拉取请求（PR）之前，为了将这些提交整理成有意义的单元，可以使用交互式变基。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 在 feature 分支下执行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git rebase -i HEAD~5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># （编辑器将打开，使用 squash 或 fixup 让历史变得整洁）&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过这种方式，可以制作出意图清晰、美观的提交历史，便于审查者理解。&lt;/p>
&lt;h2 id="82-场景2同步最新的-main-分支使用-rebase">8.2 【场景2】同步最新的 main 分支（使用 Rebase）
&lt;/h2>&lt;p>如果开发周期较长，并且不断有其他人的修改被合并到 &lt;code>main&lt;/code> 分支中，你自己的 &lt;code>feature&lt;/code> 分支就会变旧。在这种情况下，需要将 &lt;code>feature&lt;/code> 分支变基到最新的 &lt;code>main&lt;/code> 分支上以保持同步。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 获取 main 的最新信息&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git fetch origin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 将 feature 分支重新基底到最新的 main 上&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git rebase origin/main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样可以使历史记录保持为一条直线，防止后续合并时发生冲突。同时，也能避免生成多余的合并提交（例如 &amp;ldquo;Merge branch &amp;lsquo;main&amp;rsquo; into feature&amp;rdquo;）。&lt;/p>
&lt;h2 id="83-场景3整合已完成的功能使用-merge">8.3 【场景3】整合已完成的功能（使用 Merge）
&lt;/h2>&lt;p>当 &lt;code>feature&lt;/code> 分支的开发完成，终于进入要整合到 &lt;code>main&lt;/code> 分支的阶段。在这里我们使用 &lt;strong>&lt;code>git merge --no-ff&lt;/code>&lt;/strong>（这与在 GitHub 等的 Pull Request 中选择“Create a merge commit”是一样的）。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git checkout main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git merge --no-ff feature -m &lt;span class="s2">&amp;#34;Merge feature: 实现用户登录功能&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git push origin main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样一来，在 &lt;code>main&lt;/code> 分支的DAG上就会留下一个“这里合并了一个功能”的历史节点（合并提交）。日后查看历史记录时，也能更容易地以功能为单位追踪代码。&lt;/p>
&lt;hr>
&lt;h1 id="9-结论总结">9. 结论（总结）
&lt;/h1>&lt;p>在Git操作中，“一切都用Merge解决”或“一切都用Rebase保持直线”这样极端的做法，都有各自的优缺点。&lt;/p>
&lt;p>实际工作中的最佳实践是：**“本地的私有历史使用 rebase 进行整理，使其美观；而公共的整合历史使用 merge &amp;ndash;no-ff 来保留上下文”**的混合模式。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>本地（个人工作区）&lt;/strong>: 使用 &lt;code>rebase&lt;/code> 排除无用的提交，紧跟最新的主线，保持直线的历史记录。&lt;/li>
&lt;li>&lt;strong>全局（共享工作区）&lt;/strong>: 使用 &lt;code>merge --no-ff&lt;/code>，将功能分支的存在作为合并提交记录在DAG中，便于后续的追踪和撤销（revert）。&lt;/li>
&lt;/ul>
&lt;p>通过理解DAG结构和哈希函数机制等数学及架构层面的背景，Git命令就不再是死记硬背，而是升华为“有目的的历史记录设计”。在遵守变基黄金法则的前提下，根据具体情况选择最合适的命令，为整个团队构建一个易于阅读和维护的整洁的提交历史吧。&lt;/p></description></item><item><title>使用GitHub Actions构建C++项目的CI/CD流水线</title><link>http://kenji.blog/zh-cn/p/github-actions-cpp-cicd-pipeline/</link><pubDate>Sat, 12 Sep 2026 18:00:00 +0900</pubDate><guid>http://kenji.blog/zh-cn/p/github-actions-cpp-cicd-pipeline/</guid><description>&lt;img src="http://kenji.blog/p/github-actions-cpp-cicd-pipeline/img/eyecatch.jpg" alt="Featured image of post 使用GitHub Actions构建C++项目的CI/CD流水线" />&lt;h1 id="使用github-actions构建c项目的cicd流水线完全指南">使用GitHub Actions构建C++项目的CI/CD流水线：完全指南
&lt;/h1>&lt;p>在现代软件开发范式中，持续集成（Continuous Integration: CI）和持续交付/部署（Continuous Delivery/Deployment: CD）是敏捷开发过程和维持高质量软件不可或缺的要素。在众多编程语言中，相比于其他语言（例如Python、JavaScript、Go等），构建C++的CI/CD流水线伴随着独特的难度与复杂性。&lt;/p>
&lt;p>本文将极其详细地讲解如何利用GitHub Actions，从零开始为C++项目构建坚固且实用的CI/CD流水线。内容涵盖跨平台（Windows、Linux、macOS）的矩阵构建（Matrix Build）、整合CMake构建系统、使用CTest进行自动化测试、自动化静态与动态分析、覆盖率测量，以及通过GitHub Releases自动交付已编译二进制文件等所有实践技巧。&lt;/p>
&lt;h2 id="1-c项目中cicd的意义与特有问题">1. C++项目中CI/CD的意义与特有问题
&lt;/h2>&lt;p>在Web应用程序或使用脚本语言的开发中，通常在单个Docker容器上进行测试和构建就足够了。然而，C++作为一种本地编译语言，强烈依赖于运行环境的硬件架构和操作系统。&lt;/p>
&lt;p>在为C++项目引入CI/CD时，面临的主要挑战如下：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>平台的多样性&lt;/strong>：不同操作系统（如Windows、Linux、macOS）的API（Windows API、POSIX等）各不相同。即使在开发者的本地环境（例如macOS）中能正常运行，在Linux或Windows上出现编译错误也是家常便饭。&lt;/li>
&lt;li>&lt;strong>编译器的差异&lt;/strong>：Microsoft Visual C++ (MSVC)、GNU Compiler Collection (GCC)、Clang等主流编译器，在对C++标准（C++17、C++20、C++23）的实现程度、解释以及警告的严格程度上都存在差异。&lt;/li>
&lt;li>&lt;strong>构建时间&lt;/strong>：在大型C++项目中，构建花费数十分钟甚至数小时的情况并不罕见。在CI环境中，需要在有限的计算资源下，寻求缓存策略和并行化处理以高效地进行构建。&lt;/li>
&lt;li>&lt;strong>依赖管理&lt;/strong>：C++不存在像npm或pip那样绝对标准的包管理器。需要使用vcpkg、Conan或CMake的 &lt;code>FetchContent&lt;/code> 等工具，在CI环境上每次都能正确解析依赖库。&lt;/li>
&lt;li>&lt;strong>内存管理与未定义行为&lt;/strong>：由于伴随着指针操作和手动内存管理，除了单纯的逻辑测试外，还需要自动化检测内存泄漏和未定义行为（Undefined Behavior）。&lt;/li>
&lt;/ol>
&lt;p>为了解决这些问题，能够按需配置各种操作系统虚拟机，并通过代码定义复杂工作流（Configuration as Code）的GitHub Actions就成为了最佳解决方案。&lt;/p>
&lt;h2 id="2-cicd流水线架构概述">2. CI/CD流水线架构概述
&lt;/h2>&lt;p>让我们来可视化即将构建的CI/CD流水线全貌。以下的Mermaid时序图展示了从代码Push到发布（Release）的工作流。&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
participant Dev as &amp;#34;开发者&amp;#34;
participant Repo as &amp;#34;GitHub仓库&amp;#34;
participant Action as &amp;#34;GitHub Actions CI/CD&amp;#34;
participant Rel as &amp;#34;GitHub Releases&amp;#34;
Dev-&amp;gt;&amp;gt;Repo: &amp;#34;推送分支 / 开启PR&amp;#34;
Repo-&amp;gt;&amp;gt;Action: &amp;#34;触发CI工作流&amp;#34;
activate Action
Action-&amp;gt;&amp;gt;Action: &amp;#34;代码检查与静态分析 (Clang-Tidy)&amp;#34;
rect rgb(200, 220, 240)
note right of Action: &amp;#34;跨平台矩阵构建&amp;#34;
Action-&amp;gt;&amp;gt;Action: &amp;#34;在Ubuntu上构建 (GCC/Clang)&amp;#34;
Action-&amp;gt;&amp;gt;Action: &amp;#34;在Windows上构建 (MSVC)&amp;#34;
Action-&amp;gt;&amp;gt;Action: &amp;#34;在macOS上构建 (Apple Clang)&amp;#34;
end
Action-&amp;gt;&amp;gt;Action: &amp;#34;运行CTest (使用ASAN/UBSAN)&amp;#34;
Action-&amp;gt;&amp;gt;Action: &amp;#34;生成覆盖率报告&amp;#34;
alt &amp;#34;如果推送了标签 (例如 v1.0.0)&amp;#34;
Action-&amp;gt;&amp;gt;Action: &amp;#34;使用CPack打包二进制文件&amp;#34;
Action-&amp;gt;&amp;gt;Rel: &amp;#34;上传ZIP/Tarball到Release&amp;#34;
end
deactivate Action
Repo--&amp;gt;&amp;gt;Dev: &amp;#34;报告CI状态 (通过/失败)&amp;#34;
&lt;/pre>
&lt;p>在此架构中，Pull Request阶段将提供快速的反馈（静态分析、构建和测试），而在打上版本标签的时机，则会进行产物的打包和分发。&lt;/p>
&lt;h2 id="3-现代cmake的项目配置">3. 现代CMake的项目配置
&lt;/h2>&lt;p>优秀的CI流水线的基础是坚固的构建系统。我们将使用C++的事实标准：CMake。在这里，我们采用被称为“现代CMake（Modern CMake）”的面向目标（Target-oriented）方法。&lt;/p>
&lt;p>假设项目的目录结构如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">my_cpp_project/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── src/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── main.cpp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── calculator.cpp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── calculator.h
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── tests/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── test_calculator.cpp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>根目录 &lt;code>CMakeLists.txt&lt;/code> 的配置示例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.20&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyCppProject&lt;/span> &lt;span class="s">VERSION&lt;/span> &lt;span class="s">1.0.0&lt;/span> &lt;span class="s">LANGUAGES&lt;/span> &lt;span class="s">CXX&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># C++標準の設定
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD&lt;/span> &lt;span class="s">20&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD_REQUIRED&lt;/span> &lt;span class="s">ON&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_EXTENSIONS&lt;/span> &lt;span class="s">OFF&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c"># コンパイラ固有の拡張を無効化し移植性を高める
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># コンパイラ警告の厳格化
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">set_project_warnings&lt;/span> &lt;span class="s">target_name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MSVC&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">target_name&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">/W4&lt;/span> &lt;span class="s">/WX&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">else&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">target_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">target_name&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">-Wall&lt;/span> &lt;span class="s">-Wextra&lt;/span> &lt;span class="s">-Wpedantic&lt;/span> &lt;span class="s">-Werror&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">endif&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">endfunction&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ライブラリターゲットの作成
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_library&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CalculatorLib&lt;/span> &lt;span class="s">src/calculator.cpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_include_directories&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CalculatorLib&lt;/span> &lt;span class="s">PUBLIC&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_CURRENT_SOURCE_DIR&lt;/span>&lt;span class="o">}&lt;/span>&lt;span class="s">/src&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set_project_warnings&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CalculatorLib&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 実行ファイルターゲットの作成
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">add_executable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyApplication&lt;/span> &lt;span class="s">src/main.cpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyApplication&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">CalculatorLib&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set_project_warnings&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MyApplication&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># テストの有効化
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">enable_testing&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_subdirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">tests&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># インストールルールの定義 (CPack用)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">GNUInstallDirs&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">install&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">TARGETS&lt;/span> &lt;span class="s">MyApplication&lt;/span> &lt;span class="s">CalculatorLib&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">RUNTIME&lt;/span> &lt;span class="s">DESTINATION&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_INSTALL_BINDIR&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">LIBRARY&lt;/span> &lt;span class="s">DESTINATION&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_INSTALL_LIBDIR&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">ARCHIVE&lt;/span> &lt;span class="s">DESTINATION&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_INSTALL_LIBDIR&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># CPackによるパッケージング設定
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CPACK_PROJECT_NAME&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">PROJECT_NAME&lt;/span>&lt;span class="o">}&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CPACK_PROJECT_VERSION&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">PROJECT_VERSION&lt;/span>&lt;span class="o">}&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CPACK_GENERATOR&lt;/span> &lt;span class="s2">&amp;#34;ZIP;TGZ&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">WIN32&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CPACK_GENERATOR&lt;/span> &lt;span class="s2">&amp;#34;ZIP&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">endif&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CPack&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>要点：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>CMAKE_CXX_EXTENSIONS OFF&lt;/code>：防止依赖如GNU扩展等非标准功能，保证跨平台性。&lt;/li>
&lt;li>&lt;strong>严格警告 (&lt;code>-Werror&lt;/code> / &lt;code>/WX&lt;/code>)&lt;/strong>：在CI环境中将编译器警告视为错误，强制保持高质量的代码。&lt;/li>
&lt;li>&lt;strong>GNUInstallDirs&lt;/strong>：自动解析各个操作系统的标准安装路径（如 &lt;code>/usr/local/bin&lt;/code> 或 &lt;code>C:\Program Files&lt;/code>）。&lt;/li>
&lt;/ul>
&lt;h2 id="4-github-actions基础与矩阵策略">4. GitHub Actions基础与矩阵策略
&lt;/h2>&lt;p>GitHub Actions由 &lt;code>.github/workflows/&lt;/code> 目录下的YAML文件配置。
在C++项目中，最强大的功能就是“矩阵策略（Matrix Strategy）”。借此可以动态生成操作系统和编译器的组合，并并行执行。&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;触发工作流&amp;#34;] --&amp;gt; B[&amp;#34;评估矩阵作业&amp;#34;]
B --&amp;gt; C[&amp;#34;Ubuntu 22.04 (GCC 12)&amp;#34;]
B --&amp;gt; D[&amp;#34;Ubuntu 22.04 (Clang 15)&amp;#34;]
B --&amp;gt; E[&amp;#34;Windows Server 2022 (MSVC)&amp;#34;]
B --&amp;gt; F[&amp;#34;macOS 14 (Apple Clang)&amp;#34;]
&lt;/pre>
&lt;p>下面展示了作为矩阵构建基础的YAML作业（Job）定义：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Build &amp;amp; Test [${{ matrix.os }} - ${{ matrix.compiler }}]&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ matrix.os }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">strategy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fail-fast&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 1つのジョブが失敗しても他のOSのビルドを継続する&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matrix&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">include&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">os&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gcc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">c_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gcc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpp_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">g++&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">os&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clang&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">c_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clang&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpp_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clang++&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">os&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">windows-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">msvc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">c_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cl&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpp_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cl&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">os&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">macos-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apple-clang&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">c_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clang&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpp_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clang++&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>fail-fast: false&lt;/code> 非常重要。例如，如果不小心错误地使用了Linux特有的API，Ubuntu的构建将会失败，但此时我们也希望同时确认Windows的构建是否能够成功。&lt;/p>
&lt;h2 id="5-构建成本与利用阿姆达尔定律优化并行处理">5. 构建成本与利用阿姆达尔定律优化并行处理
&lt;/h2>&lt;p>在云环境中，CI/CD是在与时间赛跑，构建时间直接关系到开发者的等待时间以及运行成本。
在这里，对于构建时间的优化，我们试着用计算机科学中的“阿姆达尔定律（Amdahl&amp;rsquo;s Law）”来进行数学分析。&lt;/p>
&lt;p>阿姆达尔定律定义了，如果程序中可并行化的部分占比为 $P$，那么在使用 $N$ 个处理器时的理论最大加速比 $S(N)$ 如下：&lt;/p>
$$ S(N) = \frac{1}{(1 - P) + \frac{P}{N}} $$&lt;p>在C++的构建过程中，源代码中各个翻译单元（Translation Unit，即 &lt;code>.cpp&lt;/code> 文件）的编译是完全独立的，因此可以并行化。另一方面，CMake的配置（Configuration）以及最终的二进制链接阶段，基本上是串行执行的（不可并行化）。&lt;/p>
&lt;p>假设项目的整体构建时间中，80%为编译阶段（$P = 0.8$），20%为串行阶段（$1 - P = 0.2$）。
GitHub Actions的标准运行器（Linux）提供2个核心（线程）。因此当 $N = 2$ 时：&lt;/p>
$$ S(2) = \frac{1}{0.2 + \frac{0.8}{2}} = \frac{1}{0.2 + 0.4} = \frac{1}{0.6} \approx 1.67 $$&lt;p>仅仅使用2个核心，就能获得约1.67倍的速度提升。为了实现这一点，必须在CMake的构建命令中指定 &lt;code>--parallel&lt;/code> 选项。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Build Project&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cmake --build build --config Release --parallel 2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>此外，我们还要考虑成本计算。GitHub Actions的使用成本 $C_{total}$ 是各项作业执行时间 $T_i$ 与运行器单价 $R_i$ 乘积的总和。&lt;/p>
$$ C_{total} = \sum_{i=1}^{M} \left( T_i \times R_i \right) $$&lt;p>缩短构建时间不仅可以加快反馈循环，还能直接降低项目的运营成本（特别是在私有仓库的情况下）。如果需要进一步提速，引入 &lt;code>ccache&lt;/code> 来缓存编译结果是一种有效的方法。&lt;/p>
&lt;h2 id="6-自动化测试与sanitizers集成">6. 自动化测试与Sanitizers集成
&lt;/h2>&lt;p>在C++中，为了防患于未然，强烈建议在单元测试之外，引入能在运行时检测内存泄漏和未定义行为的“Sanitizer”。我们可以使用Google开发的AddressSanitizer (ASAN) 和 UndefinedBehaviorSanitizer (UBSAN)。&lt;/p>
&lt;p>在CMake中添加启用Sanitizer的选项：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ENABLE_SANITIZERS&lt;/span> &lt;span class="s2">&amp;#34;Enable ASAN and UBSAN&amp;#34;&lt;/span> &lt;span class="s">OFF&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ENABLE_SANITIZERS&lt;/span> &lt;span class="s">AND&lt;/span> &lt;span class="s">CMAKE_CXX_COMPILER_ID&lt;/span> &lt;span class="s">MATCHES&lt;/span> &lt;span class="s2">&amp;#34;GNU|Clang&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">add_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">-fsanitize=address,undefined&lt;/span> &lt;span class="s">-fno-omit-frame-pointer&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">add_link_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">-fsanitize=address,undefined&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">endif&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在CI流水线的Ubuntu作业中启用该选项并执行测试：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Configure CMake&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">CC&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ matrix.c_compiler }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">CXX&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ matrix.cpp_compiler }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> cmake -B build
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> -DCMAKE_BUILD_TYPE=Release
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> -DENABLE_SANITIZERS=${{ matrix.os == &amp;#39;ubuntu-latest&amp;#39; &amp;amp;&amp;amp; &amp;#39;ON&amp;#39; || &amp;#39;OFF&amp;#39; }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Run CTest&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">working-directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ASAN_OPTIONS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;detect_leaks=1:symbolize=1&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">UBSAN_OPTIONS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;print_stacktrace=1&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ctest --build-config Release --output-on-failure --parallel 2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>测试执行使用 &lt;code>ctest&lt;/code> 命令。通过指定 &lt;code>--output-on-failure&lt;/code>，仅在测试失败时将详细日志输出到CI中，从而防止日志过于庞大。&lt;/p>
&lt;h2 id="7-覆盖率测量">7. 覆盖率测量
&lt;/h2>&lt;p>可视化测试覆盖了多少代码在质量保证中至关重要。利用Linux环境（GCC），我们使用 &lt;code>gcov&lt;/code> 和 &lt;code>lcov&lt;/code> 来测量覆盖率。&lt;/p>
&lt;p>首先，在CMake中设置用于覆盖率测量的编译标志：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ENABLE_COVERAGE&lt;/span> &lt;span class="s2">&amp;#34;Enable coverage reporting&amp;#34;&lt;/span> &lt;span class="s">OFF&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ENABLE_COVERAGE&lt;/span> &lt;span class="s">AND&lt;/span> &lt;span class="s">CMAKE_CXX_COMPILER_ID&lt;/span> &lt;span class="s">STREQUAL&lt;/span> &lt;span class="s2">&amp;#34;GNU&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">add_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">--coverage&lt;/span> &lt;span class="s">-O0&lt;/span> &lt;span class="s">-g&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">add_link_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">--coverage&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">endif&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在GitHub Actions中定义一个独立作业来进行覆盖率测量：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">coverage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Test Coverage Analysis&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Install lcov&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y lcov&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Configure CMake for Coverage&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cmake -B build -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Build &amp;amp; Test&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> cmake --build build --parallel 2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> cd build &amp;amp;&amp;amp; ctest --output-on-failure&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Generate lcov Report&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">working-directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> lcov --capture --directory . --output-file coverage.info
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> lcov --remove coverage.info &amp;#39;/usr/*&amp;#39; &amp;#39;*_deps/*&amp;#39; &amp;#39;*tests/*&amp;#39; --output-file coverage.info
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> &lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Upload Coverage to Codecov&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">codecov/codecov-action@v3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">files&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build/coverage.info&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>使用 &lt;code>lcov --remove&lt;/code> 命令，将系统头文件、第三方库以及测试代码自身从覆盖率测量对象中排除。这样就能获得项目自身源代码的纯粹覆盖率。&lt;/p>
&lt;h2 id="8-通过github-releases自动交付二进制文件-cd">8. 通过GitHub Releases自动交付二进制文件 (CD)
&lt;/h2>&lt;p>构建CI/CD中的“CD”部分。当开发者在Git中打上版本标签（例如：&lt;code>v1.2.0&lt;/code>）并推送时，它会自动编译各个操作系统的可执行二进制文件，打包成ZIP或Tarball，并上传到GitHub Releases。&lt;/p>
&lt;p>这一步我们将利用CMake自带的打包工具 &lt;code>CPack&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Package Application (CPack)&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">startsWith(github.ref, &amp;#39;refs/tags/v&amp;#39;)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">working-directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cpack -C Release -V&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Upload Release Assets&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">startsWith(github.ref, &amp;#39;refs/tags/v&amp;#39;)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">softprops/action-gh-release@v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">files&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> build/*.tar.gz
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> build/*.zip
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> build/*.sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> build/*.exe&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">GITHUB_TOKEN&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.GITHUB_TOKEN }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过此配置，只需执行 &lt;code>git tag v1.0.0&lt;/code> 和 &lt;code>git push origin v1.0.0&lt;/code>，面向Windows用户的ZIP文件以及面向Linux/macOS用户的Tarball，就会在无需手动干预的情况下，自动发布到Release页面。这在向用户交付软件时是一项极为强大的功能。&lt;/p>
&lt;h2 id="9-完整的-workflow-yaml-文件">9. 完整的 Workflow YAML 文件
&lt;/h2>&lt;p>下面展示了整合了前文讲解的所有要素、坚固且实用的 &lt;code>.github/workflows/main.yml&lt;/code> 的完整代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt"> 10
&lt;/span>&lt;span class="lnt"> 11
&lt;/span>&lt;span class="lnt"> 12
&lt;/span>&lt;span class="lnt"> 13
&lt;/span>&lt;span class="lnt"> 14
&lt;/span>&lt;span class="lnt"> 15
&lt;/span>&lt;span class="lnt"> 16
&lt;/span>&lt;span class="lnt"> 17
&lt;/span>&lt;span class="lnt"> 18
&lt;/span>&lt;span class="lnt"> 19
&lt;/span>&lt;span class="lnt"> 20
&lt;/span>&lt;span class="lnt"> 21
&lt;/span>&lt;span class="lnt"> 22
&lt;/span>&lt;span class="lnt"> 23
&lt;/span>&lt;span class="lnt"> 24
&lt;/span>&lt;span class="lnt"> 25
&lt;/span>&lt;span class="lnt"> 26
&lt;/span>&lt;span class="lnt"> 27
&lt;/span>&lt;span class="lnt"> 28
&lt;/span>&lt;span class="lnt"> 29
&lt;/span>&lt;span class="lnt"> 30
&lt;/span>&lt;span class="lnt"> 31
&lt;/span>&lt;span class="lnt"> 32
&lt;/span>&lt;span class="lnt"> 33
&lt;/span>&lt;span class="lnt"> 34
&lt;/span>&lt;span class="lnt"> 35
&lt;/span>&lt;span class="lnt"> 36
&lt;/span>&lt;span class="lnt"> 37
&lt;/span>&lt;span class="lnt"> 38
&lt;/span>&lt;span class="lnt"> 39
&lt;/span>&lt;span class="lnt"> 40
&lt;/span>&lt;span class="lnt"> 41
&lt;/span>&lt;span class="lnt"> 42
&lt;/span>&lt;span class="lnt"> 43
&lt;/span>&lt;span class="lnt"> 44
&lt;/span>&lt;span class="lnt"> 45
&lt;/span>&lt;span class="lnt"> 46
&lt;/span>&lt;span class="lnt"> 47
&lt;/span>&lt;span class="lnt"> 48
&lt;/span>&lt;span class="lnt"> 49
&lt;/span>&lt;span class="lnt"> 50
&lt;/span>&lt;span class="lnt"> 51
&lt;/span>&lt;span class="lnt"> 52
&lt;/span>&lt;span class="lnt"> 53
&lt;/span>&lt;span class="lnt"> 54
&lt;/span>&lt;span class="lnt"> 55
&lt;/span>&lt;span class="lnt"> 56
&lt;/span>&lt;span class="lnt"> 57
&lt;/span>&lt;span class="lnt"> 58
&lt;/span>&lt;span class="lnt"> 59
&lt;/span>&lt;span class="lnt"> 60
&lt;/span>&lt;span class="lnt"> 61
&lt;/span>&lt;span class="lnt"> 62
&lt;/span>&lt;span class="lnt"> 63
&lt;/span>&lt;span class="lnt"> 64
&lt;/span>&lt;span class="lnt"> 65
&lt;/span>&lt;span class="lnt"> 66
&lt;/span>&lt;span class="lnt"> 67
&lt;/span>&lt;span class="lnt"> 68
&lt;/span>&lt;span class="lnt"> 69
&lt;/span>&lt;span class="lnt"> 70
&lt;/span>&lt;span class="lnt"> 71
&lt;/span>&lt;span class="lnt"> 72
&lt;/span>&lt;span class="lnt"> 73
&lt;/span>&lt;span class="lnt"> 74
&lt;/span>&lt;span class="lnt"> 75
&lt;/span>&lt;span class="lnt"> 76
&lt;/span>&lt;span class="lnt"> 77
&lt;/span>&lt;span class="lnt"> 78
&lt;/span>&lt;span class="lnt"> 79
&lt;/span>&lt;span class="lnt"> 80
&lt;/span>&lt;span class="lnt"> 81
&lt;/span>&lt;span class="lnt"> 82
&lt;/span>&lt;span class="lnt"> 83
&lt;/span>&lt;span class="lnt"> 84
&lt;/span>&lt;span class="lnt"> 85
&lt;/span>&lt;span class="lnt"> 86
&lt;/span>&lt;span class="lnt"> 87
&lt;/span>&lt;span class="lnt"> 88
&lt;/span>&lt;span class="lnt"> 89
&lt;/span>&lt;span class="lnt"> 90
&lt;/span>&lt;span class="lnt"> 91
&lt;/span>&lt;span class="lnt"> 92
&lt;/span>&lt;span class="lnt"> 93
&lt;/span>&lt;span class="lnt"> 94
&lt;/span>&lt;span class="lnt"> 95
&lt;/span>&lt;span class="lnt"> 96
&lt;/span>&lt;span class="lnt"> 97
&lt;/span>&lt;span class="lnt"> 98
&lt;/span>&lt;span class="lnt"> 99
&lt;/span>&lt;span class="lnt">100
&lt;/span>&lt;span class="lnt">101
&lt;/span>&lt;span class="lnt">102
&lt;/span>&lt;span class="lnt">103
&lt;/span>&lt;span class="lnt">104
&lt;/span>&lt;span class="lnt">105
&lt;/span>&lt;span class="lnt">106
&lt;/span>&lt;span class="lnt">107
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;C++ CI/CD Pipeline&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">push&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">branches&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;main&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;develop&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tags&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;v*.*.*&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pull_request&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">branches&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;main&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">BUILD_TYPE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Release&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build-and-test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Build [${{ matrix.os }} | ${{ matrix.compiler }}]&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ matrix.os }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">strategy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fail-fast&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matrix&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">include&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">os&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gcc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">c_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">gcc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpp_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">g++&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">os&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clang&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">c_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clang&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpp_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clang++&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">os&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">windows-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">msvc&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">c_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cl&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpp_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cl&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">os&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">macos-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apple-clang&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">c_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clang&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cpp_compiler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clang++&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Checkout Repository&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Configure CMake&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">CC&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ matrix.c_compiler }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">CXX&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ matrix.cpp_compiler }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> cmake -B build
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> -DENABLE_SANITIZERS=${{ matrix.os == &amp;#39;ubuntu-latest&amp;#39; &amp;amp;&amp;amp; &amp;#39;ON&amp;#39; || &amp;#39;OFF&amp;#39; }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Build Project&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cmake --build build --config ${{ env.BUILD_TYPE }} --parallel 2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Run Unit Tests (CTest)&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">working-directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ASAN_OPTIONS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;detect_leaks=1:symbolize=1&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">UBSAN_OPTIONS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;print_stacktrace=1&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ctest --build-config ${{ env.BUILD_TYPE }} --output-on-failure --parallel 2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Package with CPack&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">startsWith(github.ref, &amp;#39;refs/tags/v&amp;#39;)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">working-directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cpack -C ${{ env.BUILD_TYPE }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Create GitHub Release and Upload Assets&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">startsWith(github.ref, &amp;#39;refs/tags/v&amp;#39;)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">softprops/action-gh-release@v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">files&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> build/*.tar.gz
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> build/*.zip&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">GITHUB_TOKEN&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.GITHUB_TOKEN }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">coverage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Code Coverage Analysis&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">github.event_name == &amp;#39;pull_request&amp;#39; || github.ref == &amp;#39;refs/heads/main&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Checkout Repository&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Install lcov&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y lcov&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Configure CMake for Coverage&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cmake -B build -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Build Project&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cmake --build build --parallel 2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Run Tests&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">working-directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ctest --output-on-failure&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Generate lcov Report&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">working-directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> lcov --capture --directory . --output-file coverage.info
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> lcov --remove coverage.info &amp;#39;/usr/*&amp;#39; &amp;#39;*_deps/*&amp;#39; &amp;#39;*tests/*&amp;#39; --output-file coverage.info
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> &lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Upload Coverage to Codecov&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">codecov/codecov-action@v3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">files&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build/coverage.info&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fail_ci_if_error&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="10-迈向更高级的cicd静态分析与格式化">10. 迈向更高级的CI/CD（静态分析与格式化）
&lt;/h2>&lt;p>这里省略详细讲解，但在实际运维中，建议将更多的质量保证工具集成到流水线中：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>强制执行 Clang-Format&lt;/strong>：为减轻代码审查的负担，将 &lt;code>clang-format&lt;/code> 的代码风格检查集成到CI中，如果违反了格式化规则，则使流水线失败。&lt;/li>
&lt;li>&lt;strong>静态分析 (Clang-Tidy)&lt;/strong>：为了检测单靠编译器警告无法防备的潜在错误，或低效代码（如不必要的复制等），将 &lt;code>clang-tidy&lt;/code> 集成到CMake中，并在CI上运行。&lt;/li>
&lt;li>&lt;strong>利用 vcpkg / Conan 缓存&lt;/strong>：如果使用了大量第三方库，构建依赖关系会花费大量时间。利用GitHub Actions的 &lt;code>actions/cache&lt;/code>，通过保留vcpkg已安装目录或Conan缓存，可以大幅度缩短构建时间。&lt;/li>
&lt;/ol>
&lt;h2 id="结论">结论
&lt;/h2>&lt;p>由于平台依赖性和构建工具的复杂性，在C++项目中构建CI/CD流水线乍看之下门槛很高。然而，通过正确结合GitHub Actions、现代CMake以及CTest/CPack的生态系统，就能获得极具威力且自动化的开发工作流。&lt;/p>
&lt;p>本文所讲解的基于矩阵策略的跨平台验证、基于Sanitizer的运行时Bug检测、覆盖率测量，以及自动部署到GitHub Releases，都是在商业级开源项目中被广泛采用的最佳实践。&lt;/p>
&lt;p>自动化的CI/CD流水线是最小化开发者在“寻找Bug”和“手动构建/发布”上所耗费的时间，使其能够专注于本质的创造性编码活动的最强武器。请务必也引入到您的C++项目中，实现敏捷且充满安心感的开发生活。&lt;/p></description></item><item><title>如何利用Cloudflare Pages和GitHub Pages免费、极速地托管Hugo博客</title><link>http://kenji.blog/zh-cn/p/hugo-hosting-cloudflare-vs-github-pages/</link><pubDate>Sat, 12 Sep 2026 12:00:00 +0900</pubDate><guid>http://kenji.blog/zh-cn/p/hugo-hosting-cloudflare-vs-github-pages/</guid><description>&lt;img src="http://kenji.blog/p/hugo-hosting-cloudflare-vs-github-pages/img/eyecatch.jpg" alt="Featured image of post 如何利用Cloudflare Pages和GitHub Pages免费、极速地托管Hugo博客" />&lt;p>在运营网站或博客时，页面加载速度（性能）、运营成本以及安全性是至关重要的因素。过去，动态CMS（内容管理系统，如WordPress）与租用服务器的组合是主流，但现在，被称为“Jamstack”的架构受到了极大的关注。其中，将由Go语言编写的超高速静态网站生成器（SSG）“Hugo”与Cloudflare Pages或GitHub Pages等现代托管服务相结合，可以构建&lt;strong>完全免费且极速&lt;/strong>的博客环境。&lt;/p>
&lt;p>本文将从技术角度深入探讨，为您详细讲解将Hugo生成的静态网站发布到Cloudflare Pages和GitHub Pages的具体步骤、各平台在架构上的差异、如何使用GitHub Actions构建CI/CD（持续集成/持续部署）、DNS优化、缓存策略，以及如何引入注重隐私的访问分析工具。&lt;/p>
&lt;hr>
&lt;h2 id="1-静态网站生成器ssg与jamstack基础">1. 静态网站生成器（SSG）与Jamstack基础
&lt;/h2>&lt;h3 id="11-为什么选择静态网站">1.1 为什么选择静态网站？
&lt;/h3>&lt;p>传统的动态CMS（例如：WordPress）在每次收到用户请求时，都会向数据库（如MySQL）发起查询，并在服务器端（如PHP）动态生成HTML后返回。这种方式虽然灵活性高，但应对流量激增（所谓的爆发性流量或DDoS攻击）的抵抗力较低，且往往需要在前端放置缓存服务器（如Redis或Varnish），导致基础设施架构变得复杂。&lt;/p>
&lt;p>相比之下，采用Jamstack（JavaScript, APIs, and Markup）架构的静态网站生成器（SSG）会在事前（构建时）生成所有的HTML文件、CSS和JavaScript。当用户发起请求时，Web服务器（或CDN）只需将已生成的静态文件直接返回，从而实现压倒性的高速和坚固的安全性。&lt;/p>
&lt;h3 id="12-hugo的优势">1.2 Hugo的优势
&lt;/h3>&lt;p>在SSG中，有Next.js、Gatsby、Jekyll、Astro等多种选择，而Hugo最大的特点在于其&lt;strong>构建速度&lt;/strong>。得益于Go语言的并发处理能力，即使是拥有几千到几万页面的网站，构建也能在短短几秒内完成。这大幅减少了CI/CD流水线中的等待时间，直接提升了开发者体验（DX: Developer Experience）。&lt;/p>
&lt;hr>
&lt;h2 id="2-托管服务架构比较">2. 托管服务架构比较
&lt;/h2>&lt;p>将Hugo生成的静态文件托管在哪里，是接下来的课题。典型的选项包括Cloudflare Pages、GitHub Pages以及Netlify，但它们背后的网络架构各有不同。&lt;/p>
&lt;h3 id="21-cdn与边缘计算">2.1 CDN与边缘计算
&lt;/h3>&lt;p>这些平台全部利用全球分布的CDN（内容分发网络）来分发内容。然而，与仅仅缓存静态文件不同的是，能否通过“边缘计算”在离用户最近的PoP（节点，Point of Presence）执行请求路由和请求头重写，成为了它们的差异化因素。&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;用户 (东京)&amp;#34;] --&amp;gt;|低延迟| B[&amp;#34;边缘节点 (NRT)&amp;#34;]
C[&amp;#34;用户 (纽约)&amp;#34;] --&amp;gt;|低延迟| D[&amp;#34;边缘节点 (EWR)&amp;#34;]
E[&amp;#34;用户 (伦敦)&amp;#34;] --&amp;gt;|低延迟| F[&amp;#34;边缘节点 (LHR)&amp;#34;]
B --&amp;gt; G[&amp;#34;源站存储 / 对象存储&amp;#34;]
D --&amp;gt; G
F --&amp;gt; G
&lt;/pre>
&lt;h3 id="22-github-pages">2.2 GitHub Pages
&lt;/h3>&lt;p>GitHub Pages是一项能够直接从GitHub仓库发布HTML、CSS和JavaScript文件的服务。其背后使用了Fastly等CDN，能够发挥出色的性能。但是，它在自定义请求头（例如：&lt;code>Cache-Control&lt;/code>和安全头）方面存在限制，而且重定向设置还需要依赖HTML的meta refresh或Jekyll插件，作为纯粹基础设施的功能显得较为有限。&lt;/p>
&lt;h3 id="23-cloudflare-pages">2.3 Cloudflare Pages
&lt;/h3>&lt;p>Cloudflare Pages是建立在Cloudflare引以为豪的全球最大规模Anycast网络（覆盖275个以上城市）之上的静态网站托管服务。
它原生支持HTTP/3（QUIC）、图像优化以及集成边缘函数（Cloudflare Workers），可以进行极致的性能调优。此外，它不对带宽进行计费，无论流量如何激增都能免费运营，这是一个巨大的优势。&lt;/p>
&lt;h3 id="24-netlify">2.4 Netlify
&lt;/h3>&lt;p>Netlify是Jamstack的先驱，提供了集成表单功能、认证（Identity）、无服务器函数等在内的多合一DX体验。然而，当超出免费额度的带宽（每月100GB）时，将会产生高昂的按量计费费用，因此在大量使用图像和视频的博客中需要注意成本控制。&lt;/p>
&lt;hr>
&lt;h2 id="3-性能与延迟的理论计算基于latex的数学模型">3. 性能与延迟的理论计算（基于LaTeX的数学模型）
&lt;/h2>&lt;p>在评估Web性能时，减少延迟（Latency）是最重要的指标。我们来建立一个模型，看看与直接访问源站相比，使用CDN（边缘）能够减少多少延迟。&lt;/p>
&lt;p>设用户请求命中缓存的概率为“缓存命中率（Cache Hit Ratio）”，记作 $C$。且 $0 \le C \le 1$。
设到源站的延迟为 $L_{origin}$，到最近边缘节点的延迟为 $L_{edge}$。&lt;/p>
&lt;p>新的平均延迟 $L_{new}$ 可以作为以下期望值进行计算：&lt;/p>
$$ L_{new} = C \times L_{edge} + (1 - C) \times (L_{edge} + L_{origin}) $$&lt;p>将此公式简化后如下所示：&lt;/p>
$$ L_{new} = L_{edge} + (1 - C) \times L_{origin} $$&lt;p>例如，当东京的用户访问位于美国东海岸（纽约）的源站时，考虑到光纤的物理距离和路由器处理延迟，$L_{origin}$ 约在 200 ms 左右。另一方面，如果使用像Cloudflare这样的CDN，就可以连接到东京的边缘节点，$L_{edge}$ 会缩短到约 10 ms。&lt;/p>
&lt;p>假设缓存命中率为 $C = 0.95$（95%），则：&lt;/p>
$$ L_{new} = 10 + (1 - 0.95) \times 200 = 10 + 0.05 \times 200 = 10 + 10 = 20 \text{ ms} $$&lt;p>由此可见，通过引入CDN，可以将平均延迟从 210 ms 大幅（约90%）降低到 20 ms。&lt;/p>
&lt;hr>
&lt;h2 id="4-使用github-actions构建cicd流水线">4. 使用GitHub Actions构建CI/CD流水线
&lt;/h2>&lt;p>为了自动化Hugo博客的更新流程，我们利用GitHub Actions构建CI/CD流水线。这样一来，只需在本地编写Markdown文章并执行 &lt;code>git push&lt;/code>，就会自动触发构建，并部署到Cloudflare Pages或GitHub Pages。&lt;/p>
&lt;p>以下的时序图展示了从推送文章到分发给用户的整个流程。&lt;/p>
&lt;pre class="mermaid">
sequenceDiagram
participant U as &amp;#34;用户 (作者)&amp;#34;
participant G as &amp;#34;GitHub 仓库&amp;#34;
participant A as &amp;#34;GitHub Actions&amp;#34;
participant C as &amp;#34;Cloudflare Pages&amp;#34;
participant V as &amp;#34;访问者&amp;#34;
U-&amp;gt;&amp;gt;G: &amp;#34;git push origin main&amp;#34;
G-&amp;gt;&amp;gt;A: &amp;#34;触发 Push 事件&amp;#34;
A-&amp;gt;&amp;gt;A: &amp;#34;Checkout &amp;amp; 设置 Hugo&amp;#34;
A-&amp;gt;&amp;gt;A: &amp;#34;hugo --minify (构建)&amp;#34;
A-&amp;gt;&amp;gt;C: &amp;#34;部署到 Cloudflare Pages (直接上传)&amp;#34;
C--&amp;gt;&amp;gt;A: &amp;#34;部署 URL (成功)&amp;#34;
V-&amp;gt;&amp;gt;C: &amp;#34;请求网页&amp;#34;
C--&amp;gt;&amp;gt;V: &amp;#34;返回缓存的 HTML (边缘)&amp;#34;
&lt;/pre>
&lt;h3 id="41-针对cloudflare-pages的部署设置直接上传">4.1 针对Cloudflare Pages的部署设置（直接上传）
&lt;/h3>&lt;p>Cloudflare Pages提供了两种方法：一是绑定GitHub仓库并在Cloudflare的基础设施上进行构建；二是将在GitHub Actions中构建好的静态文件进行“直接上传（Direct Upload）”。如果想更严格地管理Hugo的版本，并与其他任务（如测试和图像优化）联动，推荐在GitHub Actions上构建并使用直接上传的方式。&lt;/p>
&lt;p>以下是一个用于部署到Cloudflare Pages的 &lt;code>.github/workflows/deploy.yml&lt;/code> 实用示例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Deploy Hugo site to Cloudflare Pages&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">push&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">branches&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;main&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">workflow_dispatch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build-and-deploy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;ubuntu-latest&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Checkout repository&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;actions/checkout@v4&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">submodules&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;recursive&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fetch-depth&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Setup Hugo&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;peaceiris/actions-hugo@v3&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hugo-version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.125.0&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">extended&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Build Hugo Site&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;hugo --minify --gc&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">HUGO_ENVIRONMENT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;production&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Deploy to Cloudflare Pages&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;cloudflare/pages-action@v1&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">apiToken&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.CLOUDFLARE_API_TOKEN }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">accountId&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.CLOUDFLARE_ACCOUNT_ID }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">projectName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;your-project-name&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;public&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">gitHubToken&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.GITHUB_TOKEN }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">branch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;main&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在这条流水线中，通过 &lt;code>--minify&lt;/code> 选项最小化了HTML/CSS/JS代码，并通过 &lt;code>--gc&lt;/code> 删除了不必要的文件。这些都是性能优化的基本操作。&lt;/p>
&lt;hr>
&lt;h2 id="5-深入理解dns设置自定义域名与cname--alias记录">5. 深入理解DNS设置：自定义域名与CNAME / ALIAS记录
&lt;/h2>&lt;p>当使用自定义域名（例如：&lt;code>kenji.blog&lt;/code>）时，正确的DNS（域名系统）设置是必不可少的。&lt;/p>
&lt;h3 id="51-cname记录的限制与zone-apex">5.1 CNAME记录的限制与Zone Apex
&lt;/h3>&lt;p>通常情况下，将子域名（例如：&lt;code>www.kenji.blog&lt;/code>）指向外部服务时，会使用 &lt;code>CNAME&lt;/code> 记录。然而，根据DNS规范（RFC 1034），根域名（Zone Apex，也称为裸域名，例如：&lt;code>kenji.blog&lt;/code>）不能设置 &lt;code>CNAME&lt;/code> 记录。这是因为Zone Apex必须存在SOA（起始授权机构）记录、NS（名称服务器）记录和MX（邮件交换）记录，而规则规定CNAME不能与其他资源记录共存。&lt;/p>
&lt;h3 id="52-解决方案alias--aname--cname-flattening">5.2 解决方案：ALIAS / ANAME / CNAME Flattening
&lt;/h3>&lt;p>为了解决这个问题，现代的DNS提供商提供了他们独有的扩展功能。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>ALIAS / ANAME记录&lt;/strong>: 在DNS服务器端动态解析，并将最终的A记录（IP地址）返回给客户端。Amazon Route 53等支持该功能。&lt;/li>
&lt;li>&lt;strong>CNAME Flattening&lt;/strong>: 这是Cloudflare提供的功能。它表现得就像是在Zone Apex设置了CNAME一样，但实际上是Cloudflare的权威DNS服务器自动解析出一组IP地址（A记录和AAAA记录）并透明地返回给客户端。&lt;/li>
&lt;/ul>
&lt;p>如果使用Cloudflare Pages，将域名的名称服务器委托给Cloudflare，并利用这个“CNAME Flattening”功能，将是实现无缝且高性能的最佳架构。&lt;/p>
&lt;hr>
&lt;h2 id="6-缓存策略与http请求头控制">6. 缓存策略与HTTP请求头控制
&lt;/h2>&lt;p>在静态网站加速方面，另一个关键点是“缓存策略”。在Cloudflare Pages中，可以利用生成的文件（&lt;code>_headers&lt;/code> 文件）对HTTP响应头进行精细的控制。&lt;/p>
&lt;h3 id="61-边缘缓存-edge-cache-vs-浏览器缓存-browser-cache">6.1 边缘缓存 (Edge Cache) vs 浏览器缓存 (Browser Cache)
&lt;/h3>&lt;p>缓存大致可以分为在CDN端保存的“边缘缓存（Edge Cache）”和在用户浏览器中保存的“浏览器缓存（Browser Cache）”两种。&lt;/p>
&lt;p>对于静态文件（如文件名中包含哈希值的图像、CSS、JS等），理想的做法是让浏览器进行长期的缓存。另一方面，为了使HTML文件的更新能够立即生效，一般的做法是缩短（或禁用）浏览器缓存，转而依赖边缘缓存来处理请求。&lt;/p>
&lt;p>Cloudflare Pages中的 &lt;code>_headers&lt;/code> 设置示例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># HTML文件不在浏览器缓存，每次都进行验证
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/*.html
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Cache-Control: public, max-age=0, must-revalidate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># 资源文件（CSS/JS/图像）让浏览器缓存1年
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/assets/*
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Cache-Control: public, max-age=31536000, immutable
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/img/*
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Cache-Control: public, max-age=31536000, immutable
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="62-带宽成本削减的计算公式">6.2 带宽成本削减的计算公式
&lt;/h3>&lt;p>通过设置合适的缓存头，可以大幅减少从服务器（边缘）传输的数据量。月度带宽成本 $Cost$ 可以通过各资源的传输量 $B_i$、缓存命中率 $C_i$ 以及带宽单价 $R$ 的以下模型来表示：&lt;/p>
$$ Cost = \sum_{i=1}^{n} \left( B_i \times (1 - C_i) \times R \right) $$&lt;p>由于Cloudflare的下行传输量是免费的（$R = 0$），因此直接的资金成本为 $0$。然而，如果同时使用GitHub Pages等其他基础设施，或者以AWS S3等作为后端，最大化这个缓存命中率 $C_i$ 将是削减基础设施成本的关键。&lt;/p>
&lt;hr>
&lt;h2 id="7-兼顾隐私与性能的访问分析">7. 兼顾隐私与性能的访问分析
&lt;/h2>&lt;p>在运营博客时，了解有多少用户访问的访问分析（Web Analytics）是必不可少的。长期以来，Google Analytics（GA4）一直是事实上的标准，但随着近年来隐私保护的趋势（如GDPR、CCPA）以及第三方Cookie的废除，情况正在发生变化。&lt;/p>
&lt;h3 id="71-对web性能的影响">7.1 对Web性能的影响
&lt;/h3>&lt;p>如果引入Google Analytics（具体来说是 &lt;code>gtag.js&lt;/code> 或 Google Tag Manager），将会加载和执行大量的外部脚本，从而对性能（尤其是TTFB和主线程阻塞时间）产生负面影响。&lt;/p>
&lt;p>让我们将网站的加载时间进行如下分解：&lt;/p>
&lt;pre class="mermaid">
pie title &amp;#34;典型网站加载时间分布&amp;#34;
&amp;#34;TTFB (首字节时间)&amp;#34; : 15
&amp;#34;内容下载 (HTML/CSS)&amp;#34; : 20
&amp;#34;DOM 解析与渲染&amp;#34; : 45
&amp;#34;分析与第三方 JS&amp;#34; : 20
&lt;/pre>
&lt;p>第三方JS分析工具占据总加载时间约20%到30%的情况也并不少见。&lt;/p>
&lt;h3 id="72-引入cloudflare-web-analytics">7.2 引入Cloudflare Web Analytics
&lt;/h3>&lt;p>因此，像Cloudflare Web Analytics或Plausible Analytics这样，不使用Cookie（Cookieless）且隐私优先的访问分析工具正受到瞩目。&lt;/p>
&lt;p>Cloudflare Web Analytics只需嵌入一个非常轻量的JavaScript片段即可运行，由于不生成Cookie，因此无需设置烦人的Cookie同意横幅（Cookie Consent Banner）。&lt;/p>
&lt;p>在Hugo中的实现也非常简单。只需在 &lt;code>layouts/partials/head.html&lt;/code> 或 &lt;code>layouts/partials/analytics.html&lt;/code> 中添加提供的代码片段即可。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">{{ if eq hugo.Environment &amp;#34;production&amp;#34; }}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- Cloudflare Web Analytics --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">defer&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;https://static.cloudflareinsights.com/beacon.min.js&amp;#39;&lt;/span> &lt;span class="na">data-cf-beacon&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;{&amp;#34;token&amp;#34;: &amp;#34;YOUR_CLOUDFLARE_BEACON_TOKEN&amp;#34;}&amp;#39;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- End Cloudflare Web Analytics --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{{ end }}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过添加 &lt;code>defer&lt;/code> 属性，可以异步加载脚本而不阻塞HTML的解析，并在DOM构建完成后执行。这样可以把对初始显示速度（LCP: Largest Contentful Paint 和 FCP: First Contentful Paint）的影响降到最低。&lt;/p>
&lt;hr>
&lt;h2 id="8-总结与最佳实践">8. 总结与最佳实践
&lt;/h2>&lt;p>在使用Hugo运营静态网站时，采用像Cloudflare Pages和GitHub Pages这样现代的托管平台，在性价比、加载速度和安全性方面都具有压倒性的优势。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>极速构建&lt;/strong>: 发挥Hugo的高速特性，将CI/CD流水线（GitHub Actions）的执行时间降至最低。&lt;/li>
&lt;li>&lt;strong>边缘分发&lt;/strong>: 利用Cloudflare的边缘网络，以毫秒级的延迟将内容分发给全球用户。&lt;/li>
&lt;li>&lt;strong>适当的DNS配置&lt;/strong>: 运用CNAME Flattening安全且高速地运营Zone Apex（自定义域名）。&lt;/li>
&lt;li>&lt;strong>优化缓存策略&lt;/strong>: 使用 &lt;code>_headers&lt;/code> 针对各类资源合理分离浏览器缓存和边缘缓存。&lt;/li>
&lt;li>&lt;strong>轻量级分析&lt;/strong>: 引入兼顾隐私且不损耗性能的Cloudflare Web Analytics等工具。&lt;/li>
&lt;/ol>
&lt;p>通过将这些实践结合起来，即可免费构建一个足以应对每月数百万PV级别大规模流量的、可扩展且坚固的博客系统。如果您正在考虑搭建技术博客、企业网站或个人作品集网站，请务必尝试一下这种 Jamstack + Hugo + Cloudflare Pages 的架构。&lt;/p></description></item></channel></rss>