l3build 如何打包宏包?

发布于 2026-01-28 23:27:22

请问,下面的常规编译命令改用 l3build 的方式要怎么操作最方便啊?

我的目的是得到一本完整的 PDF 手册。

附上 hello.inshello.dtx 的源码。

下面的代码是使用传统的 DocStrip 的方式所用到的编译命令。

latex hello.ins

xelatex hello.dtx
makeindex -s gglo.ist -o hello.gls hello.glo
makeindex -s gind.ist -o hello.ind hello.idx
xelatex hello.dtx
xelatex hello.dtx

不知道为什么命令 l3build unpack 不能释放出 hello.sty 文件,会卡在 \usepackage{xeCJK} 这一行。然后我就问了 AI ,并用了下面的命令来编译:

latex hello.ins
l3build doc

然后因为我看不懂 l3build 文档,所以 build.lua 文件(上传失败)的代码也是 AI 生成的:

module = "hello"
-- 关键:禁止 l3build 尝试管理打包过程
packtds = 0
-- 关键:清空 unpackfiles,表示“不需要解包”
unpackfiles = {}
-- 明确源文件目录
sourcefiledir = "."
sourcefiles = {"hello.dtx", "hello.sty"} -- 将已存在的 .sty 也列为源文件

-- 文档编译设置
docfiledir = "."
docfiles = {module..".dtx"}
typesetfiles = docfiles
typesetexe = "xelatex"
typesetopts = "-interaction=nonstopmode"
typesetindexes = {
  ["hello.glo"] = {"hello.gls", "makeindex -s gglo.ist -o hello.gls hello.glo"},
  ["hello.idx"] = {"hello.ind", "makeindex -s gind.ist -o hello.ind hello.idx"}
}
typesetruns = 3

-- 可选:在清理时保留 .sty 文件
function clean_init()
  -- 不删除 hello.sty
  rm("hello.log", "hello.aux", "hello.glo", "hello.idx", "hello.gls", "hello.ind", "hello.ilg", "hello.out")
end

查看更多

关注者
0
被浏览
90
2 个回答
myhsia
myhsia 14小时前
PhD Student in Cond-Matt Physics at Westlake University

Hello~ l3build 重度使用者来咯~

给你简单写了个最基础的 build.lua,你执行 l3build doc 即可顺利编译
(注意到你加载了一些 Windows 特色字体,由于我是 macOS,所以注释了这些行,用 fandol 字体代替)

完整的 build.lua 在末尾,我现在给你一行行解释

module              = "hello"

这行表示声明你这个项目的名称,也就是包名字,这个变量会被 l3build 内部很多函数调用.

ctanzip             = module

这个表示上传CTAN打包时的压缩包名称,那么 module 定义为 hello,那么最后压缩文件名为 hello.zip

cleanfiles          = {"*.log", "*.pdf", "*.zip", "*.curlopt"}

l3build 存在一个选项 clean,这个就是打包 上传等过程中会生成的缓存文件,执行 l3build clean 会清理掉

excludefiles        = {"*~"}

这个嘛...就是不包含的文件,意思是打包上传 CTAN 时不会被塞入压缩包,原来的值是

excludefiles = {"*~","build.lua","config-*.lua"} 

但是我把后面俩去掉了——我有个习惯,就是把打包文件也上传 CTAN,这个是被 CTAN 允许的

textfiles           = {"*.md", "LICENSE", "*.lua"}

这个就是很没用的那种纯文本文档,但是能够起声明版权等作用,最后会被包含到 zip 里.
上一行我说我想把 build.lua 塞进去,那我就在这里用了个 dirty trick.

typesetexe          = "latexmk -xelatex"

嗯 很明显这行是排版的程序,其实官方手册并不允许这样做,你使用 latexmk 会报错,但是我在 build.lua 末尾做了些手段 :-),我就想用 latexmk(傲娇)

typesetruns         = 1

那么既然我们用 latexmk,运行一次就够啦(l3build 足足运行了 3回啊3回!!!)

好啦!完整文件如下,并附上完成 build 的结果
image.png

module              = "hello"

ctanzip             = module
cleanfiles          = {"*.log", "*.pdf", "*.zip", "*.curlopt"}
excludefiles        = {"*~"}
textfiles           = {"*.md", "LICENSE", "*.lua"}
typesetdemofiles    = {module .. "-demo.tex"}
typesetexe          = "latexmk -xelatex"
typesetruns         = 1

--[== "Hacks" to `l3build` | Do not Modify ==]--

function docinit_hook()
  cp(ctanreadme, unpackdir, currentdir)
  return 0
end
function tex(file,dir,cmd)
  dir = dir or "."
  cmd = cmd or typesetexe
  if os.getenv("WINDIR") ~= nil or os.getenv("COMSPEC") ~= nil then
    upretex_aux = "-usepretex=\"" .. typesetcmds .. "\""
    makeidx_aux = "-e \"$makeindex=q/makeindex -s " .. indexstyle .. " %O %S/\""
    sandbox_aux = "set \"TEXINPUTS=../unpacked;%TEXINPUTS%;\" &&"
  else
    upretex_aux = "-usepretex=\'" .. typesetcmds .. "\'"
    makeidx_aux = "-e \'$makeindex=q/makeindex -s " .. indexstyle .. " %O %S/\'"
    sandbox_aux = "TEXINPUTS=\"../unpacked:$(kpsewhich -var-value=TEXINPUTS):\""
  end
  return run(dir, sandbox_aux .. " " .. cmd         .. " " ..
                  upretex_aux .. " " .. makeidx_aux .. " " .. file)
end

其实后边 hack 也很简单:

function docinit_hook()
  cp(ctanreadme, unpackdir, currentdir)
  return 0
end

钩子,见过没?LaTeX里也有 \AddToHook 😋
l3build 提供了钩子 docinit_hook()l3build ctan 的过程分大概三部:unpack (就是抽离 sty), typeset (编译手册), 和 tar (压缩包);那么 docinit_hook() 就发生在 typeset 前,或者说 unpack 后,因为我喜欢把譬如 README.md 打包到 dtx 里,但是它又需要在GitHub首页展示,所以我一旦更改 README,我会在 dtx 里改,然后这样每次 build 时就会覆盖掉当前仓库根目录下的 README. cp 命令的用法是 cp(文件, 源路径, 新路径). unpackdir 就是每次执行 l3build unpack 等命令后目录出现的 ./build/unpacked, currentdir 就是仓库的根目录.

这段有点小难... 也是我想很久写出来的, 就是强制 latexmk
这里相当于重新定义 l3build 里原有的 tex 函数了,吃仨参数:文件,路径,命令

function tex(file,dir,cmd)

如果 dir 被定义了,那么这里临时变量 dir 就是等于被定义的 dir 的值;否则就是根目录;cmd 也同样

  dir = dir or "."
  cmd = cmd or typesetexe

Windows 系统的黑锅(发怒)

  if os.getenv("WINDIR") ~= nil or os.getenv("COMSPEC") ~= nil then

如果是Windows系统,那么有几个地方就是双引号

    upretex_aux = "-usepretex=\"" .. typesetcmds .. "\""
    makeidx_aux = "-e \"$makeindex=q/makeindex -s " .. indexstyle .. " %O %S/\""
    sandbox_aux = "set \"TEXINPUTS=../unpacked;%TEXINPUTS%;\" &&"
  else

否则就是单引号,记得转译斜杠(这个跟 l3build 无关,不会的恶补下 Linux)

好了,介绍下这些分别是啥,请打开 latexmk 手册

  • upretex_aux: 比如哈,你的项目里有 hook (最简单的:隐藏答案!) 你希望不改变文件比如 \documentclass[hideanswer] 这种窝囊的方式,那么可以在编译时加料, 例如你可以 latexmk -usepretex='\\AtBeginDocument\\DisableImplementation' (这是 l3doc 里的例子,注意 \\ 转译),那么这时你可以在前面定义 typesetcmds 这个变量,实现加料,此时 upretex_aux 这个临时变量被定义为 -usepretex=\'\\AtBeginDocument\\DisableImplementation\'.
    upretex_aux = "-usepretex=\'" .. typesetcmds .. "\'"

众所周知,bib 参考文献有 bst 格式文件,那么索引同样. 逆天 l3doc 偏要使用 gind.ist 索引文件,所以 latexmk 也有设置索引文件style的接口,剩下的 makeidx_auxupretex_aux 同理,自己对着 latexmk 手册慢慢看吧... 对了 makeidx_auxupretex_aux 这俩变量名是我瞎写的,只要后面调用时一致即可.

    makeidx_aux = "-e \'$makeindex=q/makeindex -s " .. indexstyle .. " %O %S/\'"

沙河机制,是的,你会发现每次 unpack 后 .sty 都跑到了 ./build/unpacked 里,而 ./build/doc 里却没有,这是因为 l3build 官方也采用了沙河机制,默认情况下比如 latexmk 也好,或者单独 pdflatex 也好,都会优先检索当前文件夹,然后在检索系统 texmf(貌似是?反正就是你 tlmgr update 后包安装的位置), 那么这个路径是可以改(往上叠加的,把 ./build/unpack 也给他加进去!)

    sandbox_aux = "TEXINPUTS=\"../unpacked:$(kpsewhich -var-value=TEXINPUTS):\""
  end

好了,这几个临时变量定义完了,接下来开始调用他们!
这个函数返回什么呢?run 是官方的一个借口(你可以理解为元语吧),在 argument dir (回忆:函数定义为 function tex(file, dir, cmd),这个 dir 就好比 #2)下,然后向命令行输出这些内容,这些内容已经被定义好.

  return run(dir, sandbox_aux .. " " .. cmd         .. " " ..
                  upretex_aux .. " " .. makeidx_aux .. " " .. file)
end
myhsia
myhsia 14小时前
PhD Student in Cond-Matt Physics at Westlake University

这只是简单的例子,正常情况下还有其他的东西
你可以看 install-LaTeX-guide-zh-cn 仓库的 build.lua (是的,也是我写的),但是那个因为我和啸行老师的习惯不同,所以一些过于 "超前" 的功能我没加进去,你可以看下我的 litetable 包的 build.lua

https://github.com/myhsia/litetable/blob/main/build.lua

这个包情况复杂:dtx 文件是纯英文写的,那么 latexmk -pdf 编译;但是还有普通话和粤语手册,自然要用 xe. 这个包是个很好的例子,你可以看下他的 build.lua.
同时这个包还提供了 demo 文件,并且还要保证 demo 文件先被 typeset 并丢到 ./buiild/doc 里从而三语言文档会用 pdfpages 的 \includepdf 去插入 demo ...
同时,还有 tag 功能,一键修改 dtx 文件里的日期和版本号...
还有上传 CTAN 功能,输入CTAN包管理员个人信息等
所以略微复杂,但是我觉得你如果能学完这个包的 l3build,你就基本上能在 l3build 横着走了,接下来你就要去官方 GitHub repo去看源代码,从而指导如果想实现一些自定义功能要改什么函数

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览