This repository carries the tebako gem's ruby build patches as canonical,
machine-verified unified-diff .patch files. They were generated
mechanically from the gem's string-substitution patch maps
(lib/tebako/packager/*.rb, tebako-chainwt @ main): each substitution was
applied with Ruby's String#sub! (literal, first occurrence) to the pristine
upstream file extracted from the official ruby tarballs (see
versions.yml), and the result was diffed with git diff --no-index. No
diff was hand-written.
Per-line folders with YAML manifests:
patches/<major.minor>/— one folder per supported ruby line (3.1,3.2,3.3,3.4,4.0). A line's folder holds only that line's own (diverged) patch files plus its manifest(s): the basepatch-<line>.yamland, where a feature applies to one exact patch release only, an overlaypatch-<line>.<patch>.yaml(currentlypatch-3.2.7.yamlandpatch-3.3.7.yamlfor theruby3x7-gated onigmo/winmain fixes).- Home-line rule: a patch body shared by several lines lives once, in
the oldest line it applies to (its home line); other lines' manifests
reference it by relative path (
../<home-line>/<file>.patch) — never duplicated. Looking at one line's folder you see only its own files and the manifest listing which older-line patches it pulls in (limited information exposure). schema/— JSON Schemas:versions.schema.ymlforversions.yml,patches.schema.ymlfor everypatch-*.yamlmanifest. CI validates all manifests against them before use (tools/validate_manifests).versions.yml— every supported ruby version with its official tarball URL, sha256 (mirroring the gem'sRUBY_VERSIONS), major.minor line, and the platformscenariosit ships src releases for (subset oflinux-gnu/linux-musl/msys, absent means linux-gnu only; verified apply-clean per version).
Patches are grouped by feature family rather than by build phase; the
# X-Redesign: modern-api header note still marks the io-routing family
(memfs tebako_* shims, legacy API flagged for redesign): main.c,
io.c, dir.c, file.c, util.c, dln.c, prism_compile.c, ruby.c,
win32/file.c, win32/win32.c getcwd, tool/mkconfig.rb.
Everything else (link rules, toolchain compatibility,
rubygems) is build-config.
Real patch files: <feature>[_<patch_version>].patch — snake_case
throughout (features and file names). The _<patch_version> suffix marks
content tied to specific patch version(s) of its home line (e.g.
gnumakefile_in_pass1_msys_4.patch covers 3.2.4-3.2.6,
gnumakefile_in_pass1_msys_7.patch covers 3.2.7); whole-line bodies carry
no suffix.
Feature suffixes encode the build scenario: terminal _msys / _darwin /
_musl platform markers, and _pass1 / _pass2 where
cygwin/GNUmakefile.in differs between the toolchain and the final build.
A feature without a platform marker is platform-independent, or — when it
shares its target file with a platform-marked feature — the complementary
variant for the other platforms (e.g. dir_c_memfs vs dir_c_memfs_msys).
Manifest schema (schema/patches.schema.yml): version: "<line>" plus a
patches: array of entries (ordered; entries apply in listed order).
Each entry: feature: <snake_case>, file: <path> (local or
../<line>/...), and optionally version: "<patch>".
Resolution for ruby X.Y.Z (Tfs::PatchSelection):
- Read
patches/X.Y/patch-X.Y.yaml, then overlaypatches/X.Y/patch-X.Y.Z.yamlif present — the overlay wins per feature (replaced features keep their base position; overlay-only features append). - Take whole-line entries plus entries with
version: "<Z>"; a versioned entry supersedes its feature's whole-line entry for that Z only. - A feature whose entries are all versioned and do not cover Z is an
explicit error (
SelectionError), never silent. Exact-release-only features therefore live in overlays, not in the base manifest.
Every patch passes git apply --check against the pristine extracted tree of
every tarball whose major.minor line its filename claims (617 patch×version
checks, including the config.status checks below; additionally, every patch's
applied result was verified byte-identical to the gem's own String#sub!
transformation). A patch that did not apply to a whole line was narrowed to
the exact -<patchlevel>- releases it applies to; a substitution whose
pattern is absent upstream produces no patch at all (see "Drops and no-ops").
Exception: config.status is a generated file and is not shipped in the
tarballs. The config-status-mainlibs-darwin patches were generated against
and verified with git apply --check on config.status files produced by a
default ./configure of each claimed version on macOS (arm64). Context lines
may differ on hosts with different configure flags or platforms.
Every tarball this repo ships or rolls for a chain — the release assets
(_release-line.yml), the spec-22 chain mirrors (ci/spec22/,
ci/spec22-gems/) — is packed with all tar metadata clamped
(tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner; the gzip
header is already MTIME=0 through the pipe). The tarball sha256 is
content-addressed: identical patched trees MUST package to identical bytes.
Downstream consumers key their build caches and their platform-skip
decisions on that sha256 (the consumer-side owner of that architecture is
tamatebako/tebako-runtime-ruby, docs/build-chain.md). A plain
tar -czf stamps checkout-time mtimes and readdir member order, so the
same content rolls to a different sha on every run — cold-caching every
downstream build leg for changes that touched nothing it reads. Never
reintroduce one, and never add timestamps, absolute paths, or host
identifiers to anything tools/apply writes into the tree. The local
harness scripts tolerate bsdtar (macOS) without the clamp flags: their
mirror is single-machine scratch consumed in the same run.
The gem computes the tebako static library list (MAINLIBS) dynamically per
packaging host. Canonical patches carry the literal placeholder
@TEBAKO_MLIBS@, which the consumer substitutes at packaging time (gem:
PatchLibraries.mlibs).
tool/rbinstall.rb(next if files.empty?): pattern absent in every supported version (the gem's own comment already suspected this) — no patch emitted.config.statusMAINLIBS, linux and msys variants: not emitted — a real linux/msysconfig.statuscannot be produced on the generation host, and hand-writing the diff is disallowed. Source literals:patch_buildsystem.rb#get_config_status_patch.RubygemsUpdatePatch(rubygems repatch aftergem update --system): targets the installed rubygems tree (version depends on the update, not on the ruby tarball) — covered by the same substitutions as the pass1 rubygems patches against the ruby source tree.- Faithful partial no-ops kept as-is (the gem's
sub!silently no-ops too; patches contain only the hunks that match):DIR_C_BASE_PATCH'splain = 1substitution matches only ruby 3.1; thewin32/win32.ctebako include anchor (_MSC_VER <= 1200) is gone in ruby 4.0;ext/io/console/win32_vk.incwas fixed upstream in ruby 3.4+. - Dead gem code:
FILE_C_MSYS_PATCH(msysFile#flockreturning ENOTSUP for memfs fds) was never referenced by the gem; it is re-implemented with corrected semantics in the_WIN32branch offile_c_tebako_includes.patch(tfs_flock: locks on memfs fds no-op-succeed, matching the POSIX fcntl-lock shim semantics — roadmap item 18).LINUX_PATCHES(ext/extmk.rb) stays unconverted (defined but never referenced).
Thin executables over the model classes in tools/lib/tfs/
(Tfs::Versions, Tfs::PatchManifest, Tfs::PatchSelection,
Tfs::SchemaLint, Tfs::SourcePrep, Tfs::ReleaseDiff, Tfs::BuildPlan,
Tfs::ReleaseCopier; namespace parent tools/lib/tfs.rb
wires children with autoload):
tools/versions [--scenarios]— prints versions.yml as a GitHub Actions matrix document. Default: the version list (one CI lane per version).--scenarios: the flat (version × scenario build) release matrix — one row per coherent build (linux-gnuunsuffixed for back-compat, musl at pass 2 since only the msys GNUmakefile features are pass-sensitive, msys expanded to its pass1/pass2 pair).tools/validate_manifests— validates versions.yml and everypatch-*.yamlagainstschema/; run in CI before the manifests are used.tools/lint <version>— fetches the official tarball (sha256-verified, cached in.cache/tarballs, override withTFS_CACHE_DIR), extracts it, and runsgit apply --checkfor every patch resolved from the line's manifest set (each patch checked independently). Fails named:FAIL <version> <patch>.tools/apply <version> [outdir] [--platform NAME] [--pass 1|2]— emits<outdir>/tfs-ruby-<version>-src, the pristine tree with the version's patch set applied for one coherent build scenario. The full manifest set is not co-applicable to a single tree (platform-variant pairs such asdir_c_memfs/dir_c_memfs_msystarget the same lines, andcygwin/GNUmakefile.inhas alternative pass1/pass2 patches), so apply narrows the set:--platform(linux-gnu/linux-musl/darwin/msys, default: host) drops other platforms' patches and, on msys, the neutral variants of msys-patched files;--pass(default 2, final build) selects the GNUmakefile variant. A patch whose target is absent from the pristine tree (config.status, generated by ./configure) is deferred with a note; any other failure raises, naming version and patch.tools/monitor --detect | --onboard <version>— the release monitor.Tfs::RubyReleasesparses the official ruby-lang.org releases table (version + official tarball URL per row) and diffs against versions.yml (newer patch releases of tracked lines; the latest release of an untracked line inside the support window; previews excluded).Tfs::Onboarderthen onboards one release end-to-end: pins it into versions.yml (official URL + sha256 of the fetched tarball), seeds a manifest for a new line from the nearest existing line, extends complete-partition families to the new patch level, carries the line's overlay forward, and lints the whole set withgit apply --checkagainst the sha256-verified tarball. On any failure every touched file is restored — nothing is released silently.tools/smoke_matrix <release-tag>— prints the release-src compile-smoke matrix: one leg per (changed patch line × scenario) using the newest version of each line whosepatches/<line>/folder changed since the previous release tag (all lines when there is none).tools/build_matrix <release-tag> [--build|--copies|--previous-tag]— prints release-src's diff-aware build plan (fault isolation: a per-line patch change re-spends only that line). Over the sameTfs::ReleaseDiffprevious-tag base the smoke gate uses: build legs are the versions of changed patch lines, every version on a shared tooling change (tools/,ci/,schema/or any unattributable path — fail closed), and versions whose versions.yml entry is new or moved; copies legs are the remaining versions, carried forward from the previous release.tools/copy_asset <previous-tag> <asset> <dest-dir>— the carry-forward half of the plan: downloads one asset from the previous release, sha256-verifies the bytes against that release's publishedSHA256SUMS(Tfs::ReleaseCopier), and writes<asset>.sha256next to it for the publish job. A failed verification is a named error and deletes the download — a bad copy never ships.tools/compile_smoke <version> [outdir] [--platform NAME] [--pass 1|2]— the release-src compile gate. Applies the version's patch set for one scenario, configures the tree, and compiles every translation unit the selected patches touch (make <obj>per patched.c; sources that are#included into another TU —thread_pthread.c,thread_win32.c,prism_compile.c— are compiled through their including object). The patched TUs resolve the tebako memfs headers from the vendored compile-smoke stubs inci/include(they declare exactly the c_api surface the patches use, mirroring the real libtfs headers). Toolchains: linux-gnu native cc, linux-muslmusl-gcc, msys thex86_64-w64-mingw32cross gcc. Fails named:FAIL <version> (<platform>): <objects> did not compile.
CI: .github/workflows/lint-patches.yml lints every version on a matrix
generated from versions.yml; .github/workflows/release-src.yml (tags
v* + manual dispatch) builds the per-scenario
tfs-ruby-<version>-src[-<scenario>].tar.gz assets diff-aware
(tools/build_matrix): only versions of changed patch lines compile —
plus every version on a shared tooling change and versions whose
versions.yml entry is new or moved — while unchanged versions are carried
forward from the previous release as sha256-verified copies
(tools/copy_asset), so the release's asset set stays complete
(linux-gnu stays the unsuffixed back-compat asset; msys ships
-msys-pass1/-msys-pass2). Built artifacts are verified against the
apply output, and the tarballs plus a SHA256SUMS are published to the
release. Publish is gated on the compile-smoke matrix (roadmap 17.0; the
v0.2.8 lesson — a patch release shipped apply-clean but uncompilable and
broke every linux runtime leg): one representative leg per changed patch
line × scenario (newest version of the line), each compiling the patched
translation units far enough to catch a broken shim — no full runtime
build. .github/workflows/release-monitor.yml
(daily 06:17 UTC + manual dispatch) detects new official ruby releases and
onboards each on its own lane: a clean onboard opens an
"Onboard ruby X.Y.Z" pull request (peter-evans/create-pull-request), a
failing one files an issue naming the version and the failing patches,
with each patch's git apply hunk output. Optionally (repo variable
RELEASE_MONITOR_DISPATCH=true + TEBAKO_DISPATCH_TOKEN secret) a clean
onboard fires a repository_dispatch (event tebako release) to
tamatebako/tebako-runtime-ruby.
.github/workflows/runtime-pin-bump.yml closes the loop: on every
successful release-src publish (workflow_run) it reads the published tag
from the run's release-tag artifact and fires a repository_dispatch
(event tfs-ruby-src-release, TEBAKO_CI_PAT_TOKEN secret) to
tamatebako/tebako-runtime-ruby, which opens a pull request bumping its
DEFAULT_RELEASE pin to that tag.
Specs (bundle exec rspec): manifest parsing, selection/supersede/scenario
logic, and apply correctness against tiny local fixtures (no network).
Patches were produced by a script that loads the real gem patch classes (read-only), applies each map to the pristine files of every supported version, groups versions by identical diff body, and emits line-wide or per-release files accordingly. Each patch header cites the gem source constant/method and restates the gem's rationale comment.