Skip to content

Nix-打包Tauri应用

约 1412 字大约 5 分钟

LinuxNixFlakeTauri

2025-02-03

Tauri v2发布也有一段时间了,笔者曾多次想在NixOS上复现基于它的开发环境,但苦于不知道其依赖组成(虽然可以通过一些手段获取,但我懒,真的),所以一直没有动手。

最近正好有了时间,同时也看到Tauri官网NixOS编写了一份依赖清单,于是便有了这篇文章。

注意事项

本文涉及到的项目使用yarn 4.4.1rustc 1.84.1编译。

首先,你得创建项目不是?

要快速创建项目,可以使用nix run或者nix shell:

# 进入含有 yarn 的 nix shell
nix shell nixpkgs#corepack

# 创建项目
yarn create tauri-app

创建完成后,进入项目目录,我们需要修改yarn版本:

corepack use yarn@4.4.1   # 使用 yarn 4.4.1

以及,编辑.yarnrc.yml,加入以下内容:

nodeLinker: node-modules

笔者自己没有搞明白如何在vite项目中使用PnP,所以这里直接使用node-modules

打包第一步,前端产物

我们首先要编写前端产物的构建逻辑,这里推荐yarn-plugin-nixify

这个插件在每次运行yarn时,都会生成一份yarn-project.nix文件,该文件定义了一个yarn项目的打包器,可以用于nix构建。

我们运行:

yarn plugin import https://raw.githubusercontent.com/stephank/yarn-plugin-nixify/main/dist/yarn-plugin-nixify.js

第一次运行时,它会生成一份default.nix文件,但我们不使用这份,而是把它写成nix的包函数:

{
  callPackage,
  nodejs_23,
}:
let
  pageTarget =
    (callPackage ./yarn-project.nix {
      nodejs = nodejs_23;
    } { src = ./.; }).overrideAttrs
      (old: {
        buildPhase = ''
          runHook preBuild

          yarn build

          runHook postBuild
        '';
      });
in
pageTarget

这个包函数调用了yarn-project.nix,因为这个文件并不包含默认的buildPhase,所以我们要手动添加buildPhase,这里我们直接调用yarn build

接下来我们编写flake.nix文件:

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    flake-parts.url = "github:hercules-ci/flake-parts";
  };

  outputs =
    {
      flake-parts,
      nixpkgs,
      ...
    }@inputs:
    flake-parts.lib.mkFlake { inherit inputs; } {
      systems = [
        "x86_64-linux"
        "aarch64-linux"
        "x86_64-darwin"
        "aarch64-darwin"
      ];

      perSystem =
        { pkgs, ... }:
        let
          target = pkgs.callPackage ./default.nix { };
        in
        {
          packages = {
            default = target;
          };

          devShells = {
            default = pkgs.mkShell {
              inputsFrom = [ target ];

              packages = with pkgs; [
                nixd
                nixfmt-rfc-style
              ];
            };
          };
        };
    };
}

我们使用flake-parts来编写flake.nix,在devShell中引用我们刚刚写好的产物。

运行nix build .,我们可以看到在result/libexec/<项目名>/dist目录下生成了前端产物。

Rust编译

Rust侧的工作稍微有一些复杂,按照惯例,我们先引用rust-overlay作为rustccargo的来源:

{
  inputs = {
    # ...
    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

    outputs =
    {
      flake-parts,
      rust-overlay,
      nixpkgs,
      ...
    }@inputs:
    flake-parts.lib.mkFlake { inherit inputs; } {
      # ...

      perSystem =
        { system, ... }:
        let
          pkgs = import nixpkgs {
            inherit system;
            overlays = [ (import rust-overlay) ];
          };
          rsPkgDev = pkgs.rust-bin.stable.latest.default.override {
            extensions = [
              "rust-analyzer"
              "rust-src"
            ];
          };

          # ...
        in
        {
          # ...
        };
    };
}

然后修改default.nix文件,添加依赖项:

{
  callPackage,

  pkg-config,               # Tauri nativeBuildInputs
  gobject-introspection,
  cargo-tauri,

  at-spi2-atk,              # Tauri buildInputs
  atkmm,
  cairo,
  gdk-pixbuf,
  glib,
  gtk3,
  harfbuzz,
  librsvg,
  libsoup_3,
  pango,
  webkitgtk_4_1,
  openssl,
  nodejs_23,

  rust-bin,                 # Rust
  rsPkg ? rust-bin.stable.latest.default,
  makeRustPlatform,
}:
let
  rustPlatform = makeRustPlatform {
    cargo = rsPkg;
    rustc = rsPkg;
  };

  # pageTarget 同上 ...

  tauriTarget = rustPlatform.buildRustPackage {
    pname = "<项目名>";
    version = "<版本号>";
    src = ./src-tauri;

    doCheck = false;

    cargoLock = {
      lockFile = ./src-tauri/Cargo.lock;
      allowBuiltinFetchGit = true;
    };

    unpackPhase = ''
      runHook preUnpack

      mkdir dist
      cp -r ${pageTarget}/libexec/<项目名>/dist/* dist
      cp -r $src/* ./

      runHook postUnpack
    '';

    buildPhase = ''
      cargo-tauri build --no-bundle --config '{ "build": { "beforeBuildCommand": "", "frontendDist": "./dist" } }'
    '';

    installPhase = ''
      mkdir -p $out/bin
      cp target/release/<项目名> $out/bin
    '';

    checkPhase = '''';

    nativeBuildInputs = [
      pkg-config
      gobject-introspection
      rsPkg
      cargo-tauri
    ];

    buildInputs = [
      at-spi2-atk
      atkmm
      cairo
      gdk-pixbuf
      glib
      gtk3
      harfbuzz
      librsvg
      libsoup_3
      pango
      webkitgtk_4_1
      openssl
    ] ++ pageTarget.buildInputs;  # 把前端依赖也加进来,主要是为了方便构建devShell
  };
in
tauriTarget

我们一步步拆解该文件:

tauriTarget的构建中,我们首先要关注的是nativeBuildInputsbuildInputs,这部分基本来自Tauri Prerequisites,我们直接复制过来即可。

记得把他给的cargo删了

然后是我们重写的几个phase,在unpackPhase中,我们需要把前端产物复制到目前的构建目录里,因此重写为如下脚本:

runHook preUnpack

mkdir dist
cp -r ${pageTarget}/libexec/<项目名>/dist/* dist
cp -r $src/* ./

runHook postUnpack

因为我们覆盖了unpackPhase,所以源代码的迁移不会默认执行,我们需要手动复制

因为打包tauri项目时,我们的根目录是src-tauri,因此原先在tauri.conf.json中配置的内容有一部分是需要修改的,我们使用cargo-tauri来混入:

cargo-tauri build --no-bundle --config '{ "build": { "beforeBuildCommand": "", "frontendDist": "./dist" } }'

我们移除beforeBuildCommand,因为这部分我们已经手动执行了,并把frontendDist指向我们刚刚复制的前端产物。

值得注意的是我们只需要构建二进制,所以传入--no-bundle,禁止cargo-tauri打包deb等文件。

由于buildRustPackage的默认phase依赖固定的构建路径,我们修改了buildPhase,因此我们还需要重写installPhasecheckPhase,我们只需要复制二进制文件到指定位置即可,因此我们重写为如下脚本:

mkdir -p $out/bin
cp target/release/<项目名> $out/bin

写完打包逻辑后,如果直接使用会出依赖问题(找不到rust-tls),我们需要修改cargo依赖:

tauri = { version = "2", features = ["native-tls"] }

tauri开启native-tls特性,这样就可以正常打包了,至此我们的前后端打包均已配置完成。

DevShell

最后我们来构建devShell,因为我们的软件包已经编写完成,我们只要把它加入到devShell即可,我们修改flake.nix文件中的devShells部分:

devShells = {
  default = pkgs.mkShell {
    inputsFrom = [ (target.override { rsPkg = rsPkgDev; }) ];

    packages = with pkgs; [
        nixd
        nixfmt-rfc-style
    ];
  };
};

我们在编写软件包时故意留下了rsPkg这个变量,它可以指定我们使用的rust工具链,为了正确启动语言服务器,我们需要开启rust-srcrust-analyzer特性,将我们重写的工具链赋值给rsPkg即可。

现在,我们就可以使用nix develop进入开发环境了。

Enjoy 😃