build.nix

  1{
  2  lib,
  3  crane,
  4  rustToolchain,
  5  rustPlatform,
  6  cmake,
  7  copyDesktopItems,
  8  fetchFromGitHub,
  9  curl,
 10  clang,
 11  perl,
 12  pkg-config,
 13  protobuf,
 14  fontconfig,
 15  freetype,
 16  libgit2,
 17  openssl,
 18  sqlite,
 19  zlib,
 20  zstd,
 21  alsa-lib,
 22  libxkbcommon,
 23  wayland,
 24  libglvnd,
 25  xorg,
 26  stdenv,
 27  makeFontsConf,
 28  vulkan-loader,
 29  envsubst,
 30  cargo-about,
 31  cargo-bundle,
 32  git,
 33  livekit-libwebrtc,
 34  apple-sdk_15,
 35  darwin,
 36  darwinMinVersionHook,
 37  makeWrapper,
 38  nodejs_22,
 39
 40  withGLES ? false,
 41}:
 42
 43assert withGLES -> stdenv.hostPlatform.isLinux;
 44
 45let
 46  mkIncludeFilter =
 47    root': path: type:
 48    let
 49      # note: under lazy-trees this introduces an extra copy
 50      root = toString root' + "/";
 51      relPath = lib.removePrefix root path;
 52      topLevelIncludes = [
 53        "crates"
 54        "assets"
 55        "extensions"
 56        "script"
 57        "tooling"
 58        "Cargo.toml"
 59        ".config" # nextest?
 60      ];
 61      firstComp = builtins.head (lib.path.subpath.components relPath);
 62    in
 63    builtins.elem firstComp topLevelIncludes;
 64
 65  craneLib = crane.overrideToolchain rustToolchain;
 66  gpu-lib = if withGLES then libglvnd else vulkan-loader;
 67  commonArgs =
 68    let
 69      zedCargoLock = builtins.fromTOML (builtins.readFile ../crates/zed/Cargo.toml);
 70    in
 71    rec {
 72      pname = "zed-editor";
 73      version = zedCargoLock.package.version + "-nightly";
 74      src = builtins.path {
 75        path = ../.;
 76        filter = mkIncludeFilter ../.;
 77        name = "source";
 78      };
 79
 80      cargoLock = ../Cargo.lock;
 81
 82      nativeBuildInputs =
 83        [
 84          clang # TODO: use pkgs.clangStdenv or ignore cargo config?
 85          cmake
 86          copyDesktopItems
 87          curl
 88          perl
 89          pkg-config
 90          protobuf
 91          cargo-about
 92          rustPlatform.bindgenHook
 93        ]
 94        ++ lib.optionals stdenv.hostPlatform.isLinux [ makeWrapper ]
 95        ++ lib.optionals stdenv.hostPlatform.isDarwin [
 96          # TODO: move to overlay so it's usable in the shell
 97          (cargo-bundle.overrideAttrs (old: {
 98            version = "0.6.0-zed";
 99            src = fetchFromGitHub {
100              owner = "zed-industries";
101              repo = "cargo-bundle";
102              rev = "zed-deploy";
103              hash = "sha256-OxYdTSiR9ueCvtt7Y2OJkvzwxxnxu453cMS+l/Bi5hM=";
104            };
105          }))
106        ];
107
108      buildInputs =
109        [
110          curl
111          fontconfig
112          freetype
113          # TODO: need staticlib of this for linking the musl remote server.
114          # should make it a separate derivation/flake output
115          # see https://crane.dev/examples/cross-musl.html
116          libgit2
117          openssl
118          sqlite
119          zlib
120          zstd
121        ]
122        ++ lib.optionals stdenv.hostPlatform.isLinux [
123          alsa-lib
124          libxkbcommon
125          wayland
126          gpu-lib
127          xorg.libxcb
128        ]
129        ++ lib.optionals stdenv.hostPlatform.isDarwin [
130          apple-sdk_15
131          darwin.apple_sdk.frameworks.System
132          (darwinMinVersionHook "10.15")
133        ];
134
135      cargoExtraArgs = "--package=zed --package=cli --features=gpui/runtime_shaders";
136
137      env = {
138        ZSTD_SYS_USE_PKG_CONFIG = true;
139        FONTCONFIG_FILE = makeFontsConf {
140          fontDirectories = [
141            ../assets/fonts/plex-mono
142            ../assets/fonts/plex-sans
143          ];
144        };
145        ZED_UPDATE_EXPLANATION = "Zed has been installed using Nix. Auto-updates have thus been disabled.";
146        RELEASE_VERSION = version;
147        RUSTFLAGS = if withGLES then "--cfg gles" else "";
148        # these libraries are used with dlopen so putting them in buildInputs isn't enough
149        NIX_LDFLAGS = "-rpath ${
150          lib.makeLibraryPath [
151            gpu-lib
152            wayland
153          ]
154        }";
155        LK_CUSTOM_WEBRTC = livekit-libwebrtc;
156      };
157
158      # prevent nix from removing the "unused" wayland/gpu-lib rpaths
159      dontPatchELF = true;
160
161      cargoVendorDir = craneLib.vendorCargoDeps {
162        inherit src cargoLock;
163        overrideVendorGitCheckout =
164          let
165            hasWebRtcSys = builtins.any (crate: crate.name == "webrtc-sys");
166            # `webrtc-sys` expects a staticlib; nixpkgs' `livekit-webrtc` has been patched to
167            # produce a `dylib`... patching `webrtc-sys`'s build script is the easier option
168            # TODO: send livekit sdk a PR to make this configurable
169            postPatch = ''
170              substituteInPlace webrtc-sys/build.rs --replace-fail \
171                "cargo:rustc-link-lib=static=webrtc" "cargo:rustc-link-lib=dylib=webrtc"
172            '';
173          in
174          crates: drv:
175          if hasWebRtcSys crates then
176            drv.overrideAttrs (o: {
177              postPatch = (o.postPatch or "") + postPatch;
178            })
179          else
180            drv;
181      };
182    };
183  cargoArtifacts = craneLib.buildDepsOnly (
184    commonArgs
185    // {
186      # TODO: figure out why the main derivation is still rebuilding deps...
187      # disable pre-building the deps for now
188      buildPhaseCargoCommand = "true";
189
190      # forcibly inhibit `doInstallCargoArtifacts`...
191      # https://github.com/ipetkov/crane/blob/1d19e2ec7a29dcc25845eec5f1527aaf275ec23e/lib/setupHooks/installCargoArtifactsHook.sh#L111
192      #
193      # it is, unfortunately, not overridable in `buildDepsOnly`:
194      # https://github.com/ipetkov/crane/blob/1d19e2ec7a29dcc25845eec5f1527aaf275ec23e/lib/buildDepsOnly.nix#L85
195      preBuild = "postInstallHooks=()";
196      doCheck = false;
197    }
198  );
199in
200craneLib.buildPackage (
201  lib.recursiveUpdate commonArgs {
202    inherit cargoArtifacts;
203
204    patches = lib.optionals stdenv.hostPlatform.isDarwin [
205      # Livekit requires Swift 6
206      # We need this until livekit-rust sdk is used
207      ../script/patches/use-cross-platform-livekit.patch
208    ];
209
210    dontUseCmakeConfigure = true;
211
212    # without the env var generate-licenses fails due to crane's fetchCargoVendor, see:
213    # https://github.com/zed-industries/zed/issues/19971#issuecomment-2688455390
214    preBuild = ''
215      ALLOW_MISSING_LICENSES=yes bash script/generate-licenses
216      echo nightly > crates/zed/RELEASE_CHANNEL
217    '';
218
219    # TODO: try craneLib.cargoNextest separate output
220    # for now we're not worried about running our test suite in the nix sandbox
221    doCheck = false;
222
223    installPhase =
224      if stdenv.hostPlatform.isDarwin then
225        ''
226          runHook preInstall
227
228          pushd crates/zed
229          sed -i "s/package.metadata.bundle-nightly/package.metadata.bundle/" Cargo.toml
230          export CARGO_BUNDLE_SKIP_BUILD=true
231          app_path="$(cargo bundle --release | xargs)"
232          popd
233
234          mkdir -p $out/Applications $out/bin
235          # Zed expects git next to its own binary
236          ln -s ${git}/bin/git "$app_path/Contents/MacOS/git"
237          mv target/release/cli "$app_path/Contents/MacOS/cli"
238          mv "$app_path" $out/Applications/
239
240          # Physical location of the CLI must be inside the app bundle as this is used
241          # to determine which app to start
242          ln -s "$out/Applications/Zed Nightly.app/Contents/MacOS/cli" $out/bin/zed
243
244          runHook postInstall
245        ''
246      else
247        # TODO: icons should probably be named "zed-nightly". fix bundle-linux first
248        ''
249          runHook preInstall
250
251          mkdir -p $out/bin $out/libexec
252          cp target/release/zed $out/libexec/zed-editor
253          cp target/release/cli $out/bin/zed
254
255          install -D "crates/zed/resources/app-icon-nightly@2x.png" \
256            "$out/share/icons/hicolor/1024x1024@2x/apps/zed.png"
257          install -D crates/zed/resources/app-icon-nightly.png \
258            $out/share/icons/hicolor/512x512/apps/zed.png
259
260          # extracted from ../script/bundle-linux (envsubst) and
261          # ../script/install.sh (final desktop file name)
262          (
263            export DO_STARTUP_NOTIFY="true"
264            export APP_CLI="zed"
265            export APP_ICON="zed"
266            export APP_NAME="Zed Nightly"
267            export APP_ARGS="%U"
268            mkdir -p "$out/share/applications"
269            ${lib.getExe envsubst} < "crates/zed/resources/zed.desktop.in" > "$out/share/applications/dev.zed.Zed-Nightly.desktop"
270          )
271
272          runHook postInstall
273        '';
274
275    # TODO: why isn't this also done on macOS?
276    postFixup = lib.optionalString stdenv.hostPlatform.isLinux ''
277      wrapProgram $out/libexec/zed-editor --suffix PATH : ${lib.makeBinPath [ nodejs_22 ]}
278    '';
279
280    meta = {
281      description = "High-performance, multiplayer code editor from the creators of Atom and Tree-sitter";
282      homepage = "https://zed.dev";
283      changelog = "https://zed.dev/releases/preview";
284      license = lib.licenses.gpl3Only;
285      mainProgram = "zed";
286      platforms = lib.platforms.linux ++ lib.platforms.darwin;
287    };
288  }
289)