🐡

Typixを使って複数環境でTypstでスライドをコンパイルする

2024/12/11に公開

TL;DR

  • Typixを使うとNixとTypstが扱いやすい
  • Nixでも抽象化しきれない部分でエラーになることがある
    • 動作しているOSの情報を参照している部分とか

はじめに

Typstでスライドを作成するツールにTouyingというものがあります。

これを使うとスライドをTypstのコードから生成できます。

Typstのコードから生成できるということはPlain textから生成できるという訳で、Plain textということはgitで管理してdiffをGitHubなどで見ることができるということです。

プログラマなのでdiffを見ることができないファイルをバージョン管理するのはたいへんつらいです。

なので、これができるだけでtypstを採用する理由になるのではないかと個人的には思っています。

さて、筆者は種々の事情からMac, Windows, Linuxを併用しているのですが、これらの環境で再現性のあるコンパイルをしようとしたときに困ったことがあったので、それについての備忘録を残します。

この記事で解説しないこと

  • Typstの書き方
  • Touyingの使い方
  • Nixの使い方

この記事で解説すること

  • 複数環境でTypst, Touyingのcompileをしたときに困ったこと

この記事の対象読者

  • Plain textからスライドを生成したい人
  • 複数の環境で同じスライドを生成したい人
  • お絵描きツールをGitで管理したくない人

再現性のあるTypstのコンパイル

7日目の@tani_qiitaさんのNixによる再現性のあるTypst執筆環境で述べられているように、システムから独立したビルドの手法としてNixというものがあります。

先述の記事ではNixをそのまま使う方法が紹介されていますが、NixからTypstをeasyに扱うためのツールとしてloqusion/typixというものもあります。

fontの管理など生のNixを触ると煩雑になる部分が抽象化されているようです。

Typix aims to make it easier to use Nix in Typst projects.

  • Dependency management: supports arbitrary dependencies including fonts, images, and data
  • Reproducible: via a hermetically sealed build environment
  • Extensible: fully compatible with Typst packages

https://loqusion.github.io/typix/#feature

このツールを使って複数環境でTypstのコンパイルをしようとしたとき、環境ごとにビルドできたりできなかったりなどの事象が起きました。

コード 長いので折り畳み
#import "@preview/touying:0.5.3": *
#import themes.simple: *

#show: simple-theme.with(aspect-ratio: "16-9")

= Title

== First Slide

Hello, Touying!

#pause

Hello, Typst!

#pause

こんにちは、Typst!
{
  description = "A Typst project";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    typix = {
      url = "github:loqusion/typix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    flake-utils.url = "github:numtide/flake-utils";
    typst-packages = {
      url = "github:typst/packages";
      flake = false;
    };
  };

  outputs =
    inputs@{
      nixpkgs,
      typix,
      flake-utils,
      ...
    }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        typixLib = typix.lib.${system};

        src = typixLib.cleanTypstSource ./.;
        # Watch a project and recompile on changes
        watch-script = typixLib.watchTypstProject commonArgs;

        typstPackagesSrc = pkgs.symlinkJoin {
          name = "typst-packages-src";
          paths = [
            "${inputs.typst-packages}/packages"
          ];
        };

        typstPackagesCache = pkgs.stdenv.mkDerivation {
          name = "typst-packages-cache";
          src = typstPackagesSrc;
          dontBuild = true;
          installPhase = ''
            mkdir -p "$out"
            cp -LR --reflink=auto --no-preserve=mode -t "$out" "$src"/*
          '';
        };

        commonArgs = {
          typstSource = "main.typ";
          fontPaths = [
            "${pkgs.udev-gothic}/share/fonts/udev-gothic"
          ];
          typstOpts = {};
          virtualPaths = [];
        };

        build-drv = typixLib.buildTypstProject (
          commonArgs
          // {
            inherit src;
            XDG_CACHE_HOME = typstPackagesCache;
          }
        );

        # Compile a Typst project, and then copy the result
        # to the current directory
        build-script = typixLib.buildTypstProjectLocal (
          commonArgs
          // {
            inherit src;
            XDG_CACHE_HOME = typstPackagesCache;
          }
        );
      in
      {
        checks = {
          inherit build-drv build-script watch-script;
        };

        packages.default = build-drv;

        apps = rec {
          default = watch;
          build = flake-utils.lib.mkApp {
            drv = build-script;
          };
          watch = flake-utils.lib.mkApp {
            drv = watch-script;
          };
        };

        devShells.default = typixLib.devShell {
          inherit (commonArgs) fontPaths virtualPaths;
          packages = [
            # WARNING: Don't run `typst-build` directly, instead use `nix run .#build`
            # See https://github.com/loqusion/typix/issues/2
            # build-script
            watch-script
            # More packages can be added here, like typstfmt
            # pkgs.typstfmt
          ];
        };
      }
    );
}

具体的には下記のようなコンパイルエラーが起きました。

> (前略)
> downloading @preview/touying:0.5.3
> 295.3 KiB / 295.3 KiB (100 %) 295.3 KiB/s in 26.27 ms ETA: 0 s
>
> error: failed to decompress package (failed to create `/homeless-shelter/Library/Caches/typst/packages/preview/touying/0.5.3`)
>   ┌─ sample.typ:1:8
>   │
> 1 │ #import "@preview/touying:0.5.3": *
>   │         ^^^^^^^^^^^^^^^^^^^^^^^^
> (後略)

このエラーはMacだけで起こるものでした。

Typstのpackageの使用についてはworkaroundで紹介されているものだったので、typix側の問題かと思いましたが違いました。

どうやらTypstは内部的にdirsのcrateを使っているようで、動作しているOSによってパッケージのインストール先を変更していました。

https://github.com/typst/typst/issues/3892

なので、mac OSを対象にする場合、Typstの0.12.0で追加されたpackage-pathのオプションを使うとOS差分を減らせます。

(あるいは/homeless-shelter/Library/...をMacのときだけ用意するようにflake.nixを設定するか)

nix
56c56,58
<           typstOpts = {};
---
>           typstOpts = {
>             package-path = typstPackagesCache;
>           };
64d65
<             XDG_CACHE_HOME = typstPackagesCache;
74d74
<             XDG_CACHE_HOME = typstPackagesCache;

これで手元のPCがMacの時にCIでコンパイルしたいなど、複数OSで再現性を持ってコンパイルできるようになりました。

おわりに

本記事で使用したスクリプトはOmochice/toy-touying-typixにあります。

再現性のあるコンパイルができていていればCIでも他の人のPCでも同じ見た目のPDFが生成されるはずです。

Happy Typst Life!

Discussion