Skip to content

feat(nix): add a flake that builds Moli - #140

Open
athul-22 wants to merge 2 commits into
lexmount:mainfrom
athul-22:feat/nix-flake
Open

feat(nix): add a flake that builds Moli#140
athul-22 wants to merge 2 commits into
lexmount:mainfrom
athul-22:feat/nix-flake

Conversation

@athul-22

Copy link
Copy Markdown
Contributor

Refs #118.

Moli could not be built with Nix at all, so packaging it for nixpkgs had no starting point. This adds a flake that builds the CLI, plus a dev shell and a formatter.

$ nix run github:lexmount/moli -- fetch --dump markdown https://example.com
$ nix build github:lexmount/moli
$ nix develop github:lexmount/moli

Scope

This is the buildable foundation, not the nixpkgs submission itself — search.nixos.org needs a separate PR against NixOS/nixpkgs. nix/package.nix is deliberately split from flake.nix and takes src, version, and revision as arguments, so it can be lifted into nixpkgs with a fetchFromGitHub src and no rewrite. Happy to follow up with that submission if you want it.

What a sandboxed build runs into

Four things in this workspace block a Nix build. Each is commented where it is worked around, because none of them is guessable from the outside:

The vendored v8 crate downloads a prebuilt static library from its build script, and Nix builds have no network. There is no source-build escape hatch — the vendored tree is prebuilt-only and explicitly rejects V8_FROM_SOURCE. The way through is that download_file copies rather than downloads when RUSTY_V8_ARCHIVE is not an http(s): URL, so a fixed-output fetchurl satisfies it. The V8 src bindings are already vendored under vendor/v8-146.9.0/gen/, so nothing else is fetched.

The pinned curl-rust fork carries libcurl as a git submodule. This one is a trap: cargoLock uses importCargoLock, which calls fetchgit without fetchSubmodules, so the submodule arrives empty and the static-curl build fails. Only the fetchCargoVendor path behind cargoHash passes --fetch-submodules. cargoHash is therefore mandatory here, and switching to cargoLock later would silently break the build.

That same fork locates brotli's C headers by scanning $CARGO_HOME and a vendor/ directory beside its own manifest. Neither exists in the Nix vendor layout, so preBuild links the vendored brotlic-sys crate into a path that scan reaches.

moli-protocol derives the binary revision by shelling out to git, and a Nix source tree has no .git. vergen's fail_on_error makes that fatal in a way VERGEN_IDEMPOTENT cannot soften (gitcl returns the error before ever consulting the idempotent flag). Rather than carry a git repository into the sandbox, postPatch emits the revision the flake already knows — which is more accurate than what a tarball build would produce.

Beyond those, stylo needs Python at build time for its property tables, and bindgen needs libclang via rustPlatform.bindgenHook.

doCheck = false: the suite binds loopback HTTP servers and drives a full browser runtime, neither of which the sandbox can host.

Testing

Verified on aarch64-darwin:

  • nix build succeeds
  • nix run .#moli -- --version works
  • nix flake check passes
  • nix fmt runs clean and is idempotent
  • the resulting binary fetches a live page, and correctly refuses a robots.txt-disallowed URL

Only aarch64-darwin was actually built. The V8 archive hashes for the other three platforms come from downloading and hashing the published release assets, so they are real values rather than placeholders, but those builds are unverified. If CI or a Linux user hits something, I would expect it in the curl-sys or stylo native-dependency area rather than in the V8 wiring.

nixfmt formats files rather than directories, so formatter is a small wrapper that walks the tree; a bare pkgs.nixfmt makes nix fmt fail.

Moli could not be built with Nix at all, so packaging it for nixpkgs had
no starting point. Add a flake exposing the CLI, a dev shell, and a
formatter.

Four things about this workspace need handling before a sandboxed build
works, and each is commented where it is worked around:

- The vendored `v8` crate downloads a prebuilt static library from its
  build script. `RUSTY_V8_ARCHIVE` is copied rather than downloaded when
  it is not an http(s) URL, so a fixed-output `fetchurl` satisfies it.
  Building V8 from source is not an option: the vendored tree is
  prebuilt-only and rejects `V8_FROM_SOURCE`.
- The pinned curl-rust fork carries libcurl as a git submodule. Only the
  `fetchCargoVendor` path behind `cargoHash` passes `--fetch-submodules`,
  so `cargoLock` cannot be used here; it leaves the submodule empty and
  the static libcurl build fails.
- That same fork locates brotli's C headers by scanning `$CARGO_HOME` and
  a `vendor/` directory beside its own manifest, neither of which exists
  in the Nix vendor layout, so `preBuild` links the vendored brotlic-sys
  crate into a path the scan reaches.
- `moli-protocol` derives the binary revision by shelling out to git, and
  vergen's `fail_on_error` makes a missing repository fatal in a way
  `VERGEN_IDEMPOTENT` cannot soften. Rather than carry a git repository
  into the sandbox, emit the revision the flake already knows.

`stylo` also needs Python at build time for its property tables, and
`bindgen` needs libclang.

Tests are disabled in the sandbox: the suite binds loopback HTTP servers
and drives a full browser runtime.

Verified on aarch64-darwin: `nix build`, `nix run`, `nix flake check`, and
`nix fmt` all succeed, and the resulting binary fetches a live page. The
V8 archive hashes for the other three platforms were taken from the
published release assets, but only aarch64-darwin was built here.

Refs lexmount#118

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial Nix support for building, running, developing, and formatting Moli.

Changes:

  • Adds a four-platform Nix flake and locked nixpkgs input.
  • Packages the Rust CLI with sandbox-compatible V8 and native dependency handling.
  • Documents Nix usage and provides a development shell and formatter.

Reviewed changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
.gitignore Ignores Nix build result links.
README.md Documents Nix commands.
flake.lock Pins nixpkgs.
flake.nix Defines packages, shells, and formatter.
nix/package.nix Packages the Moli CLI.
nix/shell.nix Defines the Rust development shell.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread flake.nix Outdated
packages = forEachSystem (pkgs: rec {
moli = pkgs.callPackage ./nix/package.nix {
src = self;
revision = self.rev or self.dirtyRev or "unknown";
Comment thread nix/shell.nix
Comment on lines +3 to +27
cargo,
cargo-nextest,
clippy,
cmake,
perl,
pkg-config,
python3,
rustc,
rustPlatform,
rustfmt,
}:

mkShell {
# A dev shell is not sandboxed, so the `v8` build script can fetch its own
# prebuilt archive. Only the native toolchain has to be provided here.
packages = [
cargo
cargo-nextest
clippy
cmake
perl
pkg-config
python3
rustc
rustfmt
athul-22 added a commit to athul-22/moli that referenced this pull request Aug 21, 2026
Review on lexmount#140 caught two problems.

`self.dirtyRev` is `<commit>-dirty`, so building from a dirty worktree
embedded a non-hex revision and made `Browser.getVersion` report a value
that violates the 40/64-digit hex contract asserted in
`moli-protocol/src/version.rs`. The `"unknown"` fallback for a source
that is not a git tree broke the same contract. Strip the suffix and
fall back to git's all-zero commit sentinel instead, so every path
yields 40 hex digits.

The dev shell also relied on two undeclared host tools: `moli-protocol`
derives its revision with vergen-gitcl under `fail_on_error`, and the
vendored `v8` downloader shells out to Deno or `curl` — the slim tree
carries no `tools/download_file.py` fallback. Without either tool
`cargo build` failed inside the advertised shell. Add git and curl.

Verified on aarch64-darwin: with the worktree dirty the flake emits
VERGEN_GIT_SHA=46d3761, matching `git rev-parse HEAD`; the clean, dirty,
and non-git branches each yield 40 hex digits; the dev shell derivation
carries git 2.55.0 and curl 8.21.0 alongside the existing toolchain; and
`nix fmt` is a no-op on both files.

Refs lexmount#118

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review on lexmount#140 caught two problems.

`self.dirtyRev` is `<commit>-dirty`, so building from a dirty worktree
embedded a non-hex revision and made `Browser.getVersion` report a value
that violates the 40/64-digit hex contract asserted in
`moli-protocol/src/version.rs`. The `"unknown"` fallback for a source
that is not a git tree broke the same contract. Strip the suffix and
fall back to git's all-zero commit sentinel instead, so every path
yields 40 hex digits.

The dev shell also relied on two undeclared host tools: `moli-protocol`
derives its revision with vergen-gitcl under `fail_on_error`, and the
vendored `v8` downloader shells out to Deno or `curl` — the slim tree
carries no `tools/download_file.py` fallback. Without either tool
`cargo build` failed inside the advertised shell. Add git and curl.

Verified on aarch64-darwin: with the worktree dirty the flake emits
VERGEN_GIT_SHA=46d3761, matching `git rev-parse HEAD`; the clean, dirty,
and non-git branches each yield 40 hex digits; the dev shell derivation
carries git 2.55.0 and curl 8.21.0 alongside the existing toolchain; and
`nix fmt` is a no-op on both files.

Refs lexmount#118
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants