Skip to content

feat(ruby): Ruby language bindings#5178

Draft
omarqureshi wants to merge 9 commits into
aws:mainfrom
omarqureshi:ruby-language-bindings
Draft

feat(ruby): Ruby language bindings#5178
omarqureshi wants to merge 9 commits into
aws:mainfrom
omarqureshi:ruby-language-bindings

Conversation

@omarqureshi

@omarqureshi omarqureshi commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Objective

Introduces comprehensive Ruby language bindings for the jsii ecosystem, enabling AWS CDK (and other jsii-based libraries) to be packaged as gems and consumed idiomatically from Ruby. It adds the code-generation target (jsii-pacmak) and the core Ruby runtime (@jsii/ruby-runtime), with strict type-safety, careful object lifecycle management, and thread-safe IPC with the jsii Node kernel.

Implements RFC 0935: Ruby language bindings. The change is strictly additive — no breaking changes to existing languages and no assembly-format change beyond an additive targets.ruby schema extension.

Warning

CI is expected to be red on this branch, by design. Ruby code generation requires a jsii-compiler release with Ruby target support, which is not yet published. Until it is, the following failures are expected and are not indicative of broken work:

  • jsii-pacmak target-ruby snapshot test — this branch builds with the stock compiler (no Ruby support), so the committed assemblies carry no targets.ruby and generated names won't match the snapshot.
  • @jsii/ruby-runtime-test (generate.sh + rspec) — cannot generate the Ruby lib/ without the Ruby-capable compiler.
  • "Assert clean working directory" — a build would rewrite the assemblies.

Once the Ruby-capable compiler is released, the committed fixtures (assemblies with targets.ruby + the Ruby snapshot) are regenerated against it and CI goes green. End-to-end regeneration + publish has already been validated against the fork compiler.

Key Components

1. Code Generation (jsii-pacmak)

  • RubyTarget & RubyGenerator (packages/jsii-pacmak/lib/targets/ruby.ts): translate jsii types into idiomatic Ruby — class < Jsii::Object, behavioral interfaces as mixin modules, datatype structs as kwargs value-classes (< Jsii::Struct), enums as Jsii::Enum wrapper constants, async + sync methods, and property/method overrides.
  • Generated RBS signatures: every class, module, method, and struct gets a .rbs; output validates clean under rbs validate across jsii-calc and its dependency closure.
  • Idiomatic naming: snake_case members, PascalCase modules (configurable acronyms), UPPER_SNAKE constants, reserved-word mangling (next_next). Root namespace AWSCDK with AWS-prefix de-duplication (aws-s3AWSCDK::S3::Bucket).
  • Robust namespacing: pre-declares Ruby namespaces (module X; end) in dependency order to prevent NameError during nested type generation.
  • Security & type checking: injects Jsii::Type.check_type for runtime enforcement, and embeds type metadata as Base64-encoded JSON literals so no jsii-supplied identifier or docstring can inject into generated source via string interpolation.
  • Version management: shared version-utils.ts translates npm semver (incl. pre-release .alpha.1/.dev.1) to RubyGems ranges.

2. Ruby Runtime Core (@jsii/ruby-runtime)

  • Thread-safe IPC kernel: a re-entrant Monitor makes the bidirectional JSON-RPC stdio pipe safe under concurrency and re-entrant callbacks; an isolated stderr-draining thread prevents pipe deadlocks.
  • Callbacks & overrides: JS→Ruby callbacks (including super crossing back to the JS implementation), with user subclasses detected at construction time.
  • Object registry & lifecycle: proxies created via .allocate, mapped by $jsii.byref; deliberately never issues a guest-initiated del (matching the Python/Go runtimes) — cleanup is wholesale on kernel shutdown, which suits short-lived synth workloads.
  • Type-driven serialization: a dedicated Jsii::Serializer (rather than monkey-patching core types) handles by-value structs vs by-reference objects, avoiding leakage of host application state.

3. Compliance & Testing (@jsii/ruby-runtime-test)

  • Full compliance suite covering all 123 canonical compliance cases (diamond inheritance, async/sync overrides, property overrides, hash coercion, unions, nullability, collections, naming).
  • Ruby-specific unit specs: serializer round-trips, kernel concurrency, lazy autoload, and rbs validate. No skipped/pending tests.

4. CI / Build

  • GitHub Actions: Ruby 3.3 set up on every matrix leg, plus dedicated Ruby 3.4 and 4.0 legs; buildspec.yaml CodeBuild runtime added.
  • Wired into the compliance report (tools/jsii-compliance).

Compatibility & Known Limitations

  • Ruby 3.3+ on MRI (CRuby) only — gemspecs enforce required_ruby_version >= 3.3.0 (3.1/3.2 are EOL). JRuby/TruffleRuby are out of scope for the initial release.
  • Ambiguous unions: a Hash passed where a union admits more than one struct type is forwarded as-is; coercion only happens when exactly one struct arm makes it unambiguous.
  • Hash keys must be snake_case when supplied for struct coercion.
  • Async static methods go through the synchronous kernel path (the kernel's begin API only accepts object references — a limitation shared by all bindings).

Notes & Next Steps

  • Rosetta: the jsii-rosetta translation backend for Ruby is not included here and will follow in a subsequent PR.
  • Test fixtures: the committed test assemblies currently follow main and do not yet carry targets.ruby (it's compiler-generated). They — and the target-ruby snapshot — will be regenerated against the released Ruby-capable compiler before merge. See the warning above.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Omar Qureshi and others added 2 commits June 20, 2026 19:49
Adds Ruby as a jsii target language:

- jsii-pacmak Ruby target (lib/targets/ruby.ts): generates idiomatic
  Ruby gems from .jsii assemblies — snake_case members, PascalCase
  modules with configurable acronyms, struct value classes with
  hash-to-struct coercion (recursive through collections and
  unambiguous union arms), Jsii::Enum constants, YARD documentation,
  runtime type checking, reserved-word and runtime-API name
  protection, statics emitted on their defining class (ES6
  semantics), and complete gemspec metadata. Covered by unit tests
  and generated-code snapshots in the cross-language harness.

- @jsii/ruby-runtime: the Ruby runtime gem — thread-safe JSON-RPC
  kernel client (re-entrant Monitor, stderr drain thread), object
  registry with allocate-based hydration and reference identity,
  guarded method_missing fallback with respond_to? discipline,
  explicit Jsii::Serializer (no core-class monkey-patching),
  structural type validation, diamond-struct conformance for
  is_a?/case dispatch, and callback dispatch matching the
  generator's name mapping (enforced by a consistency spec).

- @jsii/ruby-runtime-test: 123/123 standard compliance suite tests
  (reported via tools/jsii-compliance into the shared compliance
  matrix) plus unit suites — 240 examples total.

- CI: Ruby toolchain in build/test jobs; alternate-Ruby matrix row
  (3.4; 3.3 is the baseline). Supported: MRI >= 3.3 (3.1/3.2 EOL).

Note: jsii-calc pins jsii ^6.0.0 — the targets.ruby schema lands in
the compiler via aws/jsii-compiler#2663. Until a release carrying it
exists, yarn.lock intentionally lags package.json (it still resolves
the development-fork compiler); the lockfile will be regenerated as
part of the final rebase. The working development branch is
ruby-language-bindings-self-publish.

Tracking issue: aws#5129

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Ruby pacmak target emitted jsii @example docs verbatim, leaving
TypeScript snippets in generated Ruby docstrings. Wire in RosettaTabletReader
and translate each example to Ruby (TargetLanguage.RUBY), mirroring the Python
target. Examples degrade to the original source if translation fails.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@omarqureshi
omarqureshi force-pushed the ruby-language-bindings branch from 0ea38eb to d2c88dd Compare July 15, 2026 08:12
omarqureshi and others added 6 commits July 16, 2026 10:15
Mirror the Python target's emitModuleDocumentation: for the assembly and
each submodule that has a README, translate its TypeScript samples to Ruby
via Rosetta (translateSnippetsInMarkdown, same tablet path as @example) and
emit the result as the module's YARD docstring in a doc-only `_readme.rb`
inside the module's directory, so YARD renders it on the module page.

This surfaces CDK's real usage docs — which live in module READMEs, not
per-type @example tags — in the Ruby API reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
22182fe added jsii-calc / @scope/jsii-calc-base-of-base@^2.1.1 devDeps to
@jsii/ruby-runtime and @jsii/ruby-runtime-test but left yarn.lock lagging.
A plain install tolerates that, but CI's `yarn install --immutable` forbids
any lockfile modification, so Main failed at "Install Dependencies" on this
branch. Record the resolutions (workspace-only; no new registry deps).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The committed snapshot predated the autoload + RBS refactor, so it no longer
matched the generated code (Unit Tests failed). Regenerate it: this captures
the current per-type autoload layout, the sig/*.rbs signatures, and the new
per-submodule _readme.rb module-docstring files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`@example` tags were translated to Ruby, but fenced code blocks inside a
member's `remarks` prose were emitted verbatim — so e.g. CfnResource#addOverride
showed a TypeScript sample (`cfnResource.addOverride('Properties…', […])`) in
the Ruby docs. Run `remarks` through convertMarkdown when an apiLocation is
present, mirroring the Python target, so embedded ```ts samples translate too.

Regenerate the ruby snapshot: the change relabels a remarks fence ts->ruby and
normalizes some markdown (unescaping, bullet style) in jsii-calc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s, requires)

Three display-layer fixes for translated docs prose:

- rubifyInlineRefs: translate inline `code` API references in prose to snake_case
  (`bucketArn` -> `bucket_arn`, `arnForObjects(pattern)` -> `arn_for_objects(pattern)`),
  matching the real member names. Literals/types/enums are left alone.
- Strip HTML comments (CDK's `<!--BEGIN STABILITY BANNER-->` / CFNONLY markers),
  which YARD's Markdown renderer would otherwise show as visible text.
- Strip `require` boilerplate from examples: it comes from the snippet fixture and,
  after import translation, collapses to duplicate `require 'aws-cdk-lib'` lines.
  Applied to @example (convertExample) and README/remarks fenced blocks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Requires: keep a single `require 'aws-cdk-lib'` per code block instead of
  stripping them entirely. Stripping left import-only blocks empty, and YARD's
  highlighter renders an empty ```ruby block as a literal `</code>` — the stray
  tag seen in the ACMPCA README. Deduping keeps the block non-empty.
- normalizeFences: some READMEs put a fenced block's first line on the fence-open
  line (e.g. EKSv2's ASCII diagram `\`\`\`text   +---`). redcarpet then renders it
  as a paragraph (CommonMark/GitHub opens the fence but drops that line). Split
  the trailing content to its own line so the fence opens and the line is kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant