Cider Isn't Darwin Emulation, Really

README.md

Cider #

Cider Isn't Darwin Emulation, Really.

Cider is a runtime environment for macOS applications on Linux. It runs Mach-O binaries directly on the Linux kernel through its own dyld, libSystem and kernel-emulation layer. There is no virtual machine and no Apple kernel.

Cider is a fork of Darling and is licensed under the GPL, version 3 or later. See LICENSE.

Status #

Early. Command line software works; most GUI applications do not.

What follows is what an automated check in this repository actually exercises, not a wish list. Each one boots a container and runs a real program:

Works Checked by
A shell: cider shell and bash inside it scripts/checks/buck-bash-check.nu
Booting through launchd scripts/checks/buck-launchd-check.nu
Nix, running inside the container scripts/checks/buck-nix-bash-check.nu
JavaScriptCore running a script scripts/checks/buck-jsc-check.nu
libdispatch, Security, CoreAudio, scripting bridges buck-{dispatch,security,audio,scripting}-check.nu
A trivial AppKit window on Wayland scripts/checks/buck-appkit-check.nu
A guest binary reaching a Wayland compositor scripts/checks/buck-wayland-check.nu

Anything not in that table is unverified here. In particular Cider has not been shown to install .pkg files, mount Xcode disk images, or run Xcode or its toolchain. Darling documents those; this fork does not currently test them, so it does not claim them.

The GUI backend is Wayland. The X11 one it replaced is gone, and there is no DISPLAY fallback: a window is an xdg_surface on a compositor, drawn into shared memory by Onyx2D. What that currently carries is LibreOffice, which renders, takes keyboard and mouse, resizes with the compositor, opens its menus and its file dialogs, and inserts a picture through one. That is driven by hand rather than by a check in the table above, and every step of it, with the screenshots, is in docs/wayland-port.md.

Build #

Cider builds with Nix and Buck2. Nix is the only supported packaging: there is no distribution package, and no tarball.

nix build .#cider

That is the full build and it is large. On a machine that cannot finish it, the minimal build drops the GUI frameworks, the private frameworks and the scripting languages, which is about 42 percent of the work, and still boots, runs a shell and runs Nix:

nix build .#cider-min

Everything else in flake.nix is internal: the graph, the lowering, the prefixes and the cider-buck2-* attributes are stages of the build rather than things to install.

Install and run #

On NixOS, through the module:

{
  inputs.cider.url = "git+https://tangled.org/overby.me/cider";
  # ...
  programs.cider.enable = true;
}

Otherwise install the package and use the entry point it provides:

nix profile install .#cider
cider shell echo Hello world

Prefixes #

Cider has CIDERPREFIXes, which are close to WINEPREFIXes: virtual chroot-like environments with a macOS-shaped filesystem, where software can be installed safely. The default is ~/.cider, changed by exporting CIDERPREFIX. A prefix is created and initialized on first use.

Prefixes use overlayfs, so a prefix cannot live on NFS or eCryptfs. The default location will not work with an encrypted home directory.

Development #

Where things live:

src/darwin/     the guest side: frameworks, dylibs and tools that run INSIDE the container
src/linux/      the host side: the ciderd daemon, the launcher, the Mach-O loader, build tools
buck/       our Buck2 rules, toolchains and the prefix definition
vendor/     everything that is not ours, in three parts:
              src/   upstream C sources, materialized from pins rather than committed
              rust/  vendored Rust crates, materialized the same way
              pins/  upstream components committed on purpose, each with VENDORED.md
nix/        the Nix side: the flake library, the graph and lowering, the NixOS module
scripts/    the developer loop, in nushell, split three ways:
              checks/  41 checks, the ones buck-test.nu and buck-runtime-check.nu run
              gen/     3 generators
              build/   5 build drivers
            the 42 that stay flat are tools, plus the 8 shell scripts that have to be bash

vendor/src/ and vendor/rust/ hold about 260,000 and 3,000 files respectively and are almost entirely gitignored; only their generated BUCK files are committed. Only 4 of the 148 pins are materialized on a working checkout: scripts/buck-src.nu fetches what the port currently needs, not the whole 3.8 GB.

Building without Nix, which is how you should iterate #

Nix is the packaging, not the build. The build is Buck2, and you can drive it directly. A Nix-built prefix takes an hour; a Buck2 rebuild after editing one file takes seconds, and this is the loop to use while working.

nix develop                 # the toolchain only: clang, rustc, buck2, nushell
scripts/buck-setup.nu       # writes .buckconfig.local with the store paths of those tools
buck2 build //...           # or a single target, which is the point

scripts/buck-setup.nu is what connects the two: it resolves the compiler, the guest toolchain, the host library directories that wrapgen dlopens, and the guest Rust toolchain, and writes them into a machine-local .buckconfig.local that is gitignored. Rerun it after a nixpkgs bump moves any of those store paths.

You still need Nix to GET the toolchain, and to produce an installable package. You do not need it to compile, link, or run the checks.

Run the checks with:

scripts/buck-test.nu

docs/changelog.md is the working record: what is being done, what was measured, and what was tried and rejected. docs/release-readiness.md is what stands between this and a first release.