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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/data/Dockerfile.ruby-build +119 -0
- data/data/ext/stub.c +489 -0
- data/data/ext/write-footer.c +38 -0
- data/data/prune-list.conf +40 -0
- data/data/scripts/_common.sh +17 -0
- data/data/scripts/build-ruby.sh +214 -0
- data/data/scripts/fix-dylibs.sh +93 -0
- data/data/scripts/package.sh +544 -0
- data/data/stubs/stub-aarch64-darwin +0 -0
- data/data/stubs/stub-aarch64-linux +0 -0
- data/data/stubs/stub-x86_64-linux +0 -0
- data/data/stubs/write-footer-aarch64-darwin +0 -0
- data/data/stubs/write-footer-aarch64-linux +0 -0
- data/data/stubs/write-footer-x86_64-linux +0 -0
- data/exe/rubox +3 -0
- data/lib/rubox/builder.rb +130 -0
- data/lib/rubox/cli.rb +214 -0
- data/lib/rubox/detector.rb +105 -0
- data/lib/rubox/packager.rb +118 -0
- data/lib/rubox/platform.rb +40 -0
- data/lib/rubox/version.rb +3 -0
- data/lib/rubox.rb +31 -0
- metadata +68 -0
|
@@ -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"
|