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