From 88e2ee5a8cbcb2a5519b0a393d827f98b7d7eb61 Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Wed, 1 Jul 2026 02:52:09 -0400 Subject: [PATCH] ruby: enable and verify psych/YAML on Kandelo main already builds Ruby with psych/YAML (libyaml 0.2.5, --with-ext=...,psych,...), but ships no ruby.rb Homebrew formula and no runtime test that YAML actually works. Add the psych-including formula plus a Node runtime smoke, and verify psych at runtime. - homebrew/kandelo-homebrew/Formula/ruby.rb: Ruby 4.0.5 formula whose test block exercises require 'yaml' + a YAML.dump/load round-trip (not just the interpreter). - packages/registry/ruby/demo/yaml-smoke.ts: Node runtime smoke that runs the built ruby.wasm under the kernel host (host-fs passthrough so RUBYLIB resolves) and asserts the YAML round-trip. Verified under Node: psych=5.3.1, libyaml=0.2.5; require 'yaml' + YAML.dump/load round-trip pass. Relies on main's psych-enabled build-ruby.sh and does not modify it. PR #810 (kd-p3hr) removes psych from build-ruby.sh (--with-out-ext=...,psych); that removal must be reconciled so it does not regress psych/YAML off main. Co-Authored-By: Claude Opus 4.8 --- homebrew/kandelo-homebrew/Formula/ruby.rb | 51 ++++++++++++++ packages/registry/ruby/demo/yaml-smoke.ts | 82 +++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 homebrew/kandelo-homebrew/Formula/ruby.rb create mode 100644 packages/registry/ruby/demo/yaml-smoke.ts diff --git a/homebrew/kandelo-homebrew/Formula/ruby.rb b/homebrew/kandelo-homebrew/Formula/ruby.rb new file mode 100644 index 000000000..a2c561335 --- /dev/null +++ b/homebrew/kandelo-homebrew/Formula/ruby.rb @@ -0,0 +1,51 @@ +require_relative "../Kandelo/formula_support/kandelo_package" + +class Ruby < Formula + include KandeloPackageFormula + SOURCE_URL = "https://cache.ruby-lang.org/pub/ruby/4.0/ruby-4.0.5.tar.gz" + SOURCE_SHA256 = "7d6149079a63f8ae1d326c9fa65c6019ba2dc3155eae7b39159817911c88958e" + + desc "Ruby interpreter for Kandelo (with psych/YAML)" + homepage "https://www.ruby-lang.org/" + url SOURCE_URL + sha256 SOURCE_SHA256 + license any_of: ["Ruby", "BSD-2-Clause"] + + depends_on "automattic/kandelo-homebrew/zlib" + + skip_clean "bin" + skip_clean "lib/ruby" + + def install + out_dir = kandelo_build_package("ruby", "build-ruby.sh", SOURCE_URL, SOURCE_SHA256, + script_env: { + "RUBY_VERSION" => version.to_s, + "WASM_POSIX_DEP_ZLIB_DIR" => Formula["automattic/kandelo-homebrew/zlib"].opt_prefix, + }) + kandelo_install_bin(out_dir, "ruby.wasm", "ruby") + + runtime_stage = buildpath/"ruby-runtime-stage" + system "unzip", "-q", out_dir/"ruby-runtime.zip", "-d", runtime_stage + (lib/"ruby").install Dir["#{runtime_stage}/usr/lib/ruby/*"] + bin.install Dir["#{runtime_stage}/usr/bin/*"] + end + + test do + rubylib = (lib/"ruby/4.0.0").to_s + env = { "RUBYLIB" => rubylib, "HOME" => testpath.to_s } + + output = kandelo_run_wasm(bin/"ruby", ["-e", "puts 'ruby-ok'"], env: env) + assert_equal "ruby-ok\n", output + + # psych/YAML: require 'yaml' and a YAML.dump/load round-trip must work. + yaml_prog = <<~RUBY + require 'yaml' + data = { 'name' => 'kandelo', 'nums' => [1, 2, 3], 'nested' => { 'ok' => true } } + loaded = YAML.load(YAML.dump(data)) + raise 'yaml roundtrip mismatch' unless loaded == data + puts 'yaml-ok' + RUBY + yaml_output = kandelo_run_wasm(bin/"ruby", ["-e", yaml_prog], env: env) + assert_equal "yaml-ok\n", yaml_output + end +end diff --git a/packages/registry/ruby/demo/yaml-smoke.ts b/packages/registry/ruby/demo/yaml-smoke.ts new file mode 100644 index 000000000..2593b5506 --- /dev/null +++ b/packages/registry/ruby/demo/yaml-smoke.ts @@ -0,0 +1,82 @@ +/** + * yaml-smoke.ts — kd-egn1 psych/YAML runtime smoke for Ruby on Kandelo. + * + * Runs the built ruby.wasm under the Node kernel host (raw host-fs passthrough + * so RUBYLIB resolves) and exercises require 'yaml' + YAML.dump/load round-trip. + * + * Usage: + * bash build.sh && bash packages/registry/ruby/build-ruby.sh + * npx tsx packages/registry/ruby/demo/yaml-smoke.ts + * + * Exit 0 + "YAML_SMOKE_PASS" on success; non-zero otherwise. + */ +import { existsSync, readdirSync } from "fs"; +import { resolve, dirname } from "path"; +import { runCentralizedProgram } from "../../../../host/test/centralized-test-helper"; +import { NodePlatformIO } from "../../../../host/src/platform/node"; + +const scriptDir = dirname(new URL(import.meta.url).pathname); +const repoRoot = resolve(scriptDir, "../../../.."); + +// Locate the installed Ruby stdlib dir (the one containing yaml.rb). The build's +// make-install fallback may place it under ruby-install/usr/lib/ruby/ or +// ruby-install/lib/ruby/; RUBY_SMOKE_LIB overrides for ad-hoc runs. +function findRubyLib(): string | null { + if (process.env.RUBY_SMOKE_LIB) return process.env.RUBY_SMOKE_LIB; + const roots = [ + resolve(repoRoot, "packages/registry/ruby/ruby-install/usr/lib/ruby"), + resolve(repoRoot, "packages/registry/ruby/ruby-install/lib/ruby"), + ]; + for (const root of roots) { + if (!existsSync(root)) continue; + for (const ver of readdirSync(root)) { + const dir = resolve(root, ver); + if (existsSync(resolve(dir, "yaml.rb"))) return dir; + } + } + return null; +} + +const RUBY_SCRIPT = [ + "require 'yaml'", + "data = { 'name' => 'kandelo', 'nums' => [1, 2, 3], 'nested' => { 'ok' => true, 'pi' => 3.14 } }", + "dumped = YAML.dump(data)", + "raise 'YAML.dump produced empty output' if dumped.nil? || dumped.empty?", + "loaded = YAML.load(dumped)", + "raise \"YAML round-trip mismatch: #{loaded.inspect}\" unless loaded == data", + "pv = (defined?(Psych::VERSION) ? Psych::VERSION : 'unknown')", + "lv = (defined?(Psych::LIBYAML_VERSION) ? Psych::LIBYAML_VERSION : 'unknown')", + "puts \"psych=#{pv} libyaml=#{lv}\"", + "puts 'YAML_SMOKE_PASS'", +].join("\n"); + +async function main() { + const rubyWasm = resolve(repoRoot, "packages/registry/ruby/bin/ruby.wasm"); + if (!existsSync(rubyWasm)) { + console.error("ruby.wasm not found. Run: bash packages/registry/ruby/build-ruby.sh"); + process.exit(1); + } + const rubyLib = findRubyLib(); + if (!rubyLib) { + console.error("Ruby stdlib (yaml.rb) not found under ruby-install; run build-ruby.sh"); + process.exit(1); + } + + const result = await runCentralizedProgram({ + programPath: rubyWasm, + argv: ["ruby", "-e", RUBY_SCRIPT], + env: [`RUBYLIB=${rubyLib}`, `HOME=/tmp`, `TMPDIR=/tmp`], + io: new NodePlatformIO(), + timeout: 300_000, + }); + + process.stdout.write(result.stdout); + if (result.stderr) process.stderr.write(result.stderr); + const ok = result.exitCode === 0 && result.stdout.includes("YAML_SMOKE_PASS"); + process.exit(ok ? 0 : (result.exitCode || 1)); +} + +main().catch((err) => { + console.error("Fatal error:", err); + process.exit(1); +});