bundler-spinel 0.2.1 → 0.4.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 +4 -4
- data/CHANGELOG.md +102 -0
- data/lib/bundler/spinel/cli.rb +81 -4
- data/lib/bundler/spinel/engine.rb +11 -1
- data/lib/bundler/spinel/history.rb +84 -1
- data/lib/bundler/spinel/load_bearing.rb +116 -0
- data/lib/bundler/spinel/localizer.rb +11 -9
- data/lib/bundler/spinel/probe.rb +51 -5
- data/lib/bundler/spinel/site.rb +23 -5
- data/lib/bundler/spinel/vendorer.rb +317 -17
- data/lib/bundler/spinel/verifier.rb +25 -4
- data/lib/bundler/spinel/version.rb +8 -5
- data/lib/bundler/spinel/why.rb +219 -0
- data/lib/bundler/spinel.rb +1 -0
- metadata +5 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8518bfe1ec1303fb7116e33738abb8246bb3c7072afca1b70057523f824b4a2c
|
|
4
|
+
data.tar.gz: 5d945e9ca4fa4409f5ff325a6eae9d917469e5c0e7ceb2ef69c8c356551c0fda
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ff071af5cde1af69f32f92b509092ec6d535173dfbbdbe84c02940b5118abb1f7cafacc610cc26a4c3e20263426e4a7422560ba166c766ff296d391be1758e34
|
|
7
|
+
data.tar.gz: e704f7c8a825a0cdb278c604a86bb38fd9363ac0fe8fe7406c88c52e9b77e1a76cd2675036d503b3e80fe488f27f3ff1820c7803428989f7ac1930bdaff9a412
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,108 @@ All notable changes to `bundler-spinel` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
|
|
5
5
|
project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.4.0] — 2026-06-15
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Probe: a require-only compile failure is the load-path limit, not
|
|
11
|
+
`analyze-failed` (spinel master b60fbd7 absorption).** Since matz/spinel's
|
|
12
|
+
#1383 fix, an unresolvable plain `require "gem/sub"` makes `spinel -c` exit
|
|
13
|
+
non-zero instead of warning and continuing. Because that idiom is
|
|
14
|
+
near-universal, the b60fbd7 corpus reprobe spuriously turned thousands of
|
|
15
|
+
previously-`clean` gems into `rejected:analyze-failed`. The probe now detects
|
|
16
|
+
a compile that failed *solely* on an unresolvable `require` (the
|
|
17
|
+
`CallNode \`require\`` form, with no real codegen error and no other
|
|
18
|
+
unsupported call) and classifies it `risky [load-path:require]` — the
|
|
19
|
+
documented no-load-path limitation a real Spinel project resolves by
|
|
20
|
+
vendoring. Real codegen errors still reject. +`test/probe_classify_test.rb`.
|
|
21
|
+
- **Engine rev detection for worktrees + frozen copies.** `Engine#rev` fell
|
|
22
|
+
back to an opaque `bin:<hash>` (a) in a git *worktree*, where `.git` is a
|
|
23
|
+
file not a directory, and (b) for a frozen/detached engine copy with no
|
|
24
|
+
`.git`. Both now resolve to `git:<short-sha>`: worktree `.git` files are
|
|
25
|
+
honored (`File.exist?`, not `File.directory?`), and a `.spinel_rev` stamp
|
|
26
|
+
(a bare HEAD sha written at freeze time) is read first. Keeps the ledger
|
|
27
|
+
key — and the history/diff tooling that joins on it — consistent across the
|
|
28
|
+
build layouts a reprobe actually uses. `bin/reprobe-corpus.sh`'s frozen
|
|
29
|
+
engines now carry the stamp.
|
|
30
|
+
- **cmake build-units on macOS (spinelgems#21).** The Vendorer now sets the
|
|
31
|
+
SDK libc++ include path (`CPLUS_INCLUDE_PATH` via `xcrun --show-sdk-path`)
|
|
32
|
+
for cmake build-unit invocations on Darwin — without it every C++ unit died
|
|
33
|
+
with `fatal error: 'array' file not found` (toy#27's Mac cold-start LIB
|
|
34
|
+
leg; fix verified on an M2). Caller-set `CPLUS_INCLUDE_PATH` is preserved
|
|
35
|
+
(SDK path appends); missing/failing `xcrun` degrades to the previous
|
|
36
|
+
behavior.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- **Opt-in build-units + variant build dirs (spinelgems#20).** An `optional`
|
|
40
|
+
entry may declare `"default": "disabled"`: a plain `vendor` skips its build
|
|
41
|
+
entirely (placeholder ← `disabled_cflags`) until the consumer enables it via
|
|
42
|
+
`--with-ext NAME` / `SPINEL_EXT_ENABLE` — what a CUDA unit needs to not
|
|
43
|
+
attempt nvcc configures on a CUDA-less box. Explicit disable beats enable;
|
|
44
|
+
unknown enable names warn loud. cmake units take `"build_dir"` (default
|
|
45
|
+
`"build"`, relative-only) so two entries can build one shared source dir
|
|
46
|
+
into variant trees (`build/` vs `build-cuda/`), and source dirs are copied
|
|
47
|
+
once per vendor run — a second entry sharing `dir` no longer wipes the first
|
|
48
|
+
unit's just-built artifacts. Unblocks toy merging its staged
|
|
49
|
+
`spinel-ext-gpu.json` into `spinel-ext.json` (toy#45 Phase 3 → publish).
|
|
50
|
+
- **A gem's `sig/*.rbs` acts as the type root (spinelgems#13).** Spinel infers
|
|
51
|
+
param types from call sites (whole-program, closed-world), so an *uncalled*
|
|
52
|
+
public method widens to `int`/`poly` — the failure mode hand-written seed
|
|
53
|
+
blocks exist to patch. `--rbs` was verified to re-pin uncalled methods' param,
|
|
54
|
+
return, and ivar types, so the tooling now treats a shipped `sig/` tree as
|
|
55
|
+
authoritative: `spinel-compat verify` auto-passes `--rbs <gem>/sig` when the
|
|
56
|
+
gem ships signatures (`--rbs DIR` to override, `--no-rbs` to opt out), and
|
|
57
|
+
records an `rbs:sig` provenance tag in the verdict reasons. `vendor` copies
|
|
58
|
+
each gem's `sig/` alongside `lib/`, aggregates all of them under
|
|
59
|
+
`<into>/sig/<gem>/`, and advertises the single `--rbs <into>/sig` root in
|
|
60
|
+
`deps.rb` and the CLI output. A spinel-native gem (SpinelKit, the Tep
|
|
61
|
+
batteries) ships one standard Ruby artifact — no seed soup, no manifest keys.
|
|
62
|
+
|
|
63
|
+
## [0.3.0] — 2026-06-08
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- **`spinel-compat vendor` build-units (spinelgems#14).** `spinel-ext.json` now
|
|
67
|
+
supports a `build` entry — a declared native build (`tool: cmake | make`, with
|
|
68
|
+
`dir`, `args`, `targets`, `artifacts`, and `patches`) run **inside the
|
|
69
|
+
consumer's vendor tree**, with link flags expanded relative to it (`{dir}` and
|
|
70
|
+
cross-entry `{dir:NAME}`). This lets a heavy-native gem — toy's vendored ggml
|
|
71
|
+
CMake build plus its tinynn shims — vendor **self-contained and relocatable**,
|
|
72
|
+
the same end state tep's small per-`.c` shims already had. A
|
|
73
|
+
`SPINEL_EXT_<PLACEHOLDER>` override substitutes prebuilt flags and skips the
|
|
74
|
+
build. Declared `patches` apply with stack-level already-applied detection, so
|
|
75
|
+
a `path:`-sourced dev checkout (already patched) vendors cleanly. Proven
|
|
76
|
+
end-to-end: a consumer vendors toy, the archives build in-tree, a
|
|
77
|
+
Spinel-compiled program links and runs, with zero absolute paths and survival
|
|
78
|
+
across a project move. Strictly narrower than `extconf.rb` (no free-form
|
|
79
|
+
shell; declared artifacts) — the Spinel analogue of a gemspec `extensions:`.
|
|
80
|
+
- **`spinel-compat vendor` handles transitive gem→gem dependencies
|
|
81
|
+
(spinelgems#19).** Vendoring a gem that depends on another vendored gem now
|
|
82
|
+
works: `deps.rb` is emitted in **topological order** (every gem's runtime
|
|
83
|
+
dependencies load before it, via a DFS over `spec.dependencies` with a stable
|
|
84
|
+
alphabetical tiebreak and a cycle guard) instead of the lockfile's alphabetical
|
|
85
|
+
order, and it prepends each vendored gem's `lib` to `$LOAD_PATH` so a
|
|
86
|
+
dependent's plain `require "<depgem>"` resolves under CRuby too. Spinel (no load
|
|
87
|
+
path) ignores both and relies on the topo-ordered `require_relative`s, so the
|
|
88
|
+
one `deps.rb` is correct under both runtimes — verified identical output on a
|
|
89
|
+
two-gem fixture. Unblocks `tep` → `spinel_kit` (the new stdlib-surface gem)
|
|
90
|
+
on the clean `gem "spinel_kit"` + vendor path.
|
|
91
|
+
- **`spinel-compat why <gem>` (spinelgems#12).** A legible "why doesn't this gem
|
|
92
|
+
work (yet)?" report: a plain-English cause, a category (native C-ext / Spinel
|
|
93
|
+
limitation / fixable compiler bug / dependency-blocked / metaprogramming), the
|
|
94
|
+
concrete evidence (the CRuby-vs-Spinel diff, the unresolved calls, the missing
|
|
95
|
+
require, the hard construct), and — most usefully — whether the verdict is
|
|
96
|
+
**terminal** (needs an upstream port or compiler feature) or **fixable** (a
|
|
97
|
+
tracked compiler bug that can graduate). Reads the dominant-rev ledger entry
|
|
98
|
+
like the catalog; `--probe` / `--dir` explains a fresh live probe instead.
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
- **`spinel-ext.json` wiring now warns on manifest drift.** A declared
|
|
102
|
+
placeholder that matches no vendored `.rb` emits a loud warning at vendor time
|
|
103
|
+
— replacing the per-gem "cflags canary" constants consumers maintained by hand.
|
|
104
|
+
- **Catalog/site rendering** advanced across several engine-rev reprobes (signals
|
|
105
|
+
lead every verdict tier; the download floor is off by default; human
|
|
106
|
+
attestations earn a verified rung; a load-bearing-gems roadmap page). These
|
|
107
|
+
affect the rendered spinelgems.org catalog, not the plugin's gating behaviour.
|
|
108
|
+
|
|
7
109
|
## [0.2.1] — 2026-06-01
|
|
8
110
|
|
|
9
111
|
### Fixed
|
data/lib/bundler/spinel/cli.rb
CHANGED
|
@@ -21,6 +21,7 @@ module Bundler
|
|
|
21
21
|
when "install-engine" then cmd_install_engine(argv)
|
|
22
22
|
when "init" then cmd_init(argv)
|
|
23
23
|
when "probe" then cmd_probe(argv)
|
|
24
|
+
when "why" then cmd_why(argv)
|
|
24
25
|
when "verify" then cmd_verify(argv)
|
|
25
26
|
when "vendor" then cmd_vendor(argv)
|
|
26
27
|
when "check" then cmd_check(argv)
|
|
@@ -29,6 +30,7 @@ module Bundler
|
|
|
29
30
|
when "build-site" then cmd_build_site(argv)
|
|
30
31
|
when "build-db" then cmd_build_db(argv)
|
|
31
32
|
when "build-history" then cmd_build_history(argv)
|
|
33
|
+
when "build-load-bearing" then cmd_build_load_bearing(argv)
|
|
32
34
|
when "server" then cmd_server(argv)
|
|
33
35
|
when "ledger" then cmd_ledger(argv)
|
|
34
36
|
when "diff" then cmd_diff(argv)
|
|
@@ -91,12 +93,64 @@ module Bundler
|
|
|
91
93
|
v.rejected? ? 1 : 0
|
|
92
94
|
end
|
|
93
95
|
|
|
96
|
+
# why NAME [VERSION] [--rev REV] [--probe] [--dir PATH]
|
|
97
|
+
# Legible "why doesn't this gem work (yet)?" report (spinelgems#12).
|
|
98
|
+
# Reads the recorded verdict (dominant rev, like the catalog) by default;
|
|
99
|
+
# --probe / --dir explains a fresh live probe instead.
|
|
100
|
+
def cmd_why(argv)
|
|
101
|
+
require_relative "why"
|
|
102
|
+
dir = (i = argv.index("--dir")) ? argv.delete_at(i + 1).tap { argv.delete_at(i) } : nil
|
|
103
|
+
rev = (k = argv.index("--rev")) ? argv.delete_at(k + 1).tap { argv.delete_at(k) } : nil
|
|
104
|
+
live = !!argv.delete("--probe") || !!dir
|
|
105
|
+
name = argv.shift or raise Error, "usage: spinel-compat why NAME [VERSION] [--rev REV] [--probe] [--dir PATH]"
|
|
106
|
+
version = argv.shift
|
|
107
|
+
|
|
108
|
+
if live
|
|
109
|
+
engine = Engine.new
|
|
110
|
+
src = dir ? File.expand_path(dir) : GemFetcher.new.fetch(name, version || latest_version(name))
|
|
111
|
+
v = Probe.new(engine, Ledger.new).probe(name, version || "path", src)
|
|
112
|
+
Why.new(out: @out).report(v, source: "live probe")
|
|
113
|
+
else
|
|
114
|
+
v = ledger_pick(name, version: version, rev: rev)
|
|
115
|
+
unless v
|
|
116
|
+
@out.puts " no recorded verdict for #{name}#{version ? " #{version}" : ''}. " \
|
|
117
|
+
"Run `spinel-compat why #{name} --probe` to evaluate it now."
|
|
118
|
+
return 1
|
|
119
|
+
end
|
|
120
|
+
Why.new(out: @out).report(v, source: "ledger #{v.rev}")
|
|
121
|
+
end
|
|
122
|
+
0
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# The authoritative ledger entry for a gem: a specific --rev if asked,
|
|
126
|
+
# else the entry at the ledger's dominant rev (the rev most rows share —
|
|
127
|
+
# the same target the catalog renders), else the most recent seen.
|
|
128
|
+
def ledger_pick(name, version: nil, rev: nil)
|
|
129
|
+
entries = []
|
|
130
|
+
rev_counts = Hash.new(0)
|
|
131
|
+
Ledger.new.each do |v|
|
|
132
|
+
rev_counts[v.rev] += 1
|
|
133
|
+
next unless v.gem == name
|
|
134
|
+
next if version && v.version != version
|
|
135
|
+
entries << v
|
|
136
|
+
end
|
|
137
|
+
return nil if entries.empty?
|
|
138
|
+
return entries.select { |v| v.rev == rev }.last if rev
|
|
139
|
+
|
|
140
|
+
target = rev_counts.max_by { |_, c| c }&.first
|
|
141
|
+
entries.select { |v| v.rev == target }.last || entries.last
|
|
142
|
+
end
|
|
143
|
+
|
|
94
144
|
def cmd_verify(argv)
|
|
95
145
|
dir = (i = argv.index("--dir")) ? argv.delete_at(i + 1).tap { argv.delete_at(i) } : nil
|
|
96
146
|
smoke = (j = argv.index("--smoke")) ? argv.delete_at(j + 1).tap { argv.delete_at(j) } : nil
|
|
97
147
|
full = !!argv.delete("--full")
|
|
98
148
|
tests = !!argv.delete("--tests")
|
|
99
|
-
|
|
149
|
+
# --rbs DIR / --no-rbs: explicit type root / opt out. Default :auto —
|
|
150
|
+
# use the gem's own sig/*.rbs when shipped (spinelgems#13).
|
|
151
|
+
rbs = (k = argv.index("--rbs")) ? argv.delete_at(k + 1).tap { argv.delete_at(k) } : :auto
|
|
152
|
+
rbs = false if argv.delete("--no-rbs")
|
|
153
|
+
name = argv.shift or raise Error, "usage: spinel-compat verify NAME [VERSION] [--dir PATH] [--smoke FILE | --tests] [--full] [--rbs DIR | --no-rbs]"
|
|
100
154
|
engine = Engine.new
|
|
101
155
|
if dir
|
|
102
156
|
version = argv.shift || "path"
|
|
@@ -114,7 +168,7 @@ module Bundler
|
|
|
114
168
|
smoke = File.join(Dir.tmpdir, "__spinel_tests_#{name}.rb")
|
|
115
169
|
File.write(smoke, runner)
|
|
116
170
|
end
|
|
117
|
-
v = Verifier.new(engine, Ledger.new).verify(name, version, gem_dir, smoke: smoke && File.expand_path(smoke), full: full)
|
|
171
|
+
v = Verifier.new(engine, Ledger.new).verify(name, version, gem_dir, smoke: smoke && File.expand_path(smoke), full: full, rbs: rbs)
|
|
118
172
|
print_verdict(v)
|
|
119
173
|
File.delete(smoke) if tests && smoke && File.exist?(smoke)
|
|
120
174
|
(v.verified? || v.loaded?) ? 0 : 1
|
|
@@ -136,13 +190,26 @@ module Bundler
|
|
|
136
190
|
ext_disable << argv.delete_at(d + 1).to_s
|
|
137
191
|
argv.delete_at(d)
|
|
138
192
|
end
|
|
193
|
+
# --with-ext NAME : opt IN to a default-disabled entry, e.g. a CUDA
|
|
194
|
+
# build-unit (repeatable; also SPINEL_EXT_ENABLE). spinelgems#20.
|
|
195
|
+
ext_enable = []
|
|
196
|
+
while (w = argv.index("--with-ext"))
|
|
197
|
+
ext_enable << argv.delete_at(w + 1).to_s
|
|
198
|
+
argv.delete_at(w)
|
|
199
|
+
end
|
|
139
200
|
lock = argv.shift || "Gemfile.lock"
|
|
140
201
|
raise Error, "no #{lock}; run `bundle lock` first" unless File.exist?(lock)
|
|
141
202
|
|
|
142
|
-
res = Vendorer.new.vendor(lock, into: into, ext_overrides: ext_overrides,
|
|
203
|
+
res = Vendorer.new.vendor(lock, into: into, ext_overrides: ext_overrides,
|
|
204
|
+
ext_disable: ext_disable, ext_enable: ext_enable)
|
|
143
205
|
ext = res[:extensions].to_i
|
|
144
206
|
@out.puts "vendored #{res[:count]} gem(s)#{ext.positive? ? " (+#{ext} C ext)" : ''} -> #{res[:into]}"
|
|
145
207
|
@out.puts " require_relative \"#{res[:into]}/deps\" from your Spinel entrypoint"
|
|
208
|
+
sigs = res[:sig_gems] || []
|
|
209
|
+
unless sigs.empty?
|
|
210
|
+
@out.puts " #{sigs.size} gem(s) ship sig/*.rbs type roots -> #{res[:into]}/sig"
|
|
211
|
+
@out.puts " compile with: spinel ... --rbs #{res[:into]}/sig (spinelgems#13)"
|
|
212
|
+
end
|
|
146
213
|
0
|
|
147
214
|
end
|
|
148
215
|
|
|
@@ -308,6 +375,13 @@ module Bundler
|
|
|
308
375
|
0
|
|
309
376
|
end
|
|
310
377
|
|
|
378
|
+
def cmd_build_load_bearing(argv)
|
|
379
|
+
out = (j = argv.index("--out")) ? argv[j + 1] : raise(Error, "build-load-bearing needs --out FILE")
|
|
380
|
+
f = LoadBearing.new.build_html(File.expand_path(out))
|
|
381
|
+
@out.puts "built load-bearing -> #{f}"
|
|
382
|
+
0
|
|
383
|
+
end
|
|
384
|
+
|
|
311
385
|
# Serve the static site + (with --store) the Compact Index from one process
|
|
312
386
|
# — what the deploy host runs. Port defaults to $PORT (Upsun) then 9292.
|
|
313
387
|
def cmd_server(argv)
|
|
@@ -410,8 +484,11 @@ module Bundler
|
|
|
410
484
|
spinel-compat install-engine [REV] fetch+build the Spinel compiler -> ~/.cache/spinel
|
|
411
485
|
spinel-compat init [DIR] scaffold a Spinel+Tep project (Gemfile, app.rb, bin/build)
|
|
412
486
|
spinel-compat probe NAME [VERSION] probe one gem, record a verdict
|
|
487
|
+
spinel-compat why NAME [--probe] legible "why doesn't this work (yet)?" report
|
|
413
488
|
spinel-compat verify NAME [--smoke F] differential CRuby-vs-Spinel run -> verified
|
|
414
|
-
|
|
489
|
+
[--rbs DIR | --no-rbs] type root: gem's sig/*.rbs (auto when shipped)
|
|
490
|
+
spinel-compat vendor [LOCK] [--into D] place deps where Spinel finds them + deps.rb (+ sig/ roots)
|
|
491
|
+
[--with-ext NAME | --no-ext NAME] opt in/out of optional C-ext units
|
|
415
492
|
spinel-compat check [LOCK] [--strict] gate a Gemfile.lock (exit 1 if rejected)
|
|
416
493
|
spinel-compat survey GEM... | --list F wholesale review -> reason histogram
|
|
417
494
|
spinel-compat serve --store DIR curated source (only vetted gems)
|
|
@@ -76,7 +76,17 @@ module Bundler
|
|
|
76
76
|
private
|
|
77
77
|
|
|
78
78
|
def compute_rev
|
|
79
|
-
|
|
79
|
+
# A frozen/detached engine copy (bin/reprobe-corpus.sh's
|
|
80
|
+
# spinel-frozen-<rev>, or a build whose .git was stripped) can carry a
|
|
81
|
+
# `.spinel_rev` stamp — a bare HEAD sha written at freeze time — so it
|
|
82
|
+
# still keys to its real git rev instead of an opaque binary hash.
|
|
83
|
+
stamp = File.join(@dir, ".spinel_rev")
|
|
84
|
+
if File.file?(stamp) && !(s = File.read(stamp).strip[0, 12].to_s).empty?
|
|
85
|
+
return "git:#{s}"
|
|
86
|
+
end
|
|
87
|
+
# `.git` is a *file* in a git worktree (a gitdir pointer), not a dir —
|
|
88
|
+
# File.exist? covers both so worktree checkouts report git:, not bin:.
|
|
89
|
+
if File.exist?(File.join(@dir, ".git"))
|
|
80
90
|
sha = capture("git", "-C", @dir, "rev-parse", "--short", "HEAD")
|
|
81
91
|
if sha && !sha.empty?
|
|
82
92
|
dirty = capture("git", "-C", @dir, "status", "--porcelain")
|
|
@@ -26,6 +26,89 @@ module Bundler
|
|
|
26
26
|
"(<code>e2e010c</code>); together with the new <code>instance_methods</code> const-fold " \
|
|
27
27
|
"(<a href=\"https://github.com/matz/spinel/issues/1073\">#1073</a>) and <code>transpose</code>/map " \
|
|
28
28
|
"specializations, the brass cluster and thousands more moved out of <code>rejected</code>." },
|
|
29
|
+
{ rev: "95557f5", date: "2026-06-02", commit: "module/class-body side effects + lexical const refs (#1256), Regexp.last_match(n) (#1257), preserve Float-in-Hash (#1258), Struct typing, JSON.generate, alias, +14 more",
|
|
30
|
+
file: "survey-95557f5/compat.jsonl",
|
|
31
|
+
note: "<strong>The biggest single jump yet.</strong> 22 upstream commits — including fixes for three " \
|
|
32
|
+
"issues this harness filed (<a href=\"https://github.com/matz/spinel/issues/1256\">#1256</a> module-body, " \
|
|
33
|
+
"<a href=\"https://github.com/matz/spinel/issues/1257\">#1257</a> <code>Regexp.last_match</code>, " \
|
|
34
|
+
"<a href=\"https://github.com/matz/spinel/issues/1258\">#1258</a> Float-in-Hash) plus Struct typing, " \
|
|
35
|
+
"<code>alias</code>, <code>JSON.generate</code> for records and more — moved <strong>20,175</strong> gems " \
|
|
36
|
+
"out of <code>rejected</code>, among them <code>rspec</code>, <code>globalid</code>, " \
|
|
37
|
+
"<code>mini_portile2</code> and <code>coffee-rails</code>." },
|
|
38
|
+
{ rev: "a782696", date: "2026-06-03", commit: "StringScanner unscan/check + Error, Time#to_s + puts-nil, Dir.exist? + alias_method dispatch, missing int-hash keys as nil, RBS extractor heterogeneous-union→poly, subclass-initialize poly unification (13 commits)",
|
|
39
|
+
file: "survey-a782696/compat.jsonl",
|
|
40
|
+
note: "<strong>A consolidation rev.</strong> The base verdict mix is essentially flat after the previous " \
|
|
41
|
+
"jump — 13 upstream commits of correctness fixes (<code>StringScanner</code>, <code>Time#to_s</code>, " \
|
|
42
|
+
"<code>Dir.exist?</code>/<code>alias_method</code>, and the RBS-extractor union→poly change) graduated a " \
|
|
43
|
+
"small set of gems — <code>google-adwords-api</code>, <code>libdatadog</code>, <code>random_user_agent</code>, " \
|
|
44
|
+
"<code>twitter_username_extractor</code> and the <code>redcar-*</code> cluster — while a couple regressed " \
|
|
45
|
+
"and were caught by the re-probe. The bigger story this rev was off the catalog: the harness found " \
|
|
46
|
+
"<code>spinel_analyze</code> consuming 100+ GB on a cluster of auto-generated API-SDK gems (a compiler " \
|
|
47
|
+
"memory blow-up, filed upstream)." },
|
|
48
|
+
{ rev: "9c0a5f0", date: "2026-06-04", commit: "79 commits — incl. fixes for 6 harness-filed issues: stdlib-class-in-ivar (#1305), reopen-Object (#1306), lambda/proc branch-local (#1315), &blk+block_given? (#1316), inject(&:sym) (#1317), ignored-require constant (#1273)",
|
|
49
|
+
file: "survey-9c0a5f0/compat.jsonl",
|
|
50
|
+
note: "<strong>The harness loop paying off.</strong> matz landed fixes for <strong>six</strong> issues this " \
|
|
51
|
+
"harness filed the day before — all common idioms: <code>block_given?</code> with a named " \
|
|
52
|
+
"<code>&blk</code> (<a href=\"https://github.com/matz/spinel/issues/1316\">#1316</a>), " \
|
|
53
|
+
"<code>inject(&:+)</code> (<a href=\"https://github.com/matz/spinel/issues/1317\">#1317</a>), " \
|
|
54
|
+
"reopening <code>class Object</code> (<a href=\"https://github.com/matz/spinel/issues/1306\">#1306</a>), " \
|
|
55
|
+
"a stdlib class held in an instance variable " \
|
|
56
|
+
"(<a href=\"https://github.com/matz/spinel/issues/1305\">#1305</a>), and a branch-assigned local inside a " \
|
|
57
|
+
"lambda/proc (<a href=\"https://github.com/matz/spinel/issues/1315\">#1315</a>). <strong>3,487</strong> gems " \
|
|
58
|
+
"moved out of <code>rejected</code> (110.3k→106.8k). The one feature ruled out of scope — aliasing the " \
|
|
59
|
+
"regexp special globals (<a href=\"https://github.com/matz/spinel/issues/1307\">#1307</a>) — now fails with a " \
|
|
60
|
+
"clear diagnostic instead of bad C." },
|
|
61
|
+
{ rev: "5c9790c", date: "2026-06-05", commit: "17 commits — fixes for 3 harness-filed typed-collection issues: Hash#fetch on int_int_hash (#1329), Array#join on poly_array (#1332), Class-in-collection→poly (#1337); plus regex line-anchoring/gsub-buffer + first-class string type",
|
|
62
|
+
file: "survey-5c9790c/compat.jsonl",
|
|
63
|
+
note: "<strong>Typed-collection coverage.</strong> matz fixed three issues this harness filed hours earlier — all " \
|
|
64
|
+
"the same shape: a method that exists on the generic path but was missing on a <em>specialized</em> " \
|
|
65
|
+
"collection. <code>Hash#fetch</code> on an int→int hash " \
|
|
66
|
+
"(<a href=\"https://github.com/matz/spinel/issues/1329\">#1329</a>), <code>Array#join</code> on a mixed " \
|
|
67
|
+
"<code>poly_array</code> (<a href=\"https://github.com/matz/spinel/issues/1332\">#1332</a>), and a " \
|
|
68
|
+
"<code>Class</code> value stored in a Hash/Array now typed as poly instead of int " \
|
|
69
|
+
"(<a href=\"https://github.com/matz/spinel/issues/1337\">#1337</a>, which had broken every options-hash " \
|
|
70
|
+
"carrying an exception class). The compile+scan base barely moves on fixes like these — they're " \
|
|
71
|
+
"full-surface/runtime, so the graduation shows in the behaviour-verified tier." },
|
|
72
|
+
{ rev: "57af7f9", date: "2026-06-07", commit: "~40 commits — 9 harness-filed issues closed in one wave: the ecosystem-spine front doors (alias→attr_reader #1356, &:sym-after-positional parse #1359), unary operator mangling (#1357), sp_sym_intern link (#1355), plus typed-collection nil steps (#801/#1180) and #line / --emit-symbol-map diagnostics (the #1338 RFC direction)",
|
|
73
|
+
file: "survey-57af7f9/compat.jsonl",
|
|
74
|
+
note: "<strong>The spine-gems wave.</strong> Auditing why <code>bundler</code>/<code>rake</code>/" \
|
|
75
|
+
"<code>minitest</code>/<code>thor</code> reject found two shallow front doors — " \
|
|
76
|
+
"<code>alias</code> to an <code>attr_reader</code>-generated method " \
|
|
77
|
+
"(<a href=\"https://github.com/matz/spinel/issues/1356\">#1356</a>, rake + thor) and " \
|
|
78
|
+
"<code>&:sym</code> after a positional argument mis-parsed as a hash literal " \
|
|
79
|
+
"(<a href=\"https://github.com/matz/spinel/issues/1359\">#1359</a>, bundler + minitest) — and matz closed " \
|
|
80
|
+
"both within a day, alongside 7 more harness filings. All four spine gems now compile past their old " \
|
|
81
|
+
"blockers into distinct second-tier issues " \
|
|
82
|
+
"(<a href=\"https://github.com/matz/spinel/issues/1368\">#1368</a> et al.). C compile errors now map back " \
|
|
83
|
+
"to Ruby source lines via <code>#line</code>, on by default — the " \
|
|
84
|
+
"<a href=\"https://github.com/matz/spinel/issues/1338\">#1338</a> RFC direction. The behaviour-verified " \
|
|
85
|
+
"tier reached 144 mechanical ★ this run." },
|
|
86
|
+
{ rev: "cb23cc6", date: "2026-06-09", commit: "~23 commits — 12 harness-filed issues closed in one wave, incl. Mutex/Monitor#synchronize + Thread.new now running and carrying their block value (#1360), class-instance-vars in class methods (#1352), is_a?(IncludedModule)/ancestors (#1350), \\h/\\H regex (#1349), reopened-builtin optional defaults (#1348), bitwise/shift operator mangling (#1358/#1368), the captured-&block value/type family",
|
|
87
|
+
file: "survey-cb23cc6/compat.jsonl",
|
|
88
|
+
note: "<strong>Mutex/Thread come in from the cold.</strong> With matz/spinel#1360 landing " \
|
|
89
|
+
"(<code>Mutex#synchronize</code>/<code>Thread.new</code> now run single-threaded and carry their " \
|
|
90
|
+
"block's value), the static pre-filter no longer hard-rejects them — they're flagged " \
|
|
91
|
+
"<code>risky</code> (correct for defensive use, degenerate only for true concurrency). Re-probing " \
|
|
92
|
+
"the 8,833 gems that had been rejected on sight for <code>Mutex.new</code>/<code>Thread.new</code>, " \
|
|
93
|
+
"<strong>3,493 (39.5%) moved out of <code>rejected</code></strong> — they compile now; the static " \
|
|
94
|
+
"wall had been hiding it. The four spine gems (<code>bundler</code>/<code>rake</code>/" \
|
|
95
|
+
"<code>minitest</code>/<code>thor</code>) now reject for their <em>real</em> reason — the deep " \
|
|
96
|
+
"metaprogramming surface (<code>send</code>/<code>method_missing</code>/<code>cattr_accessor</code>) — " \
|
|
97
|
+
"not a misleading <code>hard:Mutex.new</code>. The differential re-audit also caught one regression in " \
|
|
98
|
+
"the wave: returning <code>self</code> from a reopened-builtin method " \
|
|
99
|
+
"(<a href=\"https://github.com/matz/spinel/issues/1386\">#1386</a>, <code>to-bool</code>), filed same day." },
|
|
100
|
+
{ rev: "b60fbd7", date: "2026-06-15", commit: "636 commits — the largest wave yet. A type-inference + codegen rewrite (typed-array/poly ~222, inference/cast/box ~168, string ~124, block/yield ~101, module/singleton ~94) closing many harness-filed issues at once: reopened-builtin self (#1386), hash-block String type (#1382/#1394), circular require_relative (#1373), computed require (#1383), class-method yield (#1387), Scheduled-server boot + SIGTERM (#1369/#1384). New surfaces: value-type objects, --rbs/--emit-rbs/--emit-symbol-map.",
|
|
101
|
+
file: "survey-b60fbd7/compat.jsonl",
|
|
102
|
+
note: "<strong>The largest movement in the catalog's history.</strong> 636 upstream commits — a type-inference " \
|
|
103
|
+
"rewrite that resolved the entire <code>unresolved:<method></code> reject family (~60k records → ~0): " \
|
|
104
|
+
"<strong>~49,000 gems moved out of <code>rejected</code></strong> (−46%), +22k to <code>clean</code>. The " \
|
|
105
|
+
"wave also changed unresolvable <code>require</code> to hard-fail the compile (post-#1383); because " \
|
|
106
|
+
"<code>require \"gem/version\"</code> is near-universal that spuriously rejected thousands, so the probe " \
|
|
107
|
+
"now classifies a require-only failure as the no-load-path limit (<code>risky [load-path:require]</code>, " \
|
|
108
|
+
"9,716 gems corrected). Cost side, caught by re-verifying every ★ at this rev: <strong>~34 of 175 " \
|
|
109
|
+
"behaviour-verified gems regressed</strong> (12 miscompiles incl. <code>after_commit_action</code>, 22 " \
|
|
110
|
+
"codegen incl. bare-<code>super</code>/<code>ForwardingSuperNode</code>) — the trust tier dropped 175→136. " \
|
|
111
|
+
"Fresh-verify overrode stickiness, so the ★ count is honest, not carried." },
|
|
29
112
|
].freeze
|
|
30
113
|
|
|
31
114
|
ORDER = %w[clean risky rejected].freeze
|
|
@@ -133,7 +216,7 @@ module Bundler
|
|
|
133
216
|
<title>#{h title}</title><link rel="stylesheet" href="/assets/style.css"></head>
|
|
134
217
|
<body>
|
|
135
218
|
<header><a class="brand" href="/">#{gem}SpinelGems</a>
|
|
136
|
-
<nav><a href="/">Home</a> <a href="/catalog">Catalog</a> <a href="/history.html">History</a>
|
|
219
|
+
<nav><a href="/">Home</a> <a href="/catalog">Catalog</a> <a href="/load-bearing.html">Load-bearing</a> <a href="/history.html">History</a>
|
|
137
220
|
<a href="https://github.com/OriPekelman/spinelgems">GitHub</a></nav></header>
|
|
138
221
|
<main>
|
|
139
222
|
#{body}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
require "cgi"
|
|
2
|
+
require_relative "site"
|
|
3
|
+
|
|
4
|
+
module Bundler
|
|
5
|
+
module Spinel
|
|
6
|
+
# The "build-it-first" roadmap page: which gems, if made to compile, unblock
|
|
7
|
+
# the most of the ecosystem. Reads the committed
|
|
8
|
+
# harness/load-bearing/targets.tsv (transitive load-bearing + buildability
|
|
9
|
+
# impact, precomputed from the local dependency graph) and renders a clear,
|
|
10
|
+
# status-coloured table. Static, committed in site/ like the history page.
|
|
11
|
+
class LoadBearing
|
|
12
|
+
DATA = File.expand_path("../../../harness/load-bearing/targets.tsv", __dir__)
|
|
13
|
+
GLYPH = Site::GLYPH
|
|
14
|
+
|
|
15
|
+
# Buildability snapshot @ 95557f5 (from harness/load-bearing/buildability.rb).
|
|
16
|
+
BUILDABLE = 50_688
|
|
17
|
+
BLOCKED = 29_139
|
|
18
|
+
REJECTED = 110_256
|
|
19
|
+
|
|
20
|
+
def initialize(data = DATA) = (@data = data)
|
|
21
|
+
|
|
22
|
+
def build_html(out)
|
|
23
|
+
rows = File.exist?(@data) ? File.readlines(@data)[1..].map { |l| l.chomp.split("\t") } : []
|
|
24
|
+
compiler = rows.select { |r| r[7] == "compiler" }.sort_by { |r| -r[1].to_i }
|
|
25
|
+
File.write(out, page("Load-bearing gems — SpinelGems", body(compiler, rows)))
|
|
26
|
+
out
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def body(compiler, all)
|
|
32
|
+
b = +""
|
|
33
|
+
b << "<h1>Load-bearing gems</h1>\n"
|
|
34
|
+
b << %(<p class="lede">A gem matters to the ecosystem by how many gems pull it in )
|
|
35
|
+
b << %(<em>transitively</em> — directly, or as a dependency of a dependency, turtles all )
|
|
36
|
+
b << %(the way down. If Spinel can't compile a load-bearing gem, nothing above it can ship )
|
|
37
|
+
b << %(either. This is the build-it-first roadmap.</p>\n)
|
|
38
|
+
|
|
39
|
+
b << %(<div class="stat-row">\n)
|
|
40
|
+
b << stat("buildable", BUILDABLE, "whole dependency tree compiles")
|
|
41
|
+
b << stat("blocked", BLOCKED, "compiles itself — but a dependency is rejected")
|
|
42
|
+
b << stat("rejected", REJECTED, "doesn't compile")
|
|
43
|
+
b << %(</div>\n)
|
|
44
|
+
b << %(<p class="note">~<strong>#{fmt(BLOCKED)}</strong> gems compile on their own but )
|
|
45
|
+
b << %(can't actually be used because something beneath them is rejected. Fixing a load-bearing )
|
|
46
|
+
b << %(blocker flows <em>up</em> the tree — its dependents become buildable too.</p>\n)
|
|
47
|
+
|
|
48
|
+
b << %(<h2>Build-it-first targets</h2>\n)
|
|
49
|
+
b << %(<p class="sub">Ranked by <strong>impact</strong> — how many blocked gems become )
|
|
50
|
+
b << %(buildable if this one alone is fixed. Filtered to <strong>compiler-fixable</strong> )
|
|
51
|
+
b << %(failures: the native (C-extension) and heavy-metaprogramming (Rails-shaped) clusters )
|
|
52
|
+
b << %(are deliberately set aside as not the first target here. Each row carries two ways to )
|
|
53
|
+
b << %(fix it — a focused Spinel issue, or, for a small library, a PR to the gem itself.</p>\n)
|
|
54
|
+
|
|
55
|
+
b << %(<table id="catalog"><thead><tr>)
|
|
56
|
+
b << %(<th class="num">impact</th><th>gem</th><th>status</th><th class="num">load-bearing</th>)
|
|
57
|
+
b << %(<th>failure</th><th class="num">lib size</th><th>fix</th></tr></thead><tbody>\n)
|
|
58
|
+
compiler.first(120).each do |r|
|
|
59
|
+
gem, sole, _reach, transit, _dl, verdict, ftype, _ach, files, loc = r
|
|
60
|
+
fi = files.to_i
|
|
61
|
+
strat = fi.positive? && fi <= 12 ? %(<span class="badge human">lib PR</span> small — #{fi} files) :
|
|
62
|
+
%(<span class="badge rubric">Spinel issue</span>)
|
|
63
|
+
b << %(<tr>)
|
|
64
|
+
b << %(<td class="num"><strong>#{fmt sole}</strong></td>)
|
|
65
|
+
b << %(<td class="g">#{h gem}</td>)
|
|
66
|
+
b << %(<td class="v #{verdict}">#{GLYPH[verdict] || '?'} #{h verdict}</td>)
|
|
67
|
+
b << %(<td class="num">#{fmt transit}</td>)
|
|
68
|
+
b << %(<td><span class="badge rubric">#{h ftype}</span></td>)
|
|
69
|
+
b << %(<td class="num">#{fi.positive? ? "#{fi}f / #{loc}L" : "—"}</td>)
|
|
70
|
+
b << %(<td>#{strat}</td>)
|
|
71
|
+
b << %(</tr>\n)
|
|
72
|
+
end
|
|
73
|
+
b << "</tbody></table>\n"
|
|
74
|
+
|
|
75
|
+
nat = all.count { |r| r[7] == "native" }; mp = all.count { |r| r[7] == "metaprog" }
|
|
76
|
+
b << %(<p class="meta">Set aside as not-first-target: <strong>#{nat}</strong> native )
|
|
77
|
+
b << %(C-extension blockers (need FFI/ext vendoring) and <strong>#{mp}</strong> heavy-metaprogramming )
|
|
78
|
+
b << %(blockers (the Rails ecosystem). Method + data: )
|
|
79
|
+
b << %(<a href="https://github.com/OriPekelman/spinelgems/blob/main/docs/load-bearing-gems.md">docs/load-bearing-gems.md</a> · )
|
|
80
|
+
b << %(<a href="https://github.com/OriPekelman/spinelgems/blob/main/harness/load-bearing/">harness/load-bearing/</a>. )
|
|
81
|
+
b << %(Built locally from the gem cache's dependency graph; impact is per engine revision.</p>\n)
|
|
82
|
+
b
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def stat(cls, n, label)
|
|
86
|
+
%( <div class="stat #{cls}"><b>#{fmt n}</b><span>#{label}</span></div>\n)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def fmt(n)
|
|
90
|
+
n = n.to_i
|
|
91
|
+
n >= 1000 ? "#{(n / 1000.0).round(1)}k" : n.to_s
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def h(s) = CGI.escapeHTML(s.to_s)
|
|
95
|
+
|
|
96
|
+
def page(title, body)
|
|
97
|
+
gem = %(<svg class="gem" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 3h12l4 6-10 13L2 9z" fill="#7b2d8e"/><path d="M6 3 2 9l10 13z" fill="#5a1f6b" opacity=".55"/><path d="M18 3l4 6-10 13z" fill="#b14fc4"/><path d="M6 3h12l-6 6z" fill="#d98ee8"/></svg>)
|
|
98
|
+
<<~HTML
|
|
99
|
+
<!doctype html>
|
|
100
|
+
<html lang="en"><head><meta charset="utf-8">
|
|
101
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
102
|
+
<title>#{h title}</title><link rel="stylesheet" href="/assets/style.css"></head>
|
|
103
|
+
<body>
|
|
104
|
+
<header><a class="brand" href="/">#{gem}SpinelGems</a>
|
|
105
|
+
<nav><a href="/">Home</a> <a href="/catalog">Catalog</a> <a href="/load-bearing.html">Load-bearing</a>
|
|
106
|
+
<a href="/history.html">History</a> <a href="https://github.com/OriPekelman/spinelgems">GitHub</a></nav></header>
|
|
107
|
+
<main>
|
|
108
|
+
#{body}
|
|
109
|
+
</main>
|
|
110
|
+
#{Site::FOOTER_HTML}
|
|
111
|
+
</body></html>
|
|
112
|
+
HTML
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -65,17 +65,19 @@ module Bundler
|
|
|
65
65
|
file.empty? ? "line #{v['line']}" : "#{file}:#{v['line']}"
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
# Resolve bisect.sh. It lives in spinel-dev (separate from
|
|
69
|
-
# engine), so there's no fixed relation to the engine dir —
|
|
70
|
-
# conventional spots, newest-intent first: an explicit override,
|
|
71
|
-
#
|
|
68
|
+
# Resolve bisect.sh. It lives in the spinel-dev tooling repo (separate from
|
|
69
|
+
# the compiler engine), so there's no fixed relation to the engine dir —
|
|
70
|
+
# probe a few conventional spots, newest-intent first: an explicit override,
|
|
71
|
+
# a sibling checkout next to the engine, the conventional ~/sites layout.
|
|
72
|
+
# The local checkout may be named spinel-tools (gx10) or spinel-dev (the
|
|
73
|
+
# GitHub repo name), so probe both.
|
|
72
74
|
def bisect_script
|
|
73
75
|
rel = "tools/value-bisect/bisect.sh"
|
|
74
|
-
candidates = [
|
|
75
|
-
|
|
76
|
-
File.expand_path(File.join(@engine.dir, "..",
|
|
77
|
-
File.expand_path("~/sites
|
|
78
|
-
|
|
76
|
+
candidates = [ENV["SPINEL_BISECT"]]
|
|
77
|
+
%w[spinel-tools spinel-dev].each do |repo|
|
|
78
|
+
candidates << File.expand_path(File.join(@engine.dir, "..", repo, rel))
|
|
79
|
+
candidates << File.expand_path("~/sites/#{repo}/#{rel}")
|
|
80
|
+
end
|
|
79
81
|
candidates.find { |c| c && File.exist?(c) }
|
|
80
82
|
end
|
|
81
83
|
end
|