Skip to content

feat(cli): replace xgenext2fs and cartesi-machine spawns with native bindings - #509

Draft
tuler wants to merge 2 commits into
prerelease/v2-alphafrom
claude/replace-xgenext2fs-cartesi-machine-rhhnsk
Draft

feat(cli): replace xgenext2fs and cartesi-machine spawns with native bindings#509
tuler wants to merge 2 commits into
prerelease/v2-alphafrom
claude/replace-xgenext2fs-cartesi-machine-rhhnsk

Conversation

@tuler

@tuler tuler commented Aug 5, 2026

Copy link
Copy Markdown
Member

Contributes to #72.

That issue asks for the external programs the CLI drives to stop being spawned binaries, and notes that "instead of spawning binaries another possibility is to build NodeJS bindings to native code components". This PR takes that route for two of the four programs listed there.

Program Before After
xgenext2fs execa, falling back to docker run in the SDK image @deroll/genext2fs
cartesi-machine execa, falling back to docker run in the SDK image @deroll/cm
cartesi-machine-stored-hash execa, falling back to docker run in the SDK image @deroll/cm
mksquashfs unchanged unchanged
cartesi-rollups-cli / cartesi-rollups-node unchanged unchanged

Docker is still needed to build the root drive from a Dockerfile (riscv64 via QEMU), for squashfs drives, and for the services run brings up — which is the state issue #72 describes as done. The remaining work there is the Node Unit's binaries.

What changed

src/exec/genext2fs.tsempty, fromDirectory and fromTar call createImage/tarToExt2 with the same block size, faketime and readjustment settings as the old command lines. version() is now a synchronous lookup of the vendored xgenext2fs version rather than a --version subprocess.

src/machine.ts — the substantial part. The CLI used to hand cartesi-machine a command line and let it assemble the machine configuration. It now does that translation itself, transcribed from cartesi-machine.lua v0.21.0:

  • default dtb.bootargs from the emulator, with machine.boot_args appended
  • dtb.init: the splash, then dev=$(flashdrive <label>) + mount + chown per non-root drive, then export K="V", WORKDIR, USER — in the order the old argv produced
  • flash drives with root first, so it lands on /dev/pmem0 regardless of the order drives appear in cartesi.toml
  • virtio console, console=hvc1 and iunrep=1 for cartesi shell

buildMachineConfig is a pure function so this is unit-testable without running a machine; src/exec/cartesi-machine.ts holds the run loop (automatic yields acknowledged, console I/O resumed, guest exit code read from htif_tohost_data).

src/images.ts (new) — with no SDK image to take linux.bin from, the default ram_image is fetched from the pinned cartesi/machine-linux-image v0.21.0 release on first use, verified against its SHA-256 and cached under $XDG_CACHE_HOME/cartesi/images. A CARTESI_IMAGES_PATH directory holding the image is used when set, and machine.ram_image still wins over both.

Breaking

  • Machine hashes change. @deroll/cm links against emulator 0.21, the SDK image pins 0.20. Applications need redeploying.
  • Boot args are no longer double quoted. The old code passed --append-bootargs="<arg>" with no shell in between, so the quotes ended up in the kernel command line. Building the configuration directly, reproducing that would have meant reproducing a bug.
  • The standalone binaries are gone. A Bun single-file executable has no node_modules, and both addons resolve their platform .node at runtime — a host-native --compile fails too, so cross-compiling four targets was never going to work. The compile step in build.ts and the upload step in release.yaml are removed. The homebrew formula in cartesi/homebrew-tap needs to install from npm rather than the release tarball. Happy to revert this bit and solve distribution differently if you'd rather keep the binaries.

Testing

  • 169 unit tests pass; tests/unit/machine.test.ts is new and covers the configuration translation (mount points, drive ordering, environment precedence, workdir/user, interactive mode, parseMemorySize).
  • The version-reporting tests for genext2fs and cartesi-machine moved from tests/integration/ to tests/unit/, since neither needs Docker anymore, and the genext2fs one now actually builds images and asserts they are reproducible.
  • Ran the built CLI end-to-end on a sample project: drives built through the bindings, kernel downloaded and cached, machine created and stored, and the stored hash matched the final hash.
  • cartesi-machine-stored-hash.test.ts asserted a literal hash that the emulator bump invalidates. It now asserts shape and stability — worth pinning the real value once CI produces it.

Not verified locally: a successful rollup application boot, which needs Docker with riscv64. The integration tests cover that path.


Generated by Claude Code

claude added 2 commits August 4, 2026 21:53
…bindings

Build ext2 drives with @deroll/genext2fs and configure, boot, store and hash
the Cartesi machine with @deroll/cm, instead of spawning xgenext2fs,
cartesi-machine and cartesi-machine-stored-hash (falling back to running them
inside the SDK docker image).

machine.ts now translates a cartesi.toml Config into an emulator MachineConfig
directly, mirroring what the cartesi-machine CLI does with its command line:
the boot args it appends to, the init script (splash, flash drive mounts and
chowns, environment exports, WORKDIR and USER), the flash drives with root
first so it lands on pmem0, and the virtio console setup for an interactive
shell. buildMachineConfig is a pure function, covered by unit tests.

With no SDK image to take the kernel from, images.ts downloads the pinned
cartesi/image-kernel release on first use, verifies its checksum and caches it
under XDG_CACHE_HOME. CARTESI_IMAGES_PATH and machine.ram_image still win.

The emulator moves from 0.20 to 0.21, so machine hashes change. The standalone
binaries are gone: a bun single file executable has no node_modules, and both
addons resolve their platform .node at runtime, so they cannot be embedded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTEd5g3mF849BATTstssR3
cartesi/image-kernel is the repository's old name; use the canonical one. Same
release, same artifact, same checksum — GitHub was serving it through a
redirect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTEd5g3mF849BATTstssR3
@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 99bb3fa

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cartesi/cli Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​deroll/​cm@​0.2.0-alpha.4681008792100
Added@​deroll/​genext2fs@​0.2.0-alpha.0741001008670

View full report

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 95.17% (🎯 0%) 5061 / 5318
🔵 Statements 95.17% 5061 / 5318
🔵 Functions 89.94% 143 / 159
🔵 Branches 0% 0 / 0
📁 File Coverage (20 files)
File Lines Statements Functions Branches Uncovered Lines
apps/cli/src/builder/directory.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/builder/docker.ts 🟢 86.61% 🟢 86.61% 🟡 66.67% 🔴 0% 75-77, 79, 109-111, 168-177
apps/cli/src/builder/empty.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/builder/none.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/builder/tar.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/compose/builder.ts 🟢 99.79% 🟢 99.79% 🟢 100% 🔴 0% 228
apps/cli/src/compose/common.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/compose/node.ts 🟢 99.24% 🟢 99.24% 🟢 100% 🔴 0% 106
apps/cli/src/config.ts 🟢 94.32% 🟢 94.32% 🟢 95.24% 🔴 0% 75-76, 251, 260, 269, 363, ...
apps/cli/src/contracts.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
...rc/errors/ForkChainValidationError.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
...c/errors/UnsupportedForkChainError.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
...c/exec/cartesi-machine-stored-hash.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/exec/cartesi-machine.ts 🔴 16.9% 🔴 16.9% 🔴 50% 🔴 0% 43-45, 54-109
apps/cli/src/exec/genext2fs.ts 🟢 90.91% 🟢 90.91% 🟢 100% 🔴 0% 31-35
apps/cli/src/exec/index.ts 🟢 100% 🟢 100% 🟢 100% 🔴 0% -
apps/cli/src/exec/mksquashfs.ts 🟢 91.53% 🟢 91.53% 🟢 100% 🔴 0% 70-74
apps/cli/src/exec/util.ts 🟢 85.11% 🟢 85.11% 🟡 66.67% 🔴 0% 24-28, 68-69
apps/cli/src/images.ts 🔴 16.13% 🔴 16.13% 🔴 0% 🔴 0% 20-21, 27-30, 39-41, 45-49,...
apps/cli/src/machine.ts 🟢 86.52% 🟢 86.52% 🟢 84.62% 🔴 0% 22-23, 187, 275, 290, 293, ...

tuler commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

Note on the @deroll dependency

This PR depends on @deroll/cm@alpha and @deroll/genext2fs@alpha, which are personal-scope packages. That is expected to be temporary.

Per cartesi/rollups-ts#133 (comment):

Next step is to bring @deroll/cmio here as @cartesi/rollup and @deroll/cm as @cartesi/machine.
Will wait for machine-emulator 0.21.0 release for that.

So @deroll/cm@cartesi/machine is already planned, and the release it was waiting on is the one this PR pins (@deroll/cm@0.2.0-alpha.4 links against emulator 0.21.0). Earlier in that thread the per-platform packages are described as @cartesi/machine-{darwin,linux}-{arm64,x64}, "currently published as @deroll/cm" — so the platform optionalDependencies get renamed alongside the main package.

Once those land, the migration here should be a rename rather than a rework: the API surface is the same, and the only imports to touch are

  • src/exec/cartesi-machine.ts
  • src/exec/cartesi-machine-stored-hash.ts
  • src/machine.ts (types only)
  • the external array in apps/cli/build.ts

@deroll/genext2fs is newer than that comment and isn't named in the plan, so it's worth deciding separately whether it also moves to @cartesi/* — it is the one dependency here under GPL-2.0-only (inherited from xgenext2fs), while the CLI is Apache-2.0.


Generated by Claude Code

image: TEST_SDK,
cwd: machineDir,
});
it("should return the hash of a stored machine", async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants