rubox 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ # Gems safe to remove from packaged binaries.
2
+ # One gem name per line. Lines starting with # are comments.
3
+ # These are dev tools, test frameworks, and rarely-used stdlib gems.
4
+
5
+ # --- Dev / build tools ---
6
+ rdoc
7
+ irb
8
+ reline
9
+ repl_type_completor
10
+ debug
11
+ rbs
12
+ typeprof
13
+ syntax_suggest
14
+ error_highlight
15
+ bundler
16
+ rake
17
+ rake-compiler
18
+
19
+ # --- Test frameworks ---
20
+ test-unit
21
+ minitest
22
+ power_assert
23
+
24
+ # --- Rarely used stdlib ---
25
+ rss
26
+ rinda
27
+ drb
28
+ matrix
29
+ prime
30
+ nkf
31
+ syslog
32
+ net-ftp
33
+ net-pop
34
+ net-smtp
35
+ net-imap
36
+ pstore
37
+ getoptlong
38
+ observer
39
+ abbrev
40
+ resolv-replace
@@ -0,0 +1,17 @@
1
+ # Shared helpers for rubox shell scripts.
2
+ # Source this at the top: source "$(dirname "$0")/_common.sh"
3
+
4
+ # Resolve DATA_DIR and PROJECT_DIR.
5
+ # In gem layout, RUBOX_DATA_DIR is set by the Ruby CLI.
6
+ # In standalone layout, DATA_DIR is one level up from scripts/.
7
+ _SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ if [[ -n "${RUBOX_DATA_DIR:-}" ]]; then
9
+ DATA_DIR="$RUBOX_DATA_DIR"
10
+ PROJECT_DIR="$(pwd)"
11
+ else
12
+ DATA_DIR="$(cd "$_SCRIPT_DIR/.." && pwd)"
13
+ PROJECT_DIR="$DATA_DIR"
14
+ fi
15
+
16
+ # Number of parallel jobs for compilation.
17
+ JOBS="${JOBS:-$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)}"
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Build a static (or mostly-static) CRuby for a given target.
4
+ #
5
+ # Usage: ./scripts/build-ruby.sh [--ruby-version VERSION] [--target TARGET] [--output DIR]
6
+ #
7
+ # Targets:
8
+ # x86_64-linux - Linux amd64 (via Docker + Alpine/musl)
9
+ # aarch64-linux - Linux arm64 (via Docker + Alpine/musl)
10
+ # x86_64-darwin - macOS Intel (native build)
11
+ # aarch64-darwin - macOS Apple Silicon (native build)
12
+ #
13
+ set -euo pipefail
14
+
15
+ source "$(dirname "$0")/_common.sh"
16
+
17
+ RUBY_VERSION="${RUBY_VERSION:-4.0.0}"
18
+ TARGET=""
19
+ OUTPUT_DIR=""
20
+
21
+ # Parse args
22
+ while [[ $# -gt 0 ]]; do
23
+ case "$1" in
24
+ --ruby-version) RUBY_VERSION="$2"; shift 2 ;;
25
+ --target) TARGET="$2"; shift 2 ;;
26
+ --output) OUTPUT_DIR="$2"; shift 2 ;;
27
+ --jobs|-j) JOBS="$2"; shift 2 ;;
28
+ *) echo "Unknown option: $1"; exit 1 ;;
29
+ esac
30
+ done
31
+
32
+ # Auto-detect target if not specified
33
+ if [[ -z "$TARGET" ]]; then
34
+ ARCH=$(uname -m)
35
+ OS=$(uname -s | tr '[:upper:]' '[:lower:]')
36
+ case "$ARCH" in
37
+ x86_64|amd64) ARCH="x86_64" ;;
38
+ arm64|aarch64) ARCH="aarch64" ;;
39
+ esac
40
+ TARGET="${ARCH}-${OS}"
41
+ fi
42
+
43
+ if [[ -z "$OUTPUT_DIR" ]]; then
44
+ OUTPUT_DIR="build/ruby-${RUBY_VERSION}-${TARGET}"
45
+ fi
46
+
47
+ # DATA_DIR and PROJECT_DIR set by _common.sh
48
+
49
+ echo "==> Building Ruby ${RUBY_VERSION} for ${TARGET}"
50
+ echo " Output: ${OUTPUT_DIR}"
51
+ echo " Jobs: ${JOBS}"
52
+
53
+ build_linux() {
54
+ local arch="$1"
55
+ local docker_platform=""
56
+ case "$arch" in
57
+ x86_64) docker_platform="linux/amd64" ;;
58
+ aarch64) docker_platform="linux/arm64" ;;
59
+ esac
60
+
61
+ echo "==> Building via Docker (Alpine/musl) for ${docker_platform}"
62
+
63
+ docker buildx build \
64
+ --platform "${docker_platform}" \
65
+ --build-arg RUBY_VERSION="${RUBY_VERSION}" \
66
+ --build-arg JOBS="${JOBS}" \
67
+ --output "type=local,dest=${PROJECT_DIR}/${OUTPUT_DIR}" \
68
+ -f "${DATA_DIR}/Dockerfile.ruby-build" \
69
+ "${DATA_DIR}"
70
+ }
71
+
72
+ build_darwin() {
73
+ echo "==> Building natively on macOS"
74
+
75
+ local ruby_src_dir="build/src/ruby-${RUBY_VERSION}"
76
+ local ruby_tarball="build/src/ruby-${RUBY_VERSION}.tar.gz"
77
+ local prefix="${PROJECT_DIR}/${OUTPUT_DIR}"
78
+
79
+ mkdir -p build/src
80
+
81
+ # Download Ruby source
82
+ if [[ ! -f "$ruby_tarball" ]]; then
83
+ local major_minor="${RUBY_VERSION%.*}"
84
+ local url="https://cache.ruby-lang.org/pub/ruby/${major_minor}/ruby-${RUBY_VERSION}.tar.gz"
85
+ echo "==> Downloading Ruby ${RUBY_VERSION} from ${url}"
86
+ curl -fSL -o "$ruby_tarball" "$url"
87
+ fi
88
+
89
+ # Extract
90
+ if [[ ! -d "$ruby_src_dir" ]]; then
91
+ echo "==> Extracting Ruby source"
92
+ tar xzf "$ruby_tarball" -C build/src
93
+ fi
94
+
95
+ # Check for dependencies via Homebrew or system
96
+ local openssl_dir=""
97
+ local libyaml_dir=""
98
+ local libffi_dir=""
99
+ local zlib_dir=""
100
+ local readline_dir=""
101
+
102
+ if command -v brew &>/dev/null; then
103
+ openssl_dir="$(brew --prefix openssl@3 2>/dev/null || true)"
104
+ libyaml_dir="$(brew --prefix libyaml 2>/dev/null || true)"
105
+ libffi_dir="$(brew --prefix libffi 2>/dev/null || true)"
106
+ zlib_dir="$(brew --prefix zlib 2>/dev/null || true)"
107
+ readline_dir="$(brew --prefix readline 2>/dev/null || true)"
108
+ fi
109
+
110
+ # Build flags to statically link all dependencies.
111
+ # On macOS, ld prefers .dylib over .a when both exist in the same dir.
112
+ # We create a staging directory with symlinks to only the .a files,
113
+ # then point -L at that directory so the linker has no choice but to
114
+ # use the static archives.
115
+ local static_lib_stage="${PROJECT_DIR}/build/static-libs"
116
+ rm -rf "$static_lib_stage"
117
+ mkdir -p "$static_lib_stage"
118
+
119
+ local static_ldflags="-L${static_lib_stage}"
120
+ local static_cppflags=""
121
+
122
+ for dep_dir in "$openssl_dir" "$zlib_dir" "$libyaml_dir" "$readline_dir" "$libffi_dir"; do
123
+ if [[ -n "$dep_dir" && -d "$dep_dir/lib" ]]; then
124
+ # Symlink all .a files into our staging dir
125
+ for a in "$dep_dir"/lib/*.a; do
126
+ [[ -f "$a" ]] && ln -sf "$a" "$static_lib_stage/"
127
+ done
128
+ fi
129
+ if [[ -n "$dep_dir" && -d "$dep_dir/include" ]]; then
130
+ static_cppflags="$static_cppflags -I$dep_dir/include"
131
+ fi
132
+ done
133
+
134
+ echo " Static lib staging dir contents:"
135
+ ls -la "$static_lib_stage/"
136
+
137
+ local configure_args=(
138
+ --prefix="$prefix"
139
+ --disable-shared
140
+ --enable-static
141
+ --disable-install-doc
142
+ --disable-install-rdoc
143
+ --disable-install-capi
144
+ --with-static-linked-ext
145
+ --without-gmp
146
+ )
147
+
148
+ # Check if Rust is available for YJIT
149
+ if command -v rustc &>/dev/null; then
150
+ local rust_version
151
+ rust_version=$(rustc --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
152
+ echo " Rust ${rust_version} found, enabling YJIT"
153
+ configure_args+=(--enable-yjit)
154
+ else
155
+ echo " No Rust found, disabling YJIT"
156
+ configure_args+=(--disable-yjit)
157
+ fi
158
+
159
+ if [[ -n "$openssl_dir" ]]; then
160
+ configure_args+=(--with-openssl-dir="$openssl_dir")
161
+ fi
162
+ if [[ -n "$libyaml_dir" ]]; then
163
+ configure_args+=(--with-libyaml-dir="$libyaml_dir")
164
+ fi
165
+ if [[ -n "$libffi_dir" ]]; then
166
+ configure_args+=(--with-libffi-dir="$libffi_dir")
167
+ fi
168
+ if [[ -n "$readline_dir" ]]; then
169
+ configure_args+=(--with-readline-dir="$readline_dir")
170
+ fi
171
+
172
+ cd "$ruby_src_dir"
173
+
174
+ if [[ ! -f Makefile ]]; then
175
+ echo "==> Configuring Ruby"
176
+ echo " LDFLAGS: $static_ldflags"
177
+ echo " CPPFLAGS: $static_cppflags"
178
+ LDFLAGS="$static_ldflags" \
179
+ CPPFLAGS="$static_cppflags" \
180
+ ./configure "${configure_args[@]}"
181
+ fi
182
+
183
+ echo "==> Compiling Ruby (${JOBS} jobs)"
184
+ make -j"${JOBS}"
185
+
186
+ echo "==> Installing Ruby to ${prefix}"
187
+ make install
188
+
189
+ # Verify what we built
190
+ echo "==> Checking library dependencies..."
191
+ otool -L "${prefix}/bin/ruby" | head -20
192
+
193
+ cd "$PROJECT_DIR"
194
+
195
+ echo "==> Ruby built successfully at ${prefix}"
196
+ echo " Binary: ${prefix}/bin/ruby"
197
+ "${prefix}/bin/ruby" --version
198
+ }
199
+
200
+ case "$TARGET" in
201
+ x86_64-linux|aarch64-linux)
202
+ build_linux "${TARGET%%-*}"
203
+ ;;
204
+ x86_64-darwin|aarch64-darwin)
205
+ build_darwin
206
+ ;;
207
+ *)
208
+ echo "Unsupported target: $TARGET"
209
+ echo "Supported: x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin"
210
+ exit 1
211
+ ;;
212
+ esac
213
+
214
+ echo "==> Done. Ruby ${RUBY_VERSION} built for ${TARGET} at ${OUTPUT_DIR}"
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # fix-dylibs.sh - Bundle non-system dylibs and rewrite load paths.
4
+ #
5
+ # Scans all .bundle and .dylib files in a directory for non-system
6
+ # dynamic dependencies, copies them into a lib/ directory, and
7
+ # rewrites references using install_name_tool.
8
+ #
9
+ # Usage: ./scripts/fix-dylibs.sh <staging_dir>
10
+ #
11
+ set -euo pipefail
12
+
13
+ STAGING_DIR="$1"
14
+ LIB_DIR="${STAGING_DIR}/lib/dylibs"
15
+ mkdir -p "$LIB_DIR"
16
+
17
+ is_system_lib() {
18
+ local path="$1"
19
+ case "$path" in
20
+ /usr/lib/*|/System/*) return 0 ;;
21
+ *) return 1 ;;
22
+ esac
23
+ }
24
+
25
+ # Collect all .bundle and .dylib files
26
+ mapfile -t TARGETS < <(find "$STAGING_DIR" -type f \( -name "*.bundle" -o -name "*.dylib" \) ! -path "*/DWARF/*")
27
+
28
+ FIXED=0
29
+ ITERATIONS=0
30
+ MAX_ITERATIONS=10
31
+
32
+ # Iterate until no new dylibs are discovered (handles transitive deps)
33
+ while [[ $ITERATIONS -lt $MAX_ITERATIONS ]]; do
34
+ ITERATIONS=$((ITERATIONS + 1))
35
+ NEW_DEPS=0
36
+
37
+ for target in "${TARGETS[@]}"; do
38
+ # Get non-system dependencies
39
+ while IFS= read -r dep; do
40
+ dep=$(echo "$dep" | sed 's/^[[:space:]]*//' | cut -d' ' -f1)
41
+ [[ -z "$dep" ]] && continue
42
+
43
+ if is_system_lib "$dep"; then
44
+ continue
45
+ fi
46
+
47
+ dep_basename=$(basename "$dep")
48
+ bundled_path="${LIB_DIR}/${dep_basename}"
49
+
50
+ # Copy the dylib if not already bundled
51
+ if [[ ! -f "$bundled_path" ]]; then
52
+ if [[ -f "$dep" ]]; then
53
+ echo " Bundling: ${dep} -> lib/dylibs/${dep_basename}"
54
+ cp "$dep" "$bundled_path"
55
+ chmod 644 "$bundled_path"
56
+ # Add to targets for transitive dep scanning
57
+ TARGETS+=("$bundled_path")
58
+ NEW_DEPS=$((NEW_DEPS + 1))
59
+ else
60
+ echo " WARNING: dependency not found: ${dep} (needed by $(basename "$target"))"
61
+ continue
62
+ fi
63
+ fi
64
+
65
+ # Compute relative path from target to lib/dylibs/
66
+ target_dir=$(dirname "$target")
67
+ rel_path=$(ruby -e "require 'pathname'; puts Pathname.new('${LIB_DIR}').relative_path_from(Pathname.new('${target_dir}'))")
68
+ new_ref="@loader_path/${rel_path}/${dep_basename}"
69
+
70
+ # Rewrite the reference
71
+ install_name_tool -change "$dep" "$new_ref" "$target" 2>/dev/null || true
72
+ FIXED=$((FIXED + 1))
73
+ done < <(otool -L "$target" 2>/dev/null | tail -n +2)
74
+ done
75
+
76
+ if [[ $NEW_DEPS -eq 0 ]]; then
77
+ break
78
+ fi
79
+ done
80
+
81
+ # Re-sign all modified binaries (required on Apple Silicon)
82
+ if [[ $FIXED -gt 0 ]]; then
83
+ echo " Re-signing modified binaries..."
84
+ for target in "${TARGETS[@]}"; do
85
+ codesign -f -s - "$target" 2>/dev/null || true
86
+ done
87
+ # Sign bundled dylibs too
88
+ for dylib in "$LIB_DIR"/*.dylib; do
89
+ [[ -f "$dylib" ]] && codesign -f -s - "$dylib" 2>/dev/null || true
90
+ done
91
+ fi
92
+
93
+ echo " Fixed ${FIXED} dylib references across ${#TARGETS[@]} files"