From bc1fc22aea41c3031c5283f4b29fa6d4c25c5260 Mon Sep 17 00:00:00 2001 From: Largo Date: Sat, 8 Aug 2026 01:32:52 +0200 Subject: [PATCH 1/9] Fix bootstrap chicken-and-egg and regenerate missing ruby_shims 1. local-includes.mk fatally errored when build/bootstrap/mtdeps was missing, even during the 'cosmo_configure.sh --bootstrap' invocation whose whole purpose is to build it (make is invoked with the mkdeps stub via MKDEPS= on the command line). Skip the check when MKDEPS comes from the command line so bootstrap can run on fresh checkouts. 2. ruby.deps.mk references 395 ruby_shims/*.h mkdeps shim files that were never committed (they lived only in the original developer's working tree, produced by the also-uncommitted bin/create_shims.sh). Without them, make's .DEFAULT rule endlessly deletes and regenerates o/$(MODE)/depend. Add gen_ruby_shims.sh which deterministically rebuilds the shim directory from the entries in ruby.deps.mk. Co-Authored-By: Claude Fable 5 --- local-includes.mk | 5 ++ third_party/ruby-wip-4.0.0/gen_ruby_shims.sh | 54 ++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100755 third_party/ruby-wip-4.0.0/gen_ruby_shims.sh diff --git a/local-includes.mk b/local-includes.mk index 4dc2eec61e8..61667979171 100644 --- a/local-includes.mk +++ b/local-includes.mk @@ -26,8 +26,13 @@ include third_party/build/mtdeps/BUILD.mk include examples/rubyapps/BUILD.mk # Require bootstrap mtdeps for dependency generation. +# Exception: when MKDEPS is passed on the command line (as done by +# cosmo_configure.sh --bootstrap with the mkdeps stub), skip the check so the +# bootstrap build that produces build/bootstrap/mtdeps can actually run. ifeq ($(wildcard build/bootstrap/mtdeps),) +ifneq ($(origin MKDEPS),command line) $(error Missing build/bootstrap/mtdeps. Run third_party/ruby/cosmo_configure.sh --bootstrap) +endif else MKDEPS := build/bootstrap/mtdeps -P ruby_shims/ endif diff --git a/third_party/ruby-wip-4.0.0/gen_ruby_shims.sh b/third_party/ruby-wip-4.0.0/gen_ruby_shims.sh new file mode 100755 index 00000000000..9a454f16b65 --- /dev/null +++ b/third_party/ruby-wip-4.0.0/gen_ruby_shims.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Regenerate the ruby_shims/ directory required by mkdeps dependency scanning. +# +# ruby.deps.mk lists entries like ruby_shims/internal+array.h in +# THIRD_PARTY_RUBY_A_INCS. mkdeps (build/bootstrap/mtdeps -P ruby_shims/) +# needs those files to exist so it can read them during Stage 2 resolution +# (see docs/ai/cosmo_ruby/MKDEPS_AUTOMATION_SYSTEM.md). Historically they +# were produced by bin/create_shims.sh, which was never committed; this +# script rebuilds them deterministically from ruby.deps.mk instead. +# +# Mapping: strip "ruby_shims/", replace '+' with '/', then resolve against +# 1. repo root (entries like third_party+ruby+include+ruby.h) +# 2. third_party/ruby/ (entries like internal+array.h) +# Each shim is a one-line include of the resolved file so mkdeps picks up +# the real header's transitive dependencies. Unresolvable entries (e.g. +# generated files that don't exist yet) become empty shims, which merely +# weakens dependency tracking for those files. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +DEPS_MK="$ROOT/third_party/ruby/ruby.deps.mk" +OUT="$ROOT/ruby_shims" + +mkdir -p "$OUT" + +missing=0 +total=0 +while IFS= read -r entry; do + total=$((total + 1)) + name="${entry#ruby_shims/}" + real="${name//+//}" + shim="$OUT/$name" + resolved="" + for base in "" "third_party/ruby/" "third_party/ruby/include/"; do + if [ -f "$ROOT/$base$real" ]; then + resolved="$base$real" + break + fi + done + if [ -z "$resolved" ]; then + # Fall back to locating the header anywhere in the ruby tree + # (covers -I'd subdirs like enc/, enc/trans/, prism/, .ext includes). + resolved="$(cd "$ROOT" && find third_party/ruby/ -type f -path "*/$real" 2>/dev/null | sort | head -n1 || true)" + fi + if [ -n "$resolved" ]; then + printf '#include "%s"\n' "$resolved" > "$shim" + else + : > "$shim" + missing=$((missing + 1)) + echo "gen_ruby_shims: no source found for $entry (created empty shim)" >&2 + fi +done < <(grep -o 'ruby_shims/[^ \\]*' "$DEPS_MK" | sort -u) + +echo "gen_ruby_shims: wrote $total shims to $OUT ($missing empty)" From 7c56510c6d5b68fa6be0f023a8b656d1fefa65ad Mon Sep 17 00:00:00 2001 From: Largo Date: Sat, 8 Aug 2026 01:37:18 +0200 Subject: [PATCH 2/9] Parallelize cosmo_configure.sh --bootstrap make invocations The bootstrap block hard-coded make -j1, which on a clean checkout means building a large part of cosmopolitan libc single-threaded. Use -j$(nproc) by default, overridable via BOOTSTRAP_JOBS for debugging. Co-Authored-By: Claude Fable 5 --- third_party/ruby-wip-4.0.0/cosmo_configure.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/third_party/ruby-wip-4.0.0/cosmo_configure.sh b/third_party/ruby-wip-4.0.0/cosmo_configure.sh index ffaf990059a..2a292faa992 100755 --- a/third_party/ruby-wip-4.0.0/cosmo_configure.sh +++ b/third_party/ruby-wip-4.0.0/cosmo_configure.sh @@ -56,8 +56,8 @@ echo "cosmo_configure: MODE=${MODE} (using $RUBY_BIN)" exit 1 fi echo "cosmo_configure: building automate_mkdeps + mtdeps with stub mkdeps" - make -C "$ROOT" MKDEPS="$STUB" -j1 o//third_party/build/mtdeps/automate_mkdeps - make -C "$ROOT" MKDEPS="$STUB" -j1 o//third_party/build/mtdeps/mtdeps + make -C "$ROOT" MKDEPS="$STUB" -j"${BOOTSTRAP_JOBS:-$(nproc)}" o//third_party/build/mtdeps/automate_mkdeps + make -C "$ROOT" MKDEPS="$STUB" -j"${BOOTSTRAP_JOBS:-$(nproc)}" o//third_party/build/mtdeps/mtdeps mkdir -p "$ROOT/build/bootstrap" cp "$ROOT/o//third_party/build/mtdeps/automate_mkdeps" "$ROOT/build/bootstrap/automate_mkdeps" cp "$ROOT/o//third_party/build/mtdeps/mtdeps" "$ROOT/build/bootstrap/mtdeps" From b37e7e4b82210516b4222dc32d42abd7c3aba2ed Mon Sep 17 00:00:00 2001 From: Largo Date: Sat, 8 Aug 2026 01:38:46 +0200 Subject: [PATCH 3/9] Restore accidentally deleted buildlib.a archive rules Commit bd1ce479a reordered variable definitions in tool/build/lib/BUILD.mk and dropped the $(TOOL_BUILD_LIB_A): and $(TOOL_BUILD_LIB_A).pkg: rules in the process. Without prerequisites the generic archive rule packs an empty buildlib.a, and everything that links against it (automate_mkdeps, mtdeps, rubyobj, ...) fails with undefined references to getargs_* et al. Co-Authored-By: Claude Fable 5 --- tool/build/lib/BUILD.mk | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tool/build/lib/BUILD.mk b/tool/build/lib/BUILD.mk index 61b13950264..0af37bbc61c 100644 --- a/tool/build/lib/BUILD.mk +++ b/tool/build/lib/BUILD.mk @@ -67,6 +67,15 @@ TOOL_BUILD_LIB_BINS = \ $(TOOL_BUILD_LIB_COMS) \ $(TOOL_BUILD_LIB_COMS:%=%.dbg) +$(TOOL_BUILD_LIB_A): \ + tool/build/lib/ \ + $(TOOL_BUILD_LIB_A).pkg \ + $(TOOL_BUILD_LIB_A_OBJS) + +$(TOOL_BUILD_LIB_A).pkg: \ + $(TOOL_BUILD_LIB_A_OBJS) \ + $(foreach x,$(TOOL_BUILD_LIB_A_DIRECTDEPS),$($(x)_A).pkg) + ifeq ($(ARCH), x86_64) o/$(MODE)/tool/build/lib/ssefloat.o: private \ TARGET_ARCH += \ From 4ef0cb033269e80c9089d639f70de260634a5912 Mon Sep 17 00:00:00 2001 From: Largo Date: Sat, 8 Aug 2026 01:41:09 +0200 Subject: [PATCH 4/9] Make parse.c include parse.h via a portable path The committed lrama/bison-generated parse.c contained #include "/home/groobiest/Code/jart/cosmopolitan/o/third_party/ruby/generated/parse.h" i.e. an absolute path from the original developer's machine, which breaks compilation on any other checkout. Point it at the committed third_party/ruby/parse.h instead (same generated header). The ~900 remaining #line directives referencing that path are cosmetic only. Co-Authored-By: Claude Fable 5 --- third_party/ruby-wip-4.0.0/parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/ruby-wip-4.0.0/parse.c b/third_party/ruby-wip-4.0.0/parse.c index 3b5f3136e88..5f33fdb22de 100644 --- a/third_party/ruby-wip-4.0.0/parse.c +++ b/third_party/ruby-wip-4.0.0/parse.c @@ -391,7 +391,7 @@ RBIMPL_WARNING_POP() // Not sure why effective even after popped. #endif -#include "/home/groobiest/Code/jart/cosmopolitan/o/third_party/ruby/generated/parse.h" +#include "third_party/ruby/parse.h" #define NO_LEX_CTXT (struct lex_context){0} From 4d35c1ee067758330a5b1f37dcb23819113a5971 Mon Sep 17 00:00:00 2001 From: Largo Date: Sat, 8 Aug 2026 01:42:06 +0200 Subject: [PATCH 5/9] Allow CARGO override in yjit/BUILD.mk CARGO was assigned with := from $(shell command -v cargo), which comes up empty under the cocmd build shell even when cargo is installed, and could not be overridden from the environment. Use ?= so callers can pass CARGO=/usr/bin/cargo. Co-Authored-By: Claude Fable 5 --- third_party/ruby-wip-4.0.0/yjit/BUILD.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/ruby-wip-4.0.0/yjit/BUILD.mk b/third_party/ruby-wip-4.0.0/yjit/BUILD.mk index bfbbb4c1dd5..58746b026ce 100644 --- a/third_party/ruby-wip-4.0.0/yjit/BUILD.mk +++ b/third_party/ruby-wip-4.0.0/yjit/BUILD.mk @@ -19,7 +19,7 @@ endif ifeq ($(RUBY_YJIT_ENABLED),1) -CARGO := $(shell command -v cargo 2>/dev/null) +CARGO ?= $(shell command -v cargo 2>/dev/null) # Track Rust source files for dependency detection YJIT_RUST_SRCS := $(wildcard \ From 6a7c9507d6a6f8469c05d2d348e9cbd8d92a352b Mon Sep 17 00:00:00 2001 From: Largo Date: Sat, 8 Aug 2026 01:44:43 +0200 Subject: [PATCH 6/9] Serialize encdb.h/transdb.h generation to fix parallel build race Both recipes invoke ruby_prepare_encdir, which rm -rf's and recopies the shared $(RUBY_ENC_TMPDIR). Under make -jN they can run concurrently, so one recipe deletes enc sources while the other is mid-scan, yielding nondeterministic failures like: x_emoji.h:18: ENC_REPLICATE: stateless-ISO-2022-JP is not defined yet. Make transdb.h order-only depend on encdb.h. This is likely the reason the docs recommend building Ruby with -j1. Co-Authored-By: Claude Fable 5 --- third_party/ruby-wip-4.0.0/ruby.codegen.mk | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/third_party/ruby-wip-4.0.0/ruby.codegen.mk b/third_party/ruby-wip-4.0.0/ruby.codegen.mk index 99d1272ceb0..bdd058be4ab 100644 --- a/third_party/ruby-wip-4.0.0/ruby.codegen.mk +++ b/third_party/ruby-wip-4.0.0/ruby.codegen.mk @@ -236,7 +236,11 @@ THIRD_PARTY_RUBY_PATCHES += $(RUBY_ENCDB_PATCH) RUBY_TRANSDB_GEN := $(RUBY_GENDIR)/transdb.h -$(RUBY_TRANSDB_GEN): $(RUBY_TOOLDIR)/generic_erb.rb $(RUBY_SRCDIR)/template/transdb.h.tmpl | $(RUBY_GENDIR) +# Order-only dependency on encdb.h: both recipes call ruby_prepare_encdir, +# which rm -rf's and recreates the shared $(RUBY_ENC_TMPDIR). Running them +# concurrently under make -jN makes one recipe delete enc sources while the +# other is still scanning them (ENC_REPLICATE "not defined yet" failures). +$(RUBY_TRANSDB_GEN): $(RUBY_TOOLDIR)/generic_erb.rb $(RUBY_SRCDIR)/template/transdb.h.tmpl | $(RUBY_GENDIR) $(RUBY_ENCDB_GEN) $(call ruby_prepare_encdir) @cd $(RUBY_GENDIR) && $(HOST_RUBY) --disable=gems $(abspath $(RUBY_TOOLDIR))/generic_erb.rb -c -o $(abspath $@) $(abspath $(RUBY_SRCDIR)/template/transdb.h.tmpl) $(abspath $(RUBY_TRANS_TMPDIR)) @$(RUBY_ENV) $(HOST_RUBY) --disable=gems $(RUBY_ADD_TO_MANIFEST) $(RUBY_MANIFEST) transdb.h third_party/ruby/enc/transdb.h From 83b00387b06f145c50cb3486cab0bffc2dbbe399 Mon Sep 17 00:00:00 2001 From: Largo Date: Sat, 8 Aug 2026 01:46:07 +0200 Subject: [PATCH 7/9] Use committed config.h as codegen reference The generated/config.h rule required docs/reference/_ext_config_ruby_orig_3_4_7.h, a file that exists only in the original developer's working tree (never committed on any branch), making the codegen phase fail on fresh checkouts. Substitute the committed third_party/ruby/include/ruby/config.h; the chain that consumes it (generated rbconfig.rb, enc.mk, configure-ext.mk, exts.mk) is comparison-only, so this merely produces empty reference diffs. Co-Authored-By: Claude Fable 5 --- third_party/ruby-wip-4.0.0/ruby.codegen.mk | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/third_party/ruby-wip-4.0.0/ruby.codegen.mk b/third_party/ruby-wip-4.0.0/ruby.codegen.mk index bdd058be4ab..014aba038fb 100644 --- a/third_party/ruby-wip-4.0.0/ruby.codegen.mk +++ b/third_party/ruby-wip-4.0.0/ruby.codegen.mk @@ -123,7 +123,13 @@ THIRD_PARTY_RUBY_GENERATED += $(RUBY_MANIFEST) ################################################################################ # config.h - copy reference config.h to generated directory -$(RUBY_GENDIR)/config.h: docs/reference/_ext_config_ruby_orig_3_4_7.h $(RUBY_MANIFEST) | $(RUBY_GENDIR) +# The original recipe copied docs/reference/_ext_config_ruby_orig_3_4_7.h +# (the pristine config.h from the author's ruby-under-cosmocc configure run), +# but that file was never committed to the repository. Fall back to the +# committed cosmo config.h: the generated/config.h chain only feeds +# comparison/manifest artifacts (generated rbconfig.rb, enc.mk, exts.mk are +# "comparison only"), so the practical effect is empty reference diffs. +$(RUBY_GENDIR)/config.h: third_party/ruby/include/ruby/config.h $(RUBY_MANIFEST) | $(RUBY_GENDIR) @cp $< $@ @$(RUBY_ENV) $(HOST_RUBY) --disable=gems $(RUBY_ADD_TO_MANIFEST) $(RUBY_MANIFEST) config.h third_party/ruby/include/ruby/config.h From d071ca1575949d8c3345d647c5972500fb16b187 Mon Sep 17 00:00:00 2001 From: Largo Date: Sat, 8 Aug 2026 01:47:59 +0200 Subject: [PATCH 8/9] Fix mtsh compilation and cocmd-incompatible recipe comments - mtsh.o and mtsh_embed.o rules listed prerequisites but had no compile recipe; their sources live in the mtsh/ subdirectory so no pattern rule applies, and linking mtsh.com.dbg failed with 'cannot find mtsh.o'. Add the same OBJECTIFY.c recipe used by the sibling caboose rules. - ruby.codegen.mk recipes contained '@# comment' lines. Those are only harmless when SHELL is mtsh (which exists solely on machines where mtsh was previously built); under the default cocmd shell they abort with "unsupported syntax '#'". Turn them into make comments. Co-Authored-By: Claude Fable 5 --- third_party/mexican_toaster/BUILD.mk | 2 ++ third_party/ruby-wip-4.0.0/ruby.codegen.mk | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/third_party/mexican_toaster/BUILD.mk b/third_party/mexican_toaster/BUILD.mk index a9cdbd75cc6..a32ed091c35 100644 --- a/third_party/mexican_toaster/BUILD.mk +++ b/third_party/mexican_toaster/BUILD.mk @@ -59,6 +59,7 @@ o/$(MODE)/third_party/mexican_toaster/mtsh.o: \ third_party/mexican_toaster/mtsh/util.inc \ third_party/mexican_toaster/mtsh/tokenize.inc \ third_party/mexican_toaster/mtsh/entry.inc + @$(COMPILE) -AOBJECTIFY.c $(OBJECTIFY.c) -c $< -o $@ o/$(MODE)/third_party/mexican_toaster/mtsh.com.dbg: \ $$(THIRD_PARTY_MEXICAN_TOASTER_DEPS) \ @@ -100,6 +101,7 @@ o/$(MODE)/third_party/mexican_toaster/mtsh_embed.o: \ third_party/mexican_toaster/mtsh/util.inc \ third_party/mexican_toaster/mtsh/tokenize.inc \ third_party/mexican_toaster/mtsh/entry.inc + @$(COMPILE) -AOBJECTIFY.c $(OBJECTIFY.c) -c $< -o $@ o/$(MODE)/third_party/mexican_toaster/caboose.dbg: \ $$(THIRD_PARTY_MEXICAN_TOASTER_DEPS) \ diff --git a/third_party/ruby-wip-4.0.0/ruby.codegen.mk b/third_party/ruby-wip-4.0.0/ruby.codegen.mk index 014aba038fb..1722762975a 100644 --- a/third_party/ruby-wip-4.0.0/ruby.codegen.mk +++ b/third_party/ruby-wip-4.0.0/ruby.codegen.mk @@ -103,8 +103,8 @@ THIRD_PARTY_RUBY_GENERATED += $(RUBY_RBCONFIG_GEN) RUBY_RBCONFIG_PATCH := $(RUBY_PATCHDIR)/rbconfig.rb.diff $(RUBY_RBCONFIG_PATCH): $(RUBY_RBCONFIG_GEN) $(RUBY_RBCONFIG_SOURCE) | $(RUBY_PATCHDIR) - @# TODO: Fix this recipe - cocmd doesn't handle complex && || chains with redirects properly - @# Original: cmp -s $(word 2,$^) $(word 1,$^) && rm -f $@ || diff -u $(word 2,$^) $(word 1,$^) > $@ || true +# TODO: Fix this recipe - cocmd doesn't handle complex && || chains with redirects properly +# Original: cmp -s $(word 2,$^) $(word 1,$^) && rm -f $@ || diff -u $(word 2,$^) $(word 1,$^) > $@ || true @touch $@ THIRD_PARTY_RUBY_PATCHES += $(RUBY_RBCONFIG_PATCH) @@ -274,7 +274,7 @@ THIRD_PARTY_RUBY_GENERATED += $(RUBY_PROBES_GEN) RUBY_PROBES_PATCH := $(RUBY_PATCHDIR)/probes.h.diff $(RUBY_PROBES_PATCH): $(RUBY_PROBES_GEN) | $(RUBY_PATCHDIR) - @# probes.h is generated at build time in MRI; keep empty patch as sentinel +# probes.h is generated at build time in MRI; keep empty patch as sentinel @touch $@ THIRD_PARTY_RUBY_PATCHES += $(RUBY_PROBES_PATCH) @@ -299,7 +299,7 @@ THIRD_PARTY_RUBY_GENERATED += $(RUBY_ENCMK_GEN) RUBY_ENCMK_PATCH := $(RUBY_PATCHDIR)/enc.mk.diff $(RUBY_ENCMK_PATCH): $(RUBY_ENCMK_GEN) | $(RUBY_PATCHDIR) - @# enc.mk is generated at build time in MRI; keep empty patch as sentinel +# enc.mk is generated at build time in MRI; keep empty patch as sentinel @touch $@ THIRD_PARTY_RUBY_PATCHES += $(RUBY_ENCMK_PATCH) @@ -324,7 +324,7 @@ THIRD_PARTY_RUBY_GENERATED += $(RUBY_EXT_CONFIGURE_GEN) RUBY_EXT_CONFIGURE_PATCH := $(RUBY_PATCHDIR)/ext-configure-ext.mk.diff $(RUBY_EXT_CONFIGURE_PATCH): $(RUBY_EXT_CONFIGURE_GEN) | $(RUBY_PATCHDIR) - @# ext/configure-ext.mk is generated at build time in MRI; keep empty patch as sentinel +# ext/configure-ext.mk is generated at build time in MRI; keep empty patch as sentinel @touch $@ THIRD_PARTY_RUBY_PATCHES += $(RUBY_EXT_CONFIGURE_PATCH) @@ -347,7 +347,7 @@ THIRD_PARTY_RUBY_GENERATED += $(RUBY_EXTSMK_GEN) RUBY_EXTSMK_PATCH := $(RUBY_PATCHDIR)/exts.mk.diff $(RUBY_EXTSMK_PATCH): $(RUBY_EXTSMK_GEN) | $(RUBY_PATCHDIR) - @# exts.mk is generated at build time in MRI; keep empty patch as sentinel +# exts.mk is generated at build time in MRI; keep empty patch as sentinel @touch $@ THIRD_PARTY_RUBY_PATCHES += $(RUBY_EXTSMK_PATCH) From 71c22a5b0fb6ea379877be36b8ceb95393252092 Mon Sep 17 00:00:00 2001 From: Largo Date: Sat, 8 Aug 2026 01:49:04 +0200 Subject: [PATCH 9/9] Add missing bin/gem launcher script assemble_stdlib.sh copies third_party/ruby/bin/gem into the packaged /zip/bin, but the bin/ directory was never committed. Restore the standard RubyGems launcher (identical to MRI's bin/gem). Co-Authored-By: Claude Fable 5 --- third_party/ruby-wip-4.0.0/bin/gem | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 third_party/ruby-wip-4.0.0/bin/gem diff --git a/third_party/ruby-wip-4.0.0/bin/gem b/third_party/ruby-wip-4.0.0/bin/gem new file mode 100755 index 00000000000..67e2ed81454 --- /dev/null +++ b/third_party/ruby-wip-4.0.0/bin/gem @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby +#-- +# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. +# All rights reserved. +# See LICENSE.txt for permissions. +#++ + +require "rubygems" +require "rubygems/gem_runner" +require "rubygems/exceptions" + +required_version = Gem::Requirement.new ">= 1.8.7" + +unless required_version.satisfied_by? Gem.ruby_version + abort "Expected Ruby version #{required_version}, is #{Gem.ruby_version}" +end + +args = ARGV.clone + +begin + Gem::GemRunner.new.run args +rescue Gem::SystemExitException => e + exit e.exit_code +end