Class: Bundler::Spinel::Why
- Inherits:
-
Object
- Object
- Bundler::Spinel::Why
- Defined in:
- lib/bundler/spinel/why.rb
Overview
‘spinel-compat why <gem>` — a legible “why doesn’t this gem work (yet)?” report (spinelgems#12). Turns a recorded (or freshly probed) Verdict into plain English: the cause, a category (Spinel limitation vs fixable compiler bug vs native C-ext vs metaprogramming vs dependency-blocked), the specific evidence, and what it would take — including whether the verdict is TERMINAL (won’t improve without an upstream/compiler change) or FIXABLE.
The data is already in the ledger (the rubric tag, the spinel warnings distilled into ‘reasons`, the static `risks`); this assembles it instead of making a user grep C output. Where deeper localization helps, it points at `why –probe` (live compiler output) / spinel-dev doctor for deeper localization.
Constant Summary collapse
- EXPLAIN =
rubric/risk/reason signal -> structured explanation. :terminal is
:native — won't work without a Spinel-native port (terminal here) :limitation— a Spinel feature gap (improves when the compiler grows it) :bug — a fixable Spinel compiler bug (file/track upstream) :dep — blocked by a dependency (improves when the dep does) :ok — already usable; nothing to fix { "c-extension" => { category: "native (C extension)", terminal: :native, cause: "ships a C extension; Spinel is whole-program AOT and never dlopens a .so.", take: "port the extension to Spinel's ffi_cflags/ffi_func DSL (tep/SpinelKit pattern), " \ "or consume a pure-Ruby alternative. The CRuby .so cannot be vendored.", }, "needs-dep" => { category: "dependency / not self-contained", terminal: :dep, cause: "fails under CRuby in the harness too — it needs an external gem, TLS, or network the probe doesn't provide.", take: "vendor the missing dependency (must itself be Spinel-compatible) or smoke only the offline surface.", }, "load-path" => { category: "Spinel limitation (load path)", terminal: :limitation, cause: %(Spinel ignored a plain `require "gem/part"` (it has no load path), so the gem's real classes never compiled.), take: "restructure the gem to require_relative its own files (Spinel inlines those), or wait on load-path support.", }, "needs-stdlib" => { category: "Spinel limitation (stdlib surface)", terminal: :limitation, cause: "requires a standard-library feature Spinel doesn't ship.", take: "use a Spinel-safe shim for that surface (SpinelKit consolidates these: JSON/Logger/…), or wait on stdlib coverage.", }, "codegen" => { category: "compiler bug (codegen)", terminal: :bug, cause: "ordinary Ruby produced a C compile error — a fixable Spinel codegen bug, not a limitation of your code.", take: "file/track a matz/spinel issue. `why <gem> --probe` shows the live compiler error; spinel-dev doctor " \ "localizes it to a file:line, and the harness usually has a minimal reproducer already.", }, "miscompile" => { category: "compiler bug (silent miscompile)", terminal: :bug, cause: "it compiles and runs, but the output diverges from CRuby — the most dangerous failure, silently wrong.", take: "file a matz/spinel issue with the diff below; spinel-dev doctor + value-bisection localize it to a file:line + variable.", }, "unsupported" => { category: "unsupported call (often metaprogramming)", terminal: :bug, cause: "Spinel could not resolve a call and silently emitted 0 — typically dynamic dispatch (send/define_method/extend).", take: "if it's a small codegen gap, file a matz/spinel issue; if it's deep metaprogramming, the surface is currently unsupported.", }, "build-error" => { category: "build/run error", terminal: :bug, cause: "the Spinel build or run failed for a reason outside the other buckets.", take: "inspect the reasons below; `why <gem> --probe` re-runs the compiler and surfaces the raw error line.", }, "smoke-error" => { category: "inconclusive (smoke broken under CRuby)", terminal: :dep, cause: "the behaviour smoke didn't run cleanly under plain CRuby, so no Spinel conclusion can be drawn.", take: "fix the smoke (a self-contained example of the gem's API), then re-verify.", }, "analyze-oom" => { category: "compiler bug (analyzer OOM)", terminal: :bug, cause: "the Spinel analyzer exhausts memory on this gem (matz/spinel#1302); it's blacklisted from probing.", take: "terminal until matz/spinel#1302 lands; tracked there with reproducers.", }, # Static pre-filter rejections (probe=static): a hard construct found by # source scan before any compile. Thread/Mutex compile now but misbehave # (matz/spinel#1360); TracePoint/ObjectSpace are out of the AOT model. "hard-construct" => { category: "Spinel limitation (runtime construct)", terminal: :limitation, cause: "uses a construct the static filter rejects before compiling (threads/mutexes/tracing).", take: "Thread/Mutex are single-thread-degradable (matz/spinel#1360); TracePoint/ObjectSpace/set_trace_func " \ "are outside the closed-world AOT model. Often the gem works once that one construct is shimmed.", }, }.freeze
- POSITIVE =
{ "verified" => "compiles, and a behaviour smoke runs identically under CRuby and a Spinel-compiled binary. " \ "Trustworthy — the only verdict that earns a curated-source slot.", "loaded" => "compiles and loads identically to CRuby, but no behaviour smoke has exercised its logic at this rev — " \ "so a silent miscompile in that logic is still possible. Run `verify --smoke <file>` to lift it to verified.", "clean" => "compiles clean and uses no dynamic constructs Spinel degrades — but it has only been compiled, not run. " \ "Run `verify` (require-only → loaded) or `verify --smoke` (behaviour → verified) to confirm it works.", "risky" => "compiles, but the source uses dynamic constructs Spinel degrades silently — allowed by default, " \ "rejected under `check --strict`. Whether it actually works depends on whether those paths run; " \ "a behaviour smoke (`verify --smoke`) is the only way to know.", }.freeze
- USABLE =
%w[verified loaded clean risky].freeze
Instance Method Summary collapse
-
#initialize(out: $stdout) ⇒ Why
constructor
A new instance of Why.
-
#report(v, source: "ledger") ⇒ Object
Render the report for a Verdict (from the ledger or a live probe).
Constructor Details
#initialize(out: $stdout) ⇒ Why
Returns a new instance of Why.
101 102 103 |
# File 'lib/bundler/spinel/why.rb', line 101 def initialize(out: $stdout) @out = out end |
Instance Method Details
#report(v, source: "ledger") ⇒ Object
Render the report for a Verdict (from the ledger or a live probe).
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/bundler/spinel/why.rb', line 106 def report(v, source: "ledger") @out.puts @out.puts "spinel-compat why #{v.gem} (#{v.version} @ #{v.rev || 'unknown rev'}, via #{source})" @out.puts glyph = { "verified" => "★", "loaded" => "○", "clean" => "✓", "risky" => "~", "rejected" => "✗" }[v.verdict] || "?" line "verdict", "#{glyph} #{v.verdict}" if USABLE.include?(v.verdict) positive(v, glyph) else negative(v) end @out.puts v end |