build.nix

  1{
  2  lib,
  3  stdenv,
  4
  5  apple-sdk_15,
  6  darwin,
  7  darwinMinVersionHook,
  8
  9  cargo-about,
 10  cargo-bundle,
 11  crane,
 12  rustPlatform,
 13  rustToolchain,
 14
 15  copyDesktopItems,
 16  envsubst,
 17  fetchFromGitHub,
 18  makeFontsConf,
 19  makeWrapper,
 20
 21  alsa-lib,
 22  cmake,
 23  curl,
 24  fontconfig,
 25  freetype,
 26  git,
 27  libgit2,
 28  libglvnd,
 29  libxkbcommon,
 30  livekit-libwebrtc,
 31  nodejs_22,
 32  openssl,
 33  perl,
 34  pkg-config,
 35  protobuf,
 36  sqlite,
 37  vulkan-loader,
 38  wayland,
 39  xorg,
 40  zlib,
 41  zstd,
 42
 43  withGLES ? false,
 44  profile ? "release",
 45}:
 46assert withGLES -> stdenv.hostPlatform.isLinux;
 47let
 48  mkIncludeFilter =
 49    root': path: type:
 50    let
 51      # note: under lazy-trees this introduces an extra copy
 52      root = toString root' + "/";
 53      relPath = lib.removePrefix root path;
 54      topLevelIncludes = [
 55        "crates"
 56        "assets"
 57        "extensions"
 58        "script"
 59        "tooling"
 60        "Cargo.toml"
 61        ".config" # nextest?
 62        ".cargo"
 63      ];
 64      firstComp = builtins.head (lib.path.subpath.components relPath);
 65    in
 66    builtins.elem firstComp topLevelIncludes;
 67
 68  craneLib = crane.overrideToolchain rustToolchain;
 69  gpu-lib = if withGLES then libglvnd else vulkan-loader;
 70  commonArgs =
 71    let
 72      zedCargoLock = builtins.fromTOML (builtins.readFile ../crates/zed/Cargo.toml);
 73      stdenv' = stdenv;
 74    in
 75    rec {
 76      pname = "zed-editor";
 77      version = zedCargoLock.package.version + "-nightly";
 78      src = builtins.path {
 79        path = ../.;
 80        filter = mkIncludeFilter ../.;
 81        name = "source";
 82      };
 83
 84      cargoLock = ../Cargo.lock;
 85
 86      nativeBuildInputs = [
 87        cmake
 88        copyDesktopItems
 89        curl
 90        perl
 91        pkg-config
 92        protobuf
 93        # Pin cargo-about to 0.8.2. Newer versions don't work with the current license identifiers
 94        # See https://github.com/zed-industries/zed/pull/44012
 95        (cargo-about.overrideAttrs (
 96          new: old: rec {
 97            version = "0.8.2";
 98
 99            src = fetchFromGitHub {
100              owner = "EmbarkStudios";
101              repo = "cargo-about";
102              tag = version;
103              sha256 = "sha256-cNKZpDlfqEXeOE5lmu79AcKOawkPpk4PQCsBzNtIEbs=";
104            };
105
106            cargoHash = "sha256-NnocSs6UkuF/mCM3lIdFk+r51Iz2bHuYzMT/gEbT/nk=";
107
108            # NOTE: can drop once upstream uses `finalAttrs` here:
109            # https://github.com/NixOS/nixpkgs/blob/10214747f5e6e7cb5b9bdf9e018a3c7b3032f5af/pkgs/build-support/rust/build-rust-package/default.nix#L104
110            #
111            # See (for context): https://github.com/NixOS/nixpkgs/pull/382550
112            cargoDeps = rustPlatform.fetchCargoVendor {
113              inherit (new) src;
114              hash = new.cargoHash;
115              patches = new.cargoPatches or [ ];
116              name = new.cargoDepsName or new.finalPackage.name;
117            };
118          }
119        ))
120        rustPlatform.bindgenHook
121      ]
122      ++ lib.optionals stdenv'.hostPlatform.isLinux [ makeWrapper ]
123      ++ lib.optionals stdenv'.hostPlatform.isDarwin [
124        (cargo-bundle.overrideAttrs (
125          new: old: {
126            version = "0.6.1-zed";
127            src = fetchFromGitHub {
128              owner = "zed-industries";
129              repo = "cargo-bundle";
130              rev = "2be2669972dff3ddd4daf89a2cb29d2d06cad7c7";
131              hash = "sha256-cSvW0ND148AGdIGWg/ku0yIacVgW+9f1Nsi+kAQxVrI=";
132            };
133            cargoHash = "sha256-urn+A3yuw2uAO4HGmvQnKvWtHqvG9KHxNCCWTiytE4k=";
134
135            # NOTE: can drop once upstream uses `finalAttrs` here:
136            # https://github.com/NixOS/nixpkgs/blob/10214747f5e6e7cb5b9bdf9e018a3c7b3032f5af/pkgs/build-support/rust/build-rust-package/default.nix#L104
137            #
138            # See (for context): https://github.com/NixOS/nixpkgs/pull/382550
139            cargoDeps = rustPlatform.fetchCargoVendor {
140              inherit (new) src;
141              hash = new.cargoHash;
142              patches = new.cargoPatches or [ ];
143              name = new.cargoDepsName or new.finalPackage.name;
144            };
145          }
146        ))
147      ];
148
149      buildInputs = [
150        curl
151        fontconfig
152        freetype
153        # TODO: need staticlib of this for linking the musl remote server.
154        # should make it a separate derivation/flake output
155        # see https://crane.dev/examples/cross-musl.html
156        libgit2
157        openssl
158        sqlite
159        zlib
160        zstd
161      ]
162      ++ lib.optionals stdenv'.hostPlatform.isLinux [
163        alsa-lib
164        libxkbcommon
165        wayland
166        gpu-lib
167        xorg.libX11
168        xorg.libxcb
169      ]
170      ++ lib.optionals stdenv'.hostPlatform.isDarwin [
171        apple-sdk_15
172        (darwinMinVersionHook "10.15")
173      ];
174
175      cargoExtraArgs = "-p zed -p cli --locked --features=gpui/runtime_shaders";
176
177      stdenv =
178        pkgs:
179        let
180          base = pkgs.llvmPackages.stdenv;
181          addBinTools = old: {
182            cc = old.cc.override {
183              inherit (pkgs.llvmPackages) bintools;
184            };
185          };
186          custom = lib.pipe base [
187            (stdenv: stdenv.override addBinTools)
188            pkgs.stdenvAdapters.useMoldLinker
189          ];
190        in
191        if stdenv'.hostPlatform.isLinux then custom else base;
192
193      env = {
194        ZSTD_SYS_USE_PKG_CONFIG = true;
195        FONTCONFIG_FILE = makeFontsConf {
196          fontDirectories = [
197            ../assets/fonts/lilex
198            ../assets/fonts/ibm-plex-sans
199          ];
200        };
201        ZED_UPDATE_EXPLANATION = "Zed has been installed using Nix. Auto-updates have thus been disabled.";
202        RELEASE_VERSION = version;
203        LK_CUSTOM_WEBRTC = livekit-libwebrtc;
204        PROTOC = "${protobuf}/bin/protoc";
205
206        CARGO_PROFILE = profile;
207        # need to handle some profiles specially https://github.com/rust-lang/cargo/issues/11053
208        TARGET_DIR = "target/" + (if profile == "dev" then "debug" else profile);
209
210        # for some reason these deps being in buildInputs isn't enough, the only thing
211        # about them that's special is that they're manually dlopened at runtime
212        NIX_LDFLAGS = lib.optionalString stdenv'.hostPlatform.isLinux "-rpath ${
213          lib.makeLibraryPath [
214            gpu-lib
215            wayland
216          ]
217        }";
218
219        NIX_OUTPATH_USED_AS_RANDOM_SEED = "norebuilds";
220      };
221
222      # prevent nix from removing the "unused" wayland/gpu-lib rpaths
223      dontPatchELF = stdenv'.hostPlatform.isLinux;
224
225      # TODO: try craneLib.cargoNextest separate output
226      # for now we're not worried about running our test suite (or tests for deps) in the nix sandbox
227      doCheck = false;
228
229      cargoVendorDir = craneLib.vendorCargoDeps {
230        inherit src cargoLock;
231        overrideVendorGitCheckout =
232          let
233            hasWebRtcSys = builtins.any (crate: crate.name == "webrtc-sys");
234            # we can't set $RUSTFLAGS because that clobbers the cargo config
235            # see https://github.com/rust-lang/cargo/issues/5376#issuecomment-2163350032
236            glesConfig = builtins.toFile "config.toml" ''
237              [target.'cfg(all())']
238              rustflags = ["--cfg", "gles"]
239            '';
240
241            # `webrtc-sys` expects a staticlib; nixpkgs' `livekit-webrtc` has been patched to
242            # produce a `dylib`... patching `webrtc-sys`'s build script is the easier option
243            # TODO: send livekit sdk a PR to make this configurable
244            postPatch = ''
245              substituteInPlace webrtc-sys/build.rs --replace-fail \
246                "cargo:rustc-link-lib=static=webrtc" "cargo:rustc-link-lib=dylib=webrtc"
247            ''
248            + lib.optionalString withGLES ''
249              cat ${glesConfig} >> .cargo/config/config.toml
250            '';
251          in
252          crates: drv:
253          if hasWebRtcSys crates then
254            drv.overrideAttrs (o: {
255              postPatch = (o.postPatch or "") + postPatch;
256            })
257          else
258            drv;
259      };
260    };
261  cargoArtifacts = craneLib.buildDepsOnly commonArgs;
262in
263craneLib.buildPackage (
264  lib.recursiveUpdate commonArgs {
265    inherit cargoArtifacts;
266
267    dontUseCmakeConfigure = true;
268
269    # without the env var generate-licenses fails due to crane's fetchCargoVendor, see:
270    # https://github.com/zed-industries/zed/issues/19971#issuecomment-2688455390
271    # TODO: put this in a separate derivation that depends on src to avoid running it on every build
272    preBuild = ''
273      ALLOW_MISSING_LICENSES=yes bash script/generate-licenses
274      echo nightly > crates/zed/RELEASE_CHANNEL
275    '';
276
277    installPhase =
278      if stdenv.hostPlatform.isDarwin then
279        ''
280          runHook preInstall
281
282          pushd crates/zed
283          sed -i "s/package.metadata.bundle-nightly/package.metadata.bundle/" Cargo.toml
284          export CARGO_BUNDLE_SKIP_BUILD=true
285          app_path="$(cargo bundle --profile $CARGO_PROFILE | xargs)"
286          popd
287
288          mkdir -p $out/Applications $out/bin
289          # Zed expects git next to its own binary
290          ln -s ${git}/bin/git "$app_path/Contents/MacOS/git"
291          mv $TARGET_DIR/cli "$app_path/Contents/MacOS/cli"
292          mv "$app_path" $out/Applications/
293
294          # Physical location of the CLI must be inside the app bundle as this is used
295          # to determine which app to start
296          ln -s "$out/Applications/Zed Nightly.app/Contents/MacOS/cli" $out/bin/zed
297
298          runHook postInstall
299        ''
300      else
301        ''
302          runHook preInstall
303
304          mkdir -p $out/bin $out/libexec
305          cp $TARGET_DIR/zed $out/libexec/zed-editor
306          cp $TARGET_DIR/cli  $out/bin/zed
307          ln -s $out/bin/zed $out/bin/zeditor  # home-manager expects the CLI binary to be here
308
309
310          install -D "crates/zed/resources/app-icon-nightly@2x.png" \
311            "$out/share/icons/hicolor/1024x1024@2x/apps/zed.png"
312          install -D crates/zed/resources/app-icon-nightly.png \
313            $out/share/icons/hicolor/512x512/apps/zed.png
314
315          # TODO: icons should probably be named "zed-nightly"
316          (
317            export DO_STARTUP_NOTIFY="true"
318            export APP_CLI="zed"
319            export APP_ICON="zed"
320            export APP_NAME="Zed Nightly"
321            export APP_ARGS="%U"
322            mkdir -p "$out/share/applications"
323            ${lib.getExe envsubst} < "crates/zed/resources/zed.desktop.in" > "$out/share/applications/dev.zed.Zed-Nightly.desktop"
324            chmod +x "$out/share/applications/dev.zed.Zed-Nightly.desktop"
325          )
326
327          runHook postInstall
328        '';
329
330    # TODO: why isn't this also done on macOS?
331    postFixup = lib.optionalString stdenv.hostPlatform.isLinux ''
332      wrapProgram $out/libexec/zed-editor --suffix PATH : ${lib.makeBinPath [ nodejs_22 ]}
333    '';
334
335    meta = {
336      description = "High-performance, multiplayer code editor from the creators of Atom and Tree-sitter";
337      homepage = "https://zed.dev";
338      changelog = "https://zed.dev/releases/preview";
339      license = lib.licenses.gpl3Only;
340      mainProgram = "zed";
341      platforms = lib.platforms.linux ++ lib.platforms.darwin;
342    };
343  }
344)