diff --git a/docs/CHEATSHEET.md b/docs/CHEATSHEET.md index dbd83356..47180d96 100644 --- a/docs/CHEATSHEET.md +++ b/docs/CHEATSHEET.md @@ -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 すればそのまま動く + --- ## 🩺 探索 / トラブル diff --git a/nix/flake.nix b/nix/flake.nix index f83f55e5..35f29b97 100644 --- a/nix/flake.nix +++ b/nix/flake.nix @@ -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 { diff --git a/nix/shells/browser-engines.nix b/nix/shells/browser-engines.nix new file mode 100644 index 00000000..6b16c058 --- /dev/null +++ b/nix/shells/browser-engines.nix @@ -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 `. + # 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)" + ''; + }; +}