🤖

Rustプロジェクト上で、Nixを使用してビルド、チェック、cargo-llvm-covでのテストカバレッジを行なう方法

2024/07/28に公開

TL;DR

craneというRustプロジェクトをビルドするシステムにある、cargoLlvmCov関数ってスゴいよなぁという話。

どんなflake.nixを書いたか

flake.nix in kosu project

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
    treefmt-nix.url = "github:numtide/treefmt-nix";
    rust-overlay.url = "github:oxalica/rust-overlay";
    crane.url = "github:ipetkov/crane";
  };

  outputs = { self, nixpkgs, treefmt-nix, rust-overlay, flake-utils, crane }:
    flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system:
      let
        overlays = [ (import rust-overlay) ];
        pkgs = import nixpkgs {
          inherit overlays system;
        };
        lib = pkgs.lib;
        treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix;
        rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
        craneLib = (crane.mkLib pkgs).overrideToolchain rust;
        src = ./.;
        cargoArtifacts = craneLib.buildDepsOnly {
          inherit src;
          buildInputs = bevyengine-dependencies;
          nativeBuildInputs = [ pkgs.pkg-config ];
        };
        kosu = craneLib.buildPackage {
          inherit src cargoArtifacts;
          buildInputs = bevyengine-dependencies;
          nativeBuildInputs = [ pkgs.pkg-config ];
          strictDeps = true;

          doCheck = true;
        };
        cargo-clippy = craneLib.cargoClippy {
          inherit cargoArtifacts src;
          buildInputs = bevyengine-dependencies;
          nativeBuildInputs = [ pkgs.pkg-config ];
          cargoClippyExtraArgs = "--verbose -- --deny warnings";
        };
        cargo-doc = craneLib.cargoDoc {
          inherit cargoArtifacts src;
          buildInputs = bevyengine-dependencies;
          nativeBuildInputs = [ pkgs.pkg-config ];
        };
        bevyengine-dependencies = with pkgs; [
          udev
          alsa-lib
          vulkan-loader

          # To use the x11 feature
          xorg.libX11
          xorg.libXcursor
          xorg.libXi
          xorg.libXrandr

          # To use the wayland feature
          libxkbcommon
          wayland
        ];
        llvm-cov-text = craneLib.cargoLlvmCov {
          inherit cargoArtifacts src;
          buildInputs = bevyengine-dependencies;
          nativeBuildInputs = [ pkgs.pkg-config ];
          cargoExtraArgs = "--locked";
          cargoLlvmCovCommand = "test";
          cargoLlvmCovExtraArgs = "--text --output-dir $out";
        };
        llvm-cov = craneLib.cargoLlvmCov {
          inherit cargoArtifacts src;
          buildInputs = bevyengine-dependencies;
          nativeBuildInputs = [ pkgs.pkg-config ];
          cargoExtraArgs = "--locked";
          cargoLlvmCovCommand = "test";
          cargoLlvmCovExtraArgs = "--html --output-dir $out";
        };
      in
      {
        formatter = treefmtEval.config.build.wrapper;

        packages.default = kosu;
        packages.doc = cargo-doc;
        packages.llvm-cov = llvm-cov;
        packages.llvm-cov-text = llvm-cov-text;

        apps.default = flake-utils.lib.mkApp {
          drv = self.packages.${system}.default;
        };

        checks = {
          inherit kosu cargo-clippy cargo-doc llvm-cov llvm-cov-text;
          formatting = treefmtEval.config.build.check self;
        };

        devShells.default = pkgs.mkShell rec {
          buildInputs = bevyengine-dependencies ++ [
            rust
          ];

          nativeBuildInputs = [
            pkgs.pkg-config
          ];

          LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;

          shellHook = ''
            export PS1="\n[nix-shell:\w]$ "
          '';
        };
      });
}

以上のflake.nixの解説

flake.nixというモノは、パッケージのビルド式(正確にはDerivationと言う)を宣言出来たり、チェックを作成して走らせたり、開発環境を整えたりできる代物である。他にもフォーマッタを指定して、nix fmtと打つだけでプロジェクト全体のフォーマットを走らせたり、nix-overlayを作成する事だって出来る。

今回作成した部分は、let - in文の部分に書かれている。llvm-covllvm-cov-textという名前で宣言されている。ここで使用されている関数、cargoLlvmCovcraneLibを定義しないと使用出来ない点を留意して欲しい。

cargoLlvmCov関数は、ココに定義されている。

以上の式を書くと、nix build .#llvm-covとCLI上で打つとHTML形式のテストカバレッジの結果がビルドされる。nix build .#llvm-cov-textと打つと、packages.llvm-cov-textllvm-cov-textの方の定義が紐付けられているため、テキスト形式のテストカバレッジの結果がビルドされる事となる。

この時点での注意点は、cargoLlvmCovbuildInputsnativeBuildInputsを引数に含める事が可能であるという点だ。これらを含めないと、カバレッジの生成が失敗してしまうケースがある(例えると、pkg-confignativeBuildInputsに使用しているケースなど)。

GitHub ActionsでのCI

Nixを書くという事は、手元でのビルドのみを書いている訳ではない。せっかくビルドの再現性が担保されているというのに、CIで使用しないという事は、宝の持ち腐れである(言いすぎかも…?)。そこで、今回はGitHub Actionsでcargo-llvm-covの結果を、Nixでビルドしてみようではないか。

今回書いたWorkflow

name: nix-checker
on:
  push:
permissions: {}
jobs:
  run-nix-check:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
      - name: Install Nix
        uses: cachix/install-nix-action@v26
        with:
          nix_path: nixpkgs=channel:nixos-unstable
      - name: Run nix flake check
        run: nix flake check --all-systems

  run-nix-build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
      - name: Install Nix
        uses: cachix/install-nix-action@v26
        with:
          nix_path: nixpkgs=channel:nixos-unstable
      - name: Run nix-build with flakes
        run: nix build .#default

  run-nix-build-doc:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
      - name: Install Nix
        uses: cachix/install-nix-action@v26
        with:
          nix_path: nixpkgs=channel:nixos-unstable
      - name: Run nix-build with flakes
        run: nix build .#doc

  build-cargo-llvm-cov-text:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
      - name: Install Nix
        uses: cachix/install-nix-action@v26
        with:
          nix_path: nixpkgs=channel:nixos-unstable
      - name: Run nix-build with flakes
        run: nix build .#llvm-cov-text
      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage-text
          path: result

  build-cargo-llvm-cov:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
      - name: Install Nix
        uses: cachix/install-nix-action@v26
        with:
          nix_path: nixpkgs=channel:nixos-unstable
      - name: Run nix-build with flakes
        run: nix build .#llvm-cov
      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage-html
          path: result

Workflowの解説

まずは、run-nix-*たちを解説する。ひとまず、これらの名前を列挙する。

  • run-nix-check
  • run-nix-build
  • run-nix-build-doc

これらのWorkflowでやっている事は単純で、単にUbuntu上にNixをインストールして、nix build .#defaultnix build .#docを実行しているだけなのである。ちなみにドキュメントのホスティングはここでは行なっていない。これをすると何が嬉しいかというと、以下の点が挙げられる。

  1. 手元でも同様にビルドが出来る点
  2. 手元でビルドしても同様の結果になる点
  3. Workflowが単純になり、読みやすくなる点

次に、build-cargo-llvm-cov*たちを解説する。といっても、先程話した点とあまり変わらない。単にUbuntu上にNixをインストールして、nix build .#llvm-covnix build .#llvm-cov-textを実行しているだけである。こちらでは、ビルドの成果物をアーティファクトとしてアップロードしている。

注意点

私の感覚ではあるため、はっきりとした事は言えないが、このCIの実行方法には、ある弱点がある。それは、ビルド時間が長くなるという点だ(但し、Nixありと無しとで正確に時間をはかった事はない…)。そのために、プライベートリポジトリだったりGitLab CIを使用すると、時間制限に悩まされる。もしもプライベートリポジトリやGitLab CIを使用している際には、別のビルド方法やビルド時間軽減を考えた方が良いかもしれない。

GitHubで編集を提案

Discussion