Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/CHEATSHEET.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,44 @@ nix-init --url https://github.com/nosarthur/gita
# → 動く flake.nix が生成される
```

### ブラウザエンジンのビルド(Ladybird / Servo)

WebKit 以外のエンジンを母艦でビルドするための devShell。どちらも Xcode の clang と
macOS SDK を使うので darwin 限定。定義は `nix/shells/browser-engines.nix`。

```bash
# Ladybird(CMake + vcpkg)
nix develop ~/.dotfiles/nix#ladybird
cd ~/Developer/github.com/LadybirdBrowser/ladybird
python3 Meta/ladybird.py build
./Build/release/bin/Ladybird.app/Contents/MacOS/Ladybird https://example.com

# Servo(mach + cargo)
nix develop ~/.dotfiles/nix#servo
cd ~/Developer/github.com/servo/servo
./mach build --release --media-stack dummy
./target/release/servoshell --headless --exit -o out.png https://example.com
```

踏みやすい罠:

- `./mach bootstrap` は Homebrew を叩く唯一の経路なので実行しない。必要な cmake /
pkg-config は devShell 側にある。`MACH_USE_NIX` も立てない(mach が darwin で
評価できない `shell.nix` に再突入する)
- Servo の音声/動画を有効にするには GStreamer 公式 pkg (本体 + devel の2つ) を sudo で
システムに入れる必要があり、これは宣言管理の外に出る。macmini 側にだけ入れてあるので、
media 有効ビルドは macmini で行う。`./mach package` が GStreamer dylib を .app に
同梱するので、母艦は Servo.app を受け取るだけでよく何も入れなくてよい。
入れずにビルドするなら `--media-stack dummy`(再生なし)
- Ladybird の headless は 2026-08 時点の master で壊れている(Compositor プロセスが
起動しないまま WebContent が接続を叩いて落ちる)。GUI は正常
- devShell に依存ライブラリを足さない。vcpkg が自前で建てるものと衝突すると nix 側が
勝ち、成果物が `/nix/store` を参照する。その store path は誰も root していないので
次の GC で消え、ある日突然 dyld の "Library not loaded" で起動しなくなる。疑ったら
`otool -L` で `/nix/store` 参照が無いことを確認する
- ビルドが重いときは macmini に投げる(10 コア / 24GB / 空きが多い)。home が両機とも
`/Users/gapul` なので、`Build/release` を同じパスに rsync すればそのまま動く

---

## 🩺 探索 / トラブル
Expand Down
48 changes: 28 additions & 20 deletions nix/flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -590,26 +590,34 @@
# are Mach-O binaries. Putting them in a Linux shell got them execve'd, xargs fell back
# to /bin/sh, and dash reported a syntax error inside the ELF. They stay darwin-only;
# the git hooks are a local-dev convenience and checks.pre-commit is darwin-only too.
devShells.default = systemPkgs.mkShell (
{
buildInputs = [
systemPkgs.shellcheck
systemPkgs.statix
systemPkgs.stylua
systemPkgs.taplo
systemPkgs.yq-go
systemPkgs.jq
systemPkgs.just
systemPkgs.python3 # scripts/gen-docs.py (doc generation block)
systemPkgs.bun
systemPkgs.check-jsonschema
systemPkgs.actionlint
systemPkgs.gitleaks # om ci's gitleaks custom step
systemPkgs.git # ci-lint / ci-gitleaks use git ls-files / rev-parse
]
++ lib.optionals isDarwinWorkstation preCommit.enabledPackages;
}
// lib.optionalAttrs isDarwinWorkstation { inherit (preCommit) shellHook; }
devShells = {
default = systemPkgs.mkShell (
{
buildInputs = [
systemPkgs.shellcheck
systemPkgs.statix
systemPkgs.stylua
systemPkgs.taplo
systemPkgs.yq-go
systemPkgs.jq
systemPkgs.just
systemPkgs.python3 # scripts/gen-docs.py (doc generation block)
systemPkgs.bun
systemPkgs.check-jsonschema
systemPkgs.actionlint
systemPkgs.gitleaks # om ci's gitleaks custom step
systemPkgs.git # ci-lint / ci-gitleaks use git ls-files / rev-parse
]
++ lib.optionals isDarwinWorkstation preCommit.enabledPackages;
}
// lib.optionalAttrs isDarwinWorkstation { inherit (preCommit) shellHook; }
);
}
# Ladybird and Servo build with Xcode's clang against the macOS SDK,
# so those two shells only exist on darwin. They are entered by hand
# rather than built by CI: the engines take hours and tens of GB.
// lib.optionalAttrs isDarwinWorkstation (
import ./shells/browser-engines.nix { pkgs = systemPkgs; }
);
}
// lib.optionalAttrs isDarwinWorkstation {
Expand Down
134 changes: 134 additions & 0 deletions nix/shells/browser-engines.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Build environments for the two independent browser engines that can be built
# on this Mac: Ladybird (CMake + vcpkg) and Servo (mach + cargo).
#
# Neither project is packaged; these shells only assemble the toolchain their
# own build systems shell out to, so that nothing has to be installed through
# Homebrew. That matters here because darwin.nix runs Homebrew with
# `cleanup = "uninstall"`, which would remove anything a project's bootstrap
# script installed behind nix's back on the next rebuild.
#
# Both are darwin-only: they compile with Xcode's clang and reference the
# macOS SDK by absolute path.
{ pkgs }:

let
inherit (pkgs) lib;

# Both projects pin an exact Rust version in rust-toolchain.toml (Ladybird
# 1.96.1, Servo 1.97.1), and only rustup reads that file. Shipping nixpkgs'
# rustc would silently build with whatever version nixpkgs happens to carry.
rust = pkgs.rustup;

# GNU libtool and Apple's /usr/bin/libtool are unrelated programs that share a
# name. nixpkgs installs GNU's as plain `libtool`, which shadows Apple's, and
# skia's build then fails on `libtool -static -o libwuffs.a ...` -- an
# invocation only Apple's archiver understands. Homebrew avoids the collision
# by exposing GNU libtool as `glibtool`, and both projects' macOS instructions
# assume that layout, so reproduce it: `libtool` stays Apple's, while GNU's
# tools keep the names autotools actually searches for.
libtoolShim = pkgs.runCommand "libtool-macos-layout" { } ''
mkdir -p $out/bin
ln -s /usr/bin/libtool $out/bin/libtool
ln -s ${pkgs.libtool}/bin/libtool $out/bin/glibtool
ln -s ${pkgs.libtool}/bin/libtoolize $out/bin/glibtoolize
ln -s ${pkgs.libtool}/bin/libtoolize $out/bin/libtoolize
'';

# autoreconf still needs GNU libtool's m4 macros, which the shim does not carry.
aclocalPath = lib.concatStringsSep ":" [
"${pkgs.libtool}/share/aclocal"
"${pkgs.autoconf-archive}/share/aclocal"
"${pkgs.automake}/share/aclocal"
];

commonTools = [
pkgs.ninja
pkgs.nasm
pkgs.autoconf
pkgs.autoconf-archive
pkgs.automake
libtoolShim
pkgs.pkg-config
pkgs.ccache
pkgs.cmake
rust
];

# Shared by both shells: keep every nix compiler out of PATH and build with
# Xcode's clang. See the per-shell notes for why each project needs this.
appleToolchainHook = ''
export CC=/usr/bin/clang
export CXX=/usr/bin/clang++
export ACLOCAL_PATH="${aclocalPath}''${ACLOCAL_PATH:+:$ACLOCAL_PATH}"
export PATH="${libtoolShim}/bin:$PATH"
'';
in
{
# Ladybird: ./Meta/ladybird.py build
#
# mkShellNoCC is deliberate. Ladybird's Meta/Utils/find_compiler.py explicitly
# rejects clang 21 on macOS -- it links against LLVM's libc++ and then fails on
# std::__1::__hash_memory -- and prefers Xcode clang when it finds one. Putting
# a nix clang in PATH would offer it a compiler it refuses to use.
ladybird = pkgs.mkShellNoCC {
packages = commonTools ++ [
pkgs.python3
# vcpkg's bootstrap shells out to zip/unzip/tar; macOS supplies tar.
#
# Deliberately no pkgs.curl here. vcpkg builds its own curl and libpsl, but
# a nix curl on the search path wins, and liblagom-url ends up linked
# against a /nix/store libpsl. Nothing roots that path once the shell is
# gone, so the next GC deletes it and Ladybird stops launching with a dyld
# "Library not loaded" error. /usr/bin/curl covers the download step.
pkgs.zip
pkgs.unzip
];

shellHook = appleToolchainHook + ''
echo "ladybird shell: cc=$(clang --version | head -1)"
echo " libtool=$(readlink -f "$(command -v libtool)")"
'';
};

# Servo: ./mach build --release --media-stack dummy
#
# The repo ships its own shell.nix, but it cannot evaluate on darwin -- udev
# and the X11 stack sit in its unconditional buildInputs -- so this shell
# replaces it.
#
# Do NOT set MACH_USE_NIX: it makes ./mach re-exec itself into
# `nix-shell shell.nix`, which is exactly the file that cannot evaluate.
# Homebrew is only ever reached through `mach bootstrap`, so the rule is
# simply never to run that; its Brewfile asks for cmake and pkg-config, both
# of which this shell already provides.
#
# `--media-stack dummy` is the workable default on macOS: Servo accepts only
# the official GStreamer .pkg there, which is a sudo install into
# /Library/Frameworks and therefore outside declarative management. Without it
# the build fails at link time rather than falling back.
servo = pkgs.mkShellNoCC {
packages = commonTools ++ [
pkgs.uv
# .python-version pins 3.11, so uv symlinks this interpreter instead of
# downloading a build of its own.
pkgs.python311
pkgs.gnumake # mozjs needs GNU make; Apple ships make 3.81
pkgs.m4
pkgs.perl
pkgs.yasm
pkgs.openssl
];

shellHook = appleToolchainHook + ''
# mozangle and mozjs run their headers through bindgen, which drives
# libclang directly instead of the clang driver. Nothing supplies the
# macOS SDK sysroot that way, and the parse dies on `#include <array>`.
# Point bindgen at Xcode's own libclang, matching CC/CXX, and hand it the
# sysroot explicitly.
export SDKROOT="''${SDKROOT:-$(xcrun --show-sdk-path)}"
export LIBCLANG_PATH="$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib"
export BINDGEN_EXTRA_CLANG_ARGS="-isysroot $SDKROOT"
echo "servo shell: rust via rustup -- never run ./mach bootstrap (it calls brew)"
'';
};
}
Loading