Improve Nix package and shell (#21075)

Stanislav Alekseev created

With an addition of useFetchCargoVendor, crane becomes less necessary
for our use. This reuses the package from nixpkgs as well as creating a
better devshell that both work on macOS.

I use Xcode's SDKROOT and DEVELOPER_DIR to point the swift in the
livekit client crate to a correct sdk when using a devshell. Devshell
should work without that once apple releases sources for the 15.1 SDK
but for now this is an easy fix

This also replaces fenix with rust-overlay because of issues with the
out-of-sandbox access I've noticed fenix installed toolchains have

Release Notes:

- N/A

Change summary

.envrc        |   2 
flake.lock    |  70 +++--------
flake.nix     |  83 +++++++------
nix/build.nix | 313 ++++++++++++++++++++++++++++++++++++----------------
nix/shell.nix | 100 ++++++++-------
5 files changed, 328 insertions(+), 240 deletions(-)

Detailed changes

.envrc 🔗

@@ -0,0 +1,2 @@
+watch_file nix/shell.nix
+use flake

flake.lock 🔗

@@ -1,41 +1,5 @@
 {
   "nodes": {
-    "crane": {
-      "locked": {
-        "lastModified": 1727060013,
-        "narHash": "sha256-/fC5YlJy4IoAW9GhkJiwyzk0K/gQd9Qi4rRcoweyG9E=",
-        "owner": "ipetkov",
-        "repo": "crane",
-        "rev": "6b40cc876c929bfe1e3a24bf538ce3b5622646ba",
-        "type": "github"
-      },
-      "original": {
-        "owner": "ipetkov",
-        "repo": "crane",
-        "type": "github"
-      }
-    },
-    "fenix": {
-      "inputs": {
-        "nixpkgs": [
-          "nixpkgs"
-        ],
-        "rust-analyzer-src": "rust-analyzer-src"
-      },
-      "locked": {
-        "lastModified": 1727073227,
-        "narHash": "sha256-1kmkEQmFfGVuPBasqSZrNThqyMDV1SzTalQdRZxtDRs=",
-        "owner": "nix-community",
-        "repo": "fenix",
-        "rev": "88cc292eb3c689073c784d6aecc0edbd47e12881",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-community",
-        "repo": "fenix",
-        "type": "github"
-      }
-    },
     "flake-compat": {
       "locked": {
         "lastModified": 1696426674,
@@ -53,11 +17,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1726937504,
-        "narHash": "sha256-bvGoiQBvponpZh8ClUcmJ6QnsNKw0EMrCQJARK3bI1c=",
+        "lastModified": 1732014248,
+        "narHash": "sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "9357f4f23713673f310988025d9dc261c20e70c6",
+        "rev": "23e89b7da85c3640bbc2173fe04f4bd114342367",
         "type": "github"
       },
       "original": {
@@ -69,26 +33,28 @@
     },
     "root": {
       "inputs": {
-        "crane": "crane",
-        "fenix": "fenix",
         "flake-compat": "flake-compat",
-        "nixpkgs": "nixpkgs"
+        "nixpkgs": "nixpkgs",
+        "rust-overlay": "rust-overlay"
       }
     },
-    "rust-analyzer-src": {
-      "flake": false,
+    "rust-overlay": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
       "locked": {
-        "lastModified": 1726443025,
-        "narHash": "sha256-nCmG4NJpwI0IoIlYlwtDwVA49yuspA2E6OhfCOmiArQ=",
-        "owner": "rust-lang",
-        "repo": "rust-analyzer",
-        "rev": "94b526fc86eaa0e90fb4d54a5ba6313aa1e9b269",
+        "lastModified": 1732242723,
+        "narHash": "sha256-NWI8csIK0ujFlFuEXKnoc+7hWoCiEtINK9r48LUUMeU=",
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "rev": "a229311fcb45b88a95fdfa5cecd8349c809a272a",
         "type": "github"
       },
       "original": {
-        "owner": "rust-lang",
-        "ref": "nightly",
-        "repo": "rust-analyzer",
+        "owner": "oxalica",
+        "repo": "rust-overlay",
         "type": "github"
       }
     }

flake.nix 🔗

@@ -3,60 +3,61 @@
 
   inputs = {
     nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
-    fenix = {
-      url = "github:nix-community/fenix";
+    rust-overlay = {
+      url = "github:oxalica/rust-overlay";
       inputs.nixpkgs.follows = "nixpkgs";
     };
-    crane.url = "github:ipetkov/crane";
     flake-compat.url = "github:edolstra/flake-compat";
   };
 
-  outputs = {
-    nixpkgs,
-    crane,
-    fenix,
-    ...
-  }: let
-    systems = ["x86_64-linux" "aarch64-linux"];
-
-    overlays = {
-      fenix = fenix.overlays.default;
-      rust-toolchain = final: prev: {
-        rustToolchain = final.fenix.stable.toolchain;
-      };
-      zed-editor = final: prev: {
-        zed-editor = final.callPackage ./nix/build.nix {
-          craneLib = (crane.mkLib final).overrideToolchain final.rustToolchain;
-          rustPlatform = final.makeRustPlatform {
-            inherit (final.rustToolchain) cargo rustc;
+  outputs =
+    { nixpkgs, rust-overlay, ... }:
+    let
+      systems = [
+        "x86_64-linux"
+        "x86_64-darwin"
+        "aarch64-linux"
+        "aarch64-darwin"
+      ];
+
+      overlays = {
+        rust-overlay = rust-overlay.overlays.default;
+        rust-toolchain = final: prev: {
+          rustToolchain = final.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
+        };
+        zed-editor = final: prev: {
+          zed-editor = final.callPackage ./nix/build.nix {
+            rustPlatform = final.makeRustPlatform {
+              cargo = final.rustToolchain;
+              rustc = final.rustToolchain;
+            };
           };
         };
       };
-    };
 
-    mkPkgs = system:
-      import nixpkgs {
-        inherit system;
-        overlays = builtins.attrValues overlays;
-      };
+      mkPkgs =
+        system:
+        import nixpkgs {
+          inherit system;
+          overlays = builtins.attrValues overlays;
+        };
 
-    forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f (mkPkgs system));
-  in {
-    packages = forAllSystems (pkgs: {
-      zed-editor = pkgs.zed-editor;
-      default = pkgs.zed-editor;
-    });
+      forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f (mkPkgs system));
+    in
+    {
+      packages = forAllSystems (pkgs: {
+        zed-editor = pkgs.zed-editor;
+        default = pkgs.zed-editor;
+      });
 
-    devShells = forAllSystems (pkgs: {
-      default = import ./nix/shell.nix {inherit pkgs;};
-    });
+      devShells = forAllSystems (pkgs: {
+        default = import ./nix/shell.nix { inherit pkgs; };
+      });
 
-    formatter = forAllSystems (pkgs: pkgs.alejandra);
+      formatter = forAllSystems (pkgs: pkgs.nixfmt-rfc-style);
 
-    overlays =
-      overlays
-      // {
+      overlays = overlays // {
         default = nixpkgs.lib.composeManyExtensions (builtins.attrValues overlays);
       };
-  };
+    };
 }

nix/build.nix 🔗

@@ -1,10 +1,9 @@
 {
   lib,
-  craneLib,
   rustPlatform,
+  fetchpatch,
   clang,
-  llvmPackages_18,
-  mold-wrapped,
+  cmake,
   copyDesktopItems,
   curl,
   perl,
@@ -22,122 +21,236 @@
   wayland,
   libglvnd,
   xorg,
+  stdenv,
   makeFontsConf,
   vulkan-loader,
   envsubst,
-  stdenvAdapters,
+  cargo-about,
+  versionCheckHook,
+  cargo-bundle,
+  git,
+  apple-sdk_15,
+  darwinMinVersionHook,
+  makeWrapper,
+  nodejs_22,
   nix-gitignore,
+
   withGLES ? false,
-  cmake,
-}: let
-  includeFilter = path: type: let
-    baseName = baseNameOf (toString path);
-    parentDir = dirOf path;
-    inRootDir = type == "directory" && parentDir == ../.;
-  in
-    !(inRootDir && (baseName == "docs" || baseName == ".github" || baseName == "script" || baseName == ".git" || baseName == "target"));
+}:
+
+assert withGLES -> stdenv.hostPlatform.isLinux;
+
+let
+  includeFilter =
+    path: type:
+    let
+      baseName = baseNameOf (toString path);
+      parentDir = dirOf path;
+      inRootDir = type == "directory" && parentDir == ../.;
+    in
+    !(
+      inRootDir
+      && (
+        baseName == "docs"
+        || baseName == ".github"
+        || baseName == "script"
+        || baseName == ".git"
+        || baseName == "target"
+      )
+    );
+in
+rustPlatform.buildRustPackage rec {
+  pname = "zed-editor";
+  version = "nightly";
 
   src = lib.cleanSourceWith {
-    src = nix-gitignore.gitignoreSource [] ../.;
+    src = nix-gitignore.gitignoreSource [ ] ../.;
     filter = includeFilter;
     name = "source";
   };
 
-  stdenv = stdenvAdapters.useMoldLinker llvmPackages_18.stdenv;
-
-  commonArgs =
-    craneLib.crateNameFromCargoToml {cargoToml = ../crates/zed/Cargo.toml;}
-    // {
-      inherit src stdenv;
-
-      nativeBuildInputs = [
-        clang
-        copyDesktopItems
-        curl
-        mold-wrapped
-        perl
-        pkg-config
-        protobuf
-        rustPlatform.bindgenHook
-        cmake
-      ];
+  patches =
+    [
+      # Zed uses cargo-install to install cargo-about during the script execution.
+      # We provide cargo-about ourselves and can skip this step.
+      # Until https://github.com/zed-industries/zed/issues/19971 is fixed,
+      # we also skip any crate for which the license cannot be determined.
+      (fetchpatch {
+        url = "https://raw.githubusercontent.com/NixOS/nixpkgs/1fd02d90c6c097f91349df35da62d36c19359ba7/pkgs/by-name/ze/zed-editor/0001-generate-licenses.patch";
+        hash = "sha256-cLgqLDXW1JtQ2OQFLd5UolAjfy7bMoTw40lEx2jA2pk=";
+      })
+    ]
+    ++ lib.optionals stdenv.hostPlatform.isDarwin [
+      # Livekit requires Swift 6
+      # We need this until livekit-rust sdk is used
+      (fetchpatch {
+        url = "https://raw.githubusercontent.com/NixOS/nixpkgs/1fd02d90c6c097f91349df35da62d36c19359ba7/pkgs/by-name/ze/zed-editor/0002-disable-livekit-darwin.patch";
+        hash = "sha256-whZ7RaXv8hrVzWAveU3qiBnZSrvGNEHTuyNhxgMIo5w=";
+      })
+    ];
 
-      buildInputs = [
-        curl
-        fontconfig
-        freetype
-        libgit2
-        openssl
-        sqlite
-        zlib
-        zstd
-
-        alsa-lib
-        libxkbcommon
-        wayland
-        xorg.libxcb
-      ];
+  useFetchCargoVendor = true;
+  cargoHash = "sha256-xL/EBe3+rlaPwU2zZyQtsZNHGBjzAD8ZCWrQXCQVxm8=";
+
+  nativeBuildInputs =
+    [
+      clang
+      cmake
+      copyDesktopItems
+      curl
+      perl
+      pkg-config
+      protobuf
+      rustPlatform.bindgenHook
+      cargo-about
+    ]
+    ++ lib.optionals stdenv.hostPlatform.isLinux [ makeWrapper ]
+    ++ lib.optionals stdenv.hostPlatform.isDarwin [ cargo-bundle ];
 
-      ZSTD_SYS_USE_PKG_CONFIG = true;
-      FONTCONFIG_FILE = makeFontsConf {
-        fontDirectories = [
-          "../assets/fonts/zed-mono"
-          "../assets/fonts/zed-sans"
-        ];
-      };
-      ZED_UPDATE_EXPLANATION = "zed has been installed using nix. Auto-updates have thus been disabled.";
+  dontUseCmakeConfigure = true;
+
+  buildInputs =
+    [
+      curl
+      fontconfig
+      freetype
+      libgit2
+      openssl
+      sqlite
+      zlib
+      zstd
+    ]
+    ++ lib.optionals stdenv.hostPlatform.isLinux [
+      alsa-lib
+      libxkbcommon
+      wayland
+      xorg.libxcb
+    ]
+    ++ lib.optionals stdenv.hostPlatform.isDarwin [
+      apple-sdk_15
+      (darwinMinVersionHook "10.15")
+    ];
+
+  cargoBuildFlags = [
+    "--package=zed"
+    "--package=cli"
+  ];
+
+  buildFeatures = lib.optionals stdenv.hostPlatform.isDarwin [ "gpui/runtime_shaders" ];
+
+  env = {
+    ZSTD_SYS_USE_PKG_CONFIG = true;
+    FONTCONFIG_FILE = makeFontsConf {
+      fontDirectories = [
+        "${src}/assets/fonts/plex-mono"
+        "${src}/assets/fonts/plex-sans"
+      ];
     };
+    ZED_UPDATE_EXPLANATION = "Zed has been installed using Nix. Auto-updates have thus been disabled.";
+    RELEASE_VERSION = version;
+  };
 
-  cargoArtifacts = craneLib.buildDepsOnly commonArgs;
+  RUSTFLAGS = if withGLES then "--cfg gles" else "";
+  gpu-lib = if withGLES then libglvnd else vulkan-loader;
 
-  gpu-lib =
-    if withGLES
-    then libglvnd
-    else vulkan-loader;
+  preBuild = ''
+    bash script/generate-licenses
+  '';
 
-  zed = craneLib.buildPackage (commonArgs
-    // {
-      inherit cargoArtifacts;
-      cargoExtraArgs = "--package=zed --package=cli";
-      buildFeatures = ["gpui/runtime_shaders"];
-      doCheck = false;
+  postFixup = lib.optionalString stdenv.hostPlatform.isLinux ''
+    patchelf --add-rpath ${gpu-lib}/lib $out/libexec/*
+    patchelf --add-rpath ${wayland}/lib $out/libexec/*
+    wrapProgram $out/libexec/zed-editor --suffix PATH : ${lib.makeBinPath [ nodejs_22 ]}
+  '';
 
-      RUSTFLAGS =
-        if withGLES
-        then "--cfg gles"
-        else "";
+  preCheck = ''
+    export HOME=$(mktemp -d);
+  '';
 
-      postFixup = ''
-        patchelf --add-rpath ${gpu-lib}/lib $out/libexec/*
-        patchelf --add-rpath ${wayland}/lib $out/libexec/*
-      '';
+  checkFlags =
+    [
+      # Flaky: unreliably fails on certain hosts (including Hydra)
+      "--skip=zed::tests::test_window_edit_state_restoring_enabled"
+    ]
+    ++ lib.optionals stdenv.hostPlatform.isLinux [
+      # Fails on certain hosts (including Hydra) for unclear reason
+      "--skip=test_open_paths_action"
+    ];
+
+  installPhase =
+    if stdenv.hostPlatform.isDarwin then
+      ''
+        runHook preInstall
+
+        # cargo-bundle expects the binary in target/release
+        mv target/${stdenv.hostPlatform.rust.cargoShortTarget}/release/zed target/release/zed
+
+        pushd crates/zed
+
+        # Note that this is GNU sed, while Zed's bundle-mac uses BSD sed
+        sed -i "s/package.metadata.bundle-stable/package.metadata.bundle/" Cargo.toml
+        export CARGO_BUNDLE_SKIP_BUILD=true
+        app_path=$(cargo bundle --release | xargs)
+
+        # We're not using the fork of cargo-bundle, so we must manually append plist extensions
+        # Remove closing tags from Info.plist (last two lines)
+        head -n -2 $app_path/Contents/Info.plist > Info.plist
+        # Append extensions
+        cat resources/info/*.plist >> Info.plist
+        # Add closing tags
+        printf "</dict>\n</plist>\n" >> Info.plist
+        mv Info.plist $app_path/Contents/Info.plist
+
+        popd
+
+        mkdir -p $out/Applications $out/bin
+        # Zed expects git next to its own binary
+        ln -s ${git}/bin/git $app_path/Contents/MacOS/git
+        mv target/${stdenv.hostPlatform.rust.cargoShortTarget}/release/cli $app_path/Contents/MacOS/cli
+        mv $app_path $out/Applications/
+
+        # Physical location of the CLI must be inside the app bundle as this is used
+        # to determine which app to start
+        ln -s $out/Applications/Zed.app/Contents/MacOS/cli $out/bin/zed
+
+        runHook postInstall
+      ''
+    else
+      ''
+        runHook preInstall
 
-      postInstall = ''
         mkdir -p $out/bin $out/libexec
-        mv $out/bin/zed $out/libexec/zed-editor
-        mv $out/bin/cli $out/bin/zed
-
-        install -D crates/zed/resources/app-icon@2x.png $out/share/icons/hicolor/1024x1024@2x/apps/zed.png
-        install -D crates/zed/resources/app-icon.png $out/share/icons/hicolor/512x512/apps/zed.png
-
-        export DO_STARTUP_NOTIFY="true"
-        export APP_CLI="zed"
-        export APP_ICON="zed"
-        export APP_NAME="Zed"
-        export APP_ARGS="%U"
-        mkdir -p "$out/share/applications"
-        ${lib.getExe envsubst} < "crates/zed/resources/zed.desktop.in" > "$out/share/applications/dev.zed.Zed.desktop"
+        cp target/${stdenv.hostPlatform.rust.cargoShortTarget}/release/zed $out/libexec/zed-editor
+        cp target/${stdenv.hostPlatform.rust.cargoShortTarget}/release/cli $out/bin/zed
+
+        install -D ${src}/crates/zed/resources/app-icon@2x.png $out/share/icons/hicolor/1024x1024@2x/apps/zed.png
+        install -D ${src}/crates/zed/resources/app-icon.png $out/share/icons/hicolor/512x512/apps/zed.png
+
+        # extracted from https://github.com/zed-industries/zed/blob/v0.141.2/script/bundle-linux (envsubst)
+        # and https://github.com/zed-industries/zed/blob/v0.141.2/script/install.sh (final desktop file name)
+        (
+          export DO_STARTUP_NOTIFY="true"
+          export APP_CLI="zed"
+          export APP_ICON="zed"
+          export APP_NAME="Zed"
+          export APP_ARGS="%U"
+          mkdir -p "$out/share/applications"
+          ${lib.getExe envsubst} < "crates/zed/resources/zed.desktop.in" > "$out/share/applications/dev.zed.Zed.desktop"
+        )
+
+        runHook postInstall
       '';
-    });
-in
-  zed
-  // {
-    meta = with lib; {
-      description = "High-performance, multiplayer code editor from the creators of Atom and Tree-sitter";
-      homepage = "https://zed.dev";
-      changelog = "https://zed.dev/releases/preview";
-      license = licenses.gpl3Only;
-      mainProgram = "zed";
-      platforms = platforms.linux;
-    };
-  }
+
+  nativeInstallCheckInputs = [
+    versionCheckHook
+  ];
+
+  meta = {
+    description = "High-performance, multiplayer code editor from the creators of Atom and Tree-sitter";
+    homepage = "https://zed.dev";
+    changelog = "https://zed.dev/releases/preview";
+    license = lib.licenses.gpl3Only;
+    mainProgram = "zed";
+    platforms = lib.platforms.linux ++ lib.platforms.darwin;
+  };
+}

nix/shell.nix 🔗

@@ -1,51 +1,57 @@
-{pkgs ? import <nixpkgs> {}}: let
-  stdenv = pkgs.stdenvAdapters.useMoldLinker pkgs.llvmPackages_18.stdenv;
+{
+  pkgs ? import <nixpkgs> { },
+}:
+let
+  inherit (pkgs) lib;
 in
-  if pkgs.stdenv.isDarwin
-  then
-    # See https://github.com/NixOS/nixpkgs/issues/320084
-    throw "zed: nix dev-shell isn't supported on darwin yet."
-  else let
-    buildInputs = with pkgs; [
-      curl
-      fontconfig
-      freetype
-      libgit2
-      openssl
-      sqlite
-      zlib
-      zstd
-      alsa-lib
-      libxkbcommon
-      wayland
-      xorg.libxcb
-      vulkan-loader
-      rustToolchain
-    ];
-  in
-    pkgs.mkShell.override {inherit stdenv;} {
-      nativeBuildInputs = with pkgs; [
-        clang
-        curl
-        cmake
-        perl
-        pkg-config
-        protobuf
-        rustPlatform.bindgenHook
-      ];
+pkgs.mkShell rec {
+  packages = [
+    pkgs.clang
+    pkgs.curl
+    pkgs.cmake
+    pkgs.perl
+    pkgs.pkg-config
+    pkgs.protobuf
+    pkgs.rustPlatform.bindgenHook
+    pkgs.rust-analyzer
+  ];
 
-      inherit buildInputs;
+  buildInputs =
+    [
+      pkgs.curl
+      pkgs.fontconfig
+      pkgs.freetype
+      pkgs.libgit2
+      pkgs.openssl
+      pkgs.sqlite
+      pkgs.zlib
+      pkgs.zstd
+      pkgs.rustToolchain
+    ]
+    ++ lib.optionals pkgs.stdenv.hostPlatform.isLinux [
+      pkgs.alsa-lib
+      pkgs.libxkbcommon
+    ]
+    ++ lib.optional pkgs.stdenv.hostPlatform.isDarwin pkgs.apple-sdk_15;
 
-      shellHook = ''
-        export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath buildInputs}:$LD_LIBRARY_PATH"
-        export PROTOC="${pkgs.protobuf}/bin/protoc"
-      '';
+  # We set SDKROOT and DEVELOPER_DIR to the Xcode ones instead of the nixpkgs ones,
+  # because we need Swift 6.0 and nixpkgs doesn't have it.
+  # Xcode is required for development anyways
+  shellHook =
+    ''
+      export LD_LIBRARY_PATH="${lib.makeLibraryPath buildInputs}:$LD_LIBRARY_PATH"
+      export PROTOC="${pkgs.protobuf}/bin/protoc"
+    ''
+    + lib.optionalString pkgs.stdenv.hostPlatform.isDarwin ''
+      export SDKROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk";
+      export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer";
+    '';
 
-      FONTCONFIG_FILE = pkgs.makeFontsConf {
-        fontDirectories = [
-          "./assets/fonts/zed-mono"
-          "./assets/fonts/zed-sans"
-        ];
-      };
-      ZSTD_SYS_USE_PKG_CONFIG = true;
-    }
+  FONTCONFIG_FILE = pkgs.makeFontsConf {
+    fontDirectories = [
+      "./assets/fonts/zed-mono"
+      "./assets/fonts/zed-sans"
+    ];
+  };
+  ZSTD_SYS_USE_PKG_CONFIG = true;
+}