bundler-spinel 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76d26ae3fb919613ecbd538b81af7c07d1000ac69283770dcbe402f25e37e6fa
4
- data.tar.gz: f445da298655fb8e24ef3c862d68e93620ff3271cfbcc25926667c771331b01d
3
+ metadata.gz: 12d700f606c1cf8b69ae4fe623c16293bfbc560fe09b59c8fadeb4529a8a02f6
4
+ data.tar.gz: e08a61f93c03930e4c85b0e1effbaf3dfb2e05f3bd1d1979459fe4e667de4f4c
5
5
  SHA512:
6
- metadata.gz: b719d83dde793a35980b3a607625905f9da5e294ce762f39a74039a9fce4771595b5a0734ee4ccf732f549b3378e9e9db28d93f7d70da7f88d976a30f095d38e
7
- data.tar.gz: aa986cc8de2f1d84fcc0eddc5d2f8627802bf49cb218e8160b4741b59185bc351a1be98d123febd55293e703c9dd00b19fae73b88fcf4bc7842891898db3a5be
6
+ metadata.gz: 3390679f31399277e342e4853530a15d9017aa8de9cbb822dc933420953e4badc97ca497302cbfa8acf29dc6cb1380f2508c515b7e00aa1e5a0eaca502a64e73
7
+ data.tar.gz: ccf7ac613f132909ae9598f01be97c14c6f4fc12dcd70a5352c2ea4f62beb2303589463039c7c98d8787918131323c83cbe2f101ababae9e2161afa6a64c8a5e
data/CHANGELOG.md CHANGED
@@ -4,6 +4,37 @@ 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.2.0] — 2026-06-01
8
+
9
+ ### Added
10
+ - **`verify` self-localizes a miscompile.** When the differential smoke diverges
11
+ (CRuby and Spinel both run but disagree on stdout), `verify` now runs the
12
+ value-bisection harness (`spinel-dev tools/value-bisect/bisect.sh --json`) on
13
+ the still-on-disk harness and, when it pins the first scalar local to part
14
+ ways, appends a `localized:<file>:<line> <var> cruby=… spinel=…` reason. This
15
+ upgrades a bare `diff:L2 cruby=… spinel=…` ("the outputs differ") into a line
16
+ to look at ("`x` is wrong here"). Strictly best-effort and non-fatal: if the
17
+ harness can't be found (it's a separate repo — override with `SPINEL_BISECT`,
18
+ else probed next to the engine and at `~/sites/spinel-dev`) or can't attribute
19
+ the divergence to a traced scalar, the verdict is returned unchanged.
20
+
21
+ ### Changed
22
+ - **`init` scaffold uses the published tep gem.** Now that tep is on RubyGems
23
+ (`tep 0.11.1`), the generated `Gemfile` emits plain `gem "tep", ">= 0.11.1"`
24
+ instead of the `git:`-fallback comment. Docs (`README`, `docs/adoption.md`)
25
+ updated to the published-gem form. spinelgems#10.
26
+ - **`init`'s `bin/build` made self-sufficient + correct.** A clean-room run
27
+ showed the old `bundle install && ./bin/build` flow couldn't run: the
28
+ `engine: spinel` marker makes `bundle install` refuse under CRuby (by design),
29
+ and `bundle lock` + `vendor` place tep's *lib* but never install the `tep`
30
+ *CLI*. `bin/build` now `gem install`s tep if absent, uses `bundle lock` (not
31
+ install), exports `SPINEL` (so tep finds the provisioned engine), then
32
+ provisions + vendors + compiles; the `init` next-steps reflect this.
33
+ **The full onboarding now runs end-to-end** — validated in a clean `ruby:3.3`
34
+ container: `gem install bundler-spinel` → `init` → `./bin/build` → `./app`
35
+ serves a Spinel-compiled Tep app (needs `tep ≥ 0.11.1`, which builds its C
36
+ helpers on demand). spinelgems#10.
37
+
7
38
  ## [0.1.1] — 2026-06-01
8
39
 
9
40
  ### Fixed
@@ -67,6 +98,7 @@ and the ledger format may all change before `0.0.1`. Install with `--pre`.
67
98
  - Wholesale `survey` with a thread-safe ledger, a per-compile wall-clock
68
99
  timeout (`analyze-timeout` reject reason), and a ledger-based report.
69
100
 
101
+ [0.2.0]: https://github.com/OriPekelman/spinelgems/releases/tag/v0.2.0
70
102
  [0.1.1]: https://github.com/OriPekelman/spinelgems/releases/tag/v0.1.1
71
103
  [0.1.0]: https://github.com/OriPekelman/spinelgems/releases/tag/v0.1.0
72
104
  [0.0.1.pre]: https://github.com/OriPekelman/spinelgems/releases/tag/v0.0.1.pre
data/README.md CHANGED
@@ -37,8 +37,8 @@ limited and still growing. So we surveyed the ecosystem and publish a
37
37
  source "https://rubygems.org"
38
38
  ruby "3.3.0", engine: "spinel", engine_version: "0.0.0"
39
39
 
40
- gem "tep", git: "https://…/tep.git" # siblings via path:/git:
41
- gem "some_pure_ruby_lib"
40
+ gem "tep" # published on RubyGems
41
+ gem "some_unreleased_lib", git: "…" # or a sibling via path:/git:
42
42
  ```
43
43
 
44
44
  `bundle lock` resolves normally (it ignores the engine marker); the marker guards
@@ -176,9 +176,11 @@ module Bundler
176
176
  out.puts "Scaffolded a Spinel + Tep project in #{dir}/"
177
177
  out.puts "Next:"
178
178
  out.puts " cd #{dir}" unless File.expand_path(dir) == Dir.pwd
179
- out.puts " bundle install # resolves deps (gated by the compat ledger)"
180
- out.puts " ./bin/build # provisions Spinel, vendors deps, compiles app.rb"
179
+ out.puts " ./bin/build # ensures tep, resolves + vendors deps, provisions Spinel, compiles"
181
180
  out.puts " ./app -p 4567 # run the native binary"
181
+ out.puts ""
182
+ out.puts "(The `engine: spinel` Gemfile marker makes `bundle install` refuse to run"
183
+ out.puts " under CRuby by design — bin/build uses `bundle lock` + `spinel-compat vendor`.)"
182
184
  end
183
185
 
184
186
  def write(out, path, body)
@@ -200,9 +202,10 @@ module Bundler
200
202
  # engine revision install-engine builds is pinned in ./SPINEL_PIN.
201
203
  ruby "3.3.0", engine: "spinel", engine_version: "0.0.0"
202
204
 
203
- # The web framework — Sinatra-style, compiles via Spinel. Until tep is on
204
- # RubyGems, point at the repo: gem "tep", git: "https://github.com/OriPekelman/tep.git"
205
- gem "tep"
205
+ # The web framework — Sinatra-style, compiles via Spinel. >= 0.11.1 builds
206
+ # its C helpers on demand (needed for `gem install tep` without `make`).
207
+ # (For an unreleased sibling instead: gem "tep", git: "https://github.com/OriPekelman/tep.git")
208
+ gem "tep", ">= 0.11.1"
206
209
  RUBY
207
210
  end
208
211
 
@@ -219,12 +222,16 @@ module Bundler
219
222
  def build_sh
220
223
  <<~SH
221
224
  #!/usr/bin/env bash
222
- # Provision the Spinel compiler, vendor deps where Spinel finds them,
223
- # and compile app.rb to a native binary.
225
+ # From a fresh checkout to a native binary. The Gemfile's `engine: spinel`
226
+ # marker makes `bundle install` refuse to run under CRuby, so we resolve
227
+ # with `bundle lock` and place deps with `spinel-compat vendor` instead.
224
228
  set -e
225
- spinel-compat install-engine # fetch+build the pinned engine (cached)
226
- spinel-compat vendor # place deps + write deps.rb
227
- tep build app.rb -o app # compile -> ./app
229
+ command -v tep >/dev/null 2>&1 || gem install tep # the tep build CLI (a compile-time tool)
230
+ [ -f Gemfile.lock ] || bundle lock # resolve deps (NOT `bundle install`)
231
+ spinel-compat install-engine # fetch+build the pinned engine (cached)
232
+ export SPINEL="${SPINEL:-$HOME/.cache/spinel/current/spinel}" # tell tep where the engine is
233
+ spinel-compat vendor # place deps where Spinel follows them
234
+ tep build app.rb -o app # compile -> ./app
228
235
  echo "built ./app — run it with: ./app -p 4567"
229
236
  SH
230
237
  end
@@ -0,0 +1,83 @@
1
+ require "open3"
2
+ require "json"
3
+
4
+ module Bundler
5
+ module Spinel
6
+ # Upgrades a bare `miscompile` verdict (CRuby and Spinel agree to run but
7
+ # disagree on stdout) into a *located* one: the (file, line, variable) where
8
+ # a scalar local first diverges — via the value-bisection harness that lives
9
+ # in the spinel-dev repo (tools/value-bisect/bisect.sh).
10
+ #
11
+ # Why: a miscompile reason of `diff:L2 cruby="42" spinel="0"` says two outputs
12
+ # differ but not *why*. The bisector traces every scalar local under both
13
+ # CRuby and a Spinel --debug build and reports the first to part ways, turning
14
+ # that into `localized:foo.rb:12 total cruby=42 spinel=0` — a line to look at.
15
+ #
16
+ # Strictly best-effort and non-fatal. If the harness can't be found (it's a
17
+ # separate repo), can't run (the engine dir is a bare binary with no compiler
18
+ # sources / no lldb), or can't pin a scalar (the divergence is in output
19
+ # formatting, not a traced local), verify still returns the miscompile verdict
20
+ # unchanged — this only ever *adds* a `localized:` reason when it has one.
21
+ class Localizer
22
+ def initialize(engine)
23
+ @engine = engine
24
+ end
25
+
26
+ # Run the bisector on `harness` (a self-contained .rb; require_relative'd
27
+ # files are traced too) and return a short reason string, or nil when
28
+ # localization isn't possible or didn't pin a value.
29
+ def localize(harness)
30
+ script = bisect_script or return nil
31
+ # SPINEL_DIR points the bisector at the same compiler the engine uses;
32
+ # it shells out to that checkout's spinel_analyze.rb / spinel_codegen.rb.
33
+ # bisect.sh exits 1 *on divergence* (the case we want), so the exit code
34
+ # is not a success signal — parse stdout regardless. stdout carries only
35
+ # the single JSON object in --json mode; progress goes to stderr.
36
+ out, _err, _st = Open3.capture3(
37
+ { "SPINEL_DIR" => @engine.dir }, "sh", script, "--json", harness
38
+ )
39
+ line = out.lines.map(&:strip).reject(&:empty?).last or return nil
40
+ verdict = begin
41
+ JSON.parse(line)
42
+ rescue JSON::ParserError
43
+ return nil
44
+ end
45
+ format_verdict(verdict)
46
+ rescue StandardError
47
+ nil
48
+ end
49
+
50
+ private
51
+
52
+ def format_verdict(v)
53
+ case v["verdict"]
54
+ when "diverge"
55
+ "localized:#{loc(v)} #{v['variable']} cruby=#{v['cruby']} spinel=#{v['spinel']}"
56
+ when "crash"
57
+ "localized:crash@#{loc(v)} signal=#{v['signal']}"
58
+ end
59
+ # Any other verdict (ok / exit-differ / abort) means the bisector couldn't
60
+ # attribute the divergence to a traced scalar; leave the verdict un-enriched.
61
+ end
62
+
63
+ def loc(v)
64
+ file = v["file"].to_s
65
+ file.empty? ? "line #{v['line']}" : "#{file}:#{v['line']}"
66
+ end
67
+
68
+ # Resolve bisect.sh. It lives in spinel-dev (separate from the compiler
69
+ # engine), so there's no fixed relation to the engine dir — probe a few
70
+ # conventional spots, newest-intent first: an explicit override, a sibling
71
+ # spinel-dev checkout next to the engine, the conventional ~/sites layout.
72
+ def bisect_script
73
+ rel = "tools/value-bisect/bisect.sh"
74
+ candidates = [
75
+ ENV["SPINEL_BISECT"],
76
+ File.expand_path(File.join(@engine.dir, "..", "spinel-dev", rel)),
77
+ File.expand_path("~/sites/spinel-dev/#{rel}")
78
+ ]
79
+ candidates.find { |c| c && File.exist?(c) }
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,4 +1,5 @@
1
1
  require "open3"
2
+ require_relative "localizer"
2
3
 
3
4
  module Bundler
4
5
  module Spinel
@@ -50,6 +51,15 @@ module Bundler
50
51
  unless verdict == "verified" || verdict == "loaded" || verdict == "clean"
51
52
  reasons = ["rubric:#{rubric(ruby_ok, ruby_err, spin_ok, spin_err, ruby_out, spin_out, gem_name)}"] + reasons
52
53
  end
54
+ # Self-localize a miscompile: a `diff:L2 cruby=… spinel=…` reason says
55
+ # the outputs differ but not where the value went wrong. The bisector
56
+ # traces the still-on-disk harness (require_relative'd gem files included)
57
+ # and, when it pins a diverging scalar, appends `localized:<file>:<line>
58
+ # <var> cruby=… spinel=…`. Best-effort: nil when it can't localize.
59
+ if verdict == "rejected" && reasons.include?("miscompile")
60
+ loc = Localizer.new(@engine).localize(harness)
61
+ reasons += [loc] if loc
62
+ end
53
63
  @ledger.record(@ledger.build(
54
64
  gem: gem_name, version: version, rev: @engine.rev,
55
65
  verdict: verdict, reasons: reasons, probe: full ? "verify-full" : "verify"
@@ -4,6 +4,6 @@ module Bundler
4
4
  # are proven, and `install-engine` now provisions the compiler too, so a
5
5
  # newcomer can `gem install bundler-spinel` and onboard without an
6
6
  # out-of-band Spinel build (spinelgems#9).
7
- VERSION = "0.1.1"
7
+ VERSION = "0.2.0"
8
8
  end
9
9
  end
@@ -10,6 +10,7 @@ require_relative "spinel/ledger"
10
10
  require_relative "spinel/gem_fetcher"
11
11
  require_relative "spinel/probe"
12
12
  require_relative "spinel/verifier"
13
+ require_relative "spinel/localizer"
13
14
  require_relative "spinel/vendorer"
14
15
  require_relative "spinel/survey"
15
16
  require_relative "spinel/checker"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler-spinel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ori Pekelman
@@ -37,6 +37,7 @@ files:
37
37
  - lib/bundler/spinel/gem_fetcher.rb
38
38
  - lib/bundler/spinel/history.rb
39
39
  - lib/bundler/spinel/ledger.rb
40
+ - lib/bundler/spinel/localizer.rb
40
41
  - lib/bundler/spinel/platform.rb
41
42
  - lib/bundler/spinel/probe.rb
42
43
  - lib/bundler/spinel/proxy.rb