拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Coursebook预处理与条件编译详解:从 include 到宏定义的完整指南

Coursebook预处理与条件编译详解:从 include 到宏定义的完整指南

Coursebook预处理与条件编译详解:从 #include 到宏定义的完整指南

【免费下载链接】coursebookOpen Source Introductory Systems Programming Textbook for the University of Illinois项目地址: https://gitcode.com/GitHub_Trending/co/coursebook

Coursebook是伊利诺伊大学开源的 CS341 系统编程教材,用 LaTeX 编写、由 Makefile 驱动构建。它最值得一读的不是章节内容,而是源码背后的预处理与条件编译机制:章节如何按序引入、ifepub条件开关如何生效、TAGGED变量如何切换构建模式、宏定义如何在 PDF / EPUB / Wiki 三种输出间被覆盖。本文带你从 C 语言最熟悉的#include讲起,完整拆解这套构建管线。

为什么一本教材需要"预处理" 🧩

学过 C 语言的人都清楚:#include负责把别的文件"塞进"当前文件,宏定义负责按条件生成或改写代码。LaTeX 世界里有几乎一模一样的概念,而 Coursebook 把这套思想发挥到了极致——同一份源码,最终要产出三种完全不同的东西:

产物构建入口关键预处理手段
带无障碍标签的 PDF(默认)main_tagged.tex\DocumentMetadata+ 条件编译
未加标签的 PDF(调试用)main_wrapper.texTAGGED=0变量切换
EPUB 电子书 / Wiki 网页版main.tex 经 pandoc宏重定义覆盖

下面按构建顺序,一站一站看源码到底是怎么被"预处理"的。

第一步:order.yaml 生成 include 指令

整本书的 18 个章节顺序,写在一份易读的 YAML 文件里,见 order.yaml:

- introduction/introduction - background/background - introc/introc ... - post_mortems/post_mortems

构建时,Makefile 会先用 gen_order.py 把这份清单转成 LaTeX 的\include指令,输出到自动生成的order.tex,规则见 Makefile:

$(ORDER_TEX): $(ORDER_TEX_DEP) python3 _scripts/gen_order.py $^ > $@

核心逻辑只有几行:读取 YAML 列表(列表天然有序),对每一项套上\include{...}模板后打印,见 gen_order.py。这就像 C 编译器把多个.c文件按链接顺序串起来——想调整章节顺序,只需改 YAML,无需碰任何.tex文件。

第二步:\input 与 \include,LaTeX 版的 #include

main.tex 通过\input{order.tex}引入上面生成的全部\include指令,于是 18 章依次并入主文档。这里两个命令的分工值得新手记一下:

  • \input:把文件内容原样"粘贴"进来,不强制换页。main.tex用它引入序言和章节清单,见 main.tex。
  • \include:除了引入内容,还会自动处理分页和辅助文件,适合章节这种大块内容。

每个章节目录都自成一体:.tex正文、.bib参考文献、drawings/插图,例如 deadlock/deadlock.tex 中插图就是这样被引入的,且带了无障碍 alt 文本,见 deadlock.tex:

\includegraphics[width=.6\textwidth,alt={Resource allocation graph...}]{deadlock/drawings/rag.eps}

除了\include,LaTeX 还有一个更狠的"条件编译"工具——\includeonly。Coursebook 用它构建单章 PDF:Makefile 为每个章节临时生成一个 wrapper 文件,先声明只构建某一章,再完整输入main.tex,见 Makefile:

echo "\includeonly{$(basename $<)}\input{$(MAIN_TEX)}" >> $@.tmp

这相当于 C 里#ifdef CHAPTER_X只编译对应模块:整本书的排版上下文(字体、页眉、参考文献)原封不动,却只输出一章,调试速度飞快。

第三步:ifepub 条件开关的默认值技巧 🔁

main.tex开头有一段堪称教科书级的 LaTeX 条件编译,见 main.tex:

\ifcsname ifepub\endcsname\else \expandafter\let\csname ifepub\expandafter\endcsname \csname iffalse\endcsname \fi

翻译成人话:如果调用方没定义ifepub这个开关,就默认把它设成"关"。这样任何输出渠道都能通过"预定义开关"来控制正文走向,而不必修改main.tex本身。

以 PDF 构建为例,入口 main_wrapper.tex 只有一行:先显式声明\let\ifepub\iffalse,再\input{main.tex}——相当于 C 程序main之前先#define IFEPUB 0。

第四步:Makefile 里 TAGGED 变量的条件编译

真正的"模式开关"在 Makefile 顶层,用 shell 变量做条件编译:

TAGGED ?= 1 ifeq ($(TAGGED),0) MAIN_TEX=main_wrapper.tex else MAIN_TEX=main_tagged.tex endif
  • 直接make pdf:走 main_tagged.tex,在最前面注入\DocumentMetadata{...}(语言、PDF 标准 UA-2 等元数据),产出屏幕阅读器友好的带标签 PDF;
  • make pdf TAGGED=0:跳过元数据,直接构建同款书籍,更快、更利于调试。

巧妙的是 Makefile 还用一个.pdf-mode时间戳文件(见 Makefile)记录上次构建的模式,模式一变就强制全部重编——避免两种模式的 PDF 互相"串味"。

第五步:宏定义与覆盖机制

Coursebook 把"可替换点"全部做成宏,在 prelude.tex 统一定义:

\newcommand{\keyword}[1]{\underline{\smash{\textbf{\texttt{#1}}}}} \newcommand{\todo}[1]{{\color{red} TODO: {#1}}}

然后不同输出渠道各带一份"重定义文件",在正文之前先行注入:

  • EPUB 渠道:epub_redefinitions.tex 把\keyword重新定义为纯\texttt{#1}。原因很实用——pandoc 会把\smash连同内容一起吞掉,不覆盖的话全书关键词全部变空,见 Makefile 中 epub 规则把这个文件排在main.tex之前。
  • Wiki / GitHub 渠道:github_redefinitions.tex 把\gls、\todo、\epigraph、\keyword全部降级为纯文本,并把 100 多个数学命令重定义为 Unicode 符号(\leq→≤),让网页预览也能正常显示。

这就是宏定义覆盖的精髓:正文只写一次,每个输出渠道用"前置注入"的方式替换实现,与 C 中用不同config.h编译同一份源码的思路如出一辙。

三种产物构建速查表

目标命令预处理要点
带标签 PDFmake pdfmain_tagged.tex注入元数据 +ifepub默认关闭
调试用 PDFmake pdf TAGGED=0跳过元数据,入口换为main_wrapper.tex
单章 PDFmake chapters\includeonly只编译指定章
EPUBmake epubpandoc +epub_redefinitions.tex宏覆盖

相关配置文件还可顺藤摸瓜:latexmkrc(编译行为)、cs341book.sty(仅 PDF 生效的排版样式,注释里明确说明 pandoc 永远不会读取它)、prelude.tex(全部宏包与依赖声明)。

新手常见问题 FAQ

Q1:改了 order.yaml 没生效?order.tex是生成物,Makefile 会在依赖过期时自动重新运行gen_order.py,直接make即可,不要手改order.tex。

Q2:\input和\include到底怎么选?顺序编排、小段内容用\input;章节这种需要独立分页和辅助文件的大块用\include——Coursebook 就是这么分工的。

Q3:为什么 PDF 和 EPUB 里同一个词样式不一样?这是故意的。\keyword在 prelude.tex 里是粗体下划线等宽,在 epub_redefinitions.tex 里是普通等宽——各渠道的渲染能力不同,宏覆盖正是为此存在。

Q4:想给全书加一个"草稿水印",应该从哪里下手?仿照ifepub的模式:新增一个条件开关,在prelude.tex里写默认值,再让对应渠道的入口文件预先定义它——一行正文都不用动。

小结

Coursebook 的构建体系把 C 语言预处理器的三大件搬进了 LaTeX 世界:文件引入(order.yaml→\include)、条件编译(ifepub开关 +TAGGED变量 +\includeonly)、宏定义覆盖(按输出渠道重定义)。理解这套机制后,你不仅能在 18 章之间自由构建,还能举一反三地设计自己的多格式文档管线。动手从make pdf开始,跑一遍全流程,是最快的学习方式 🚀。

【免费下载链接】coursebookOpen Source Introductory Systems Programming Textbook for the University of Illinois项目地址: https://gitcode.com/GitHub_Trending/co/coursebook

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表