自动确定编译所需的所有 ctan 包

自动确定编译所需的所有 ctan 包

我保持相对精简的 texlive,并通过 tlmgr 根据需要安装单个软件包。有时我会和同事一起工作,并需要编写一篇新论文。

现在,我的流程是尝试编译(通常使用 pdflatex)。如果编译器报错,例如,说File 'cancel.sty' not found,那么我将通过 tlmgr 安装必要的包。然后我尝试再次编译,并根据需要重复。

诚然,这不是最糟糕的事情。可能需要 5 分钟才能熟悉一个新项目。但如果有一个工具可以确定全部一次删除必需或缺失的软件包。我设想一个工具,它可以收集所有依赖项并编写类似于dependencies.txtPython 中常见的依赖项。

有这样的事吗?


我还会指出,我可能正在尝试优化此问题的错误解决方案。如果我应该以完全不同的方式考虑依赖管理,请告诉我。但出于空间原因,我没有计划安装庞大的 texlive。

答案1

你可以编写一些脚本来完成 Lua 中的大量工作。下面的脚本使用 TeX Live 的--recorder数据,因此你需要执行类似以下操作

pdflatex <name> --recorder

texlua depreader.lua <name>

(假设你调用脚本depreader.lua,这就是我所拥有的)。还有一些限制

  • 只能拾取 TeX 运行读取的文件;Biber 或 MakeIndex 等所需的内容将会丢失(kpse我怀疑拾取这些内容需要某种形式的修改,更类似于 MiKTeX)
  • 您必须确保记录器运行已设置好一切(例如,条件加载、多次运行等可能会导致问题)
  • 它不会拾取用于格式构建的文件(连字符模式等)
  • 它很慢,因为它需要tlmgr调用每一个文件名
  • 它没有考虑 TeX Live 数据库中列出的依赖项(因此每个 TL 包都明确列出)

话虽如此,我认为这会让你动起来:

local name = arg[1] or "test"

local execute = os.execute
local find = string.find
local gmatch = string.gmatch
local gsub = string.gsub
local insert = table.insert
local match = string.match
local popen = io.popen
local sort = table.sort

local function extract_name(file)
  local path, name = match(file,"^(.*)/([^/]*)$")
  if path then
    return name
  else
    return file
  end
end

local f = io.open(name .. ".fls")
local data = gsub(f:read("*all") .. "\n","\r\n","\n")

local t = {}

for line in gmatch(data,"([^\n]*)\n") do
  if not (line == ""
       or match(line,name .. ".tex$")
       or match(line,"^PWD ")
       or match(line,"^OUTPUT")
       or match(line,"^INPUT %./")
       or match(line,"%.aux$")
       or match(line,"%.cnf$")
       or match(line,"%.fmt$")
       or match(line,"texmf%-var")) then
    local file = extract_name(gsub(line,"^INPUT ",""))
    t[file] = line
  end
end

local pkgs = {}
local unmatched = {}

for name,path in pairs(t) do
  local f = popen("tlmgr search --file /" .. name)
  local data = f:read("*all")
  local _,n = gsub(data,":","")
  if n == 1 then
    pkgs[match(data,"^([^:]*)")] = true
  elseif n == 0 then
    unmatched[name] = true
  else
    local function search(data,path)
      local pkgline = ""
      for line in gmatch(data,"([^\n]*)\n") do
        if match(line,":$") then
          pkgline = line
        else
          -- Find 'plain' - no patterns, also trimming spaces
          if find(path,gsub(line,"%s+",""),0,true) then
            return gsub(pkgline,":$","")
          end
        end
      end
      return false
    end
    local pkg = search(data,path)
    if pkg then
      pkgs[pkg] = true
    else
      unmatched[name] = true
    end
  end
end

local function print_sorted(t)
  local s = {}
  for k,_ in pairs(t) do
    insert(s,k)
  end
  sort(s)
  for _,v in ipairs(s) do
    print(v)
  end
end

print("TeX Live packages")
print_sorted(pkgs)

if next(unmatched) then
  print("\nNo match for")
  print_sorted(unmatched)
end

如果有兴趣,我可能会尝试让这个过程更顺畅一些,但不会尝试去做latexmk诸如多次运行等事情。

相关内容