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 +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +2 -2
- data/lib/bundler/spinel/engine_installer.rb +17 -10
- data/lib/bundler/spinel/localizer.rb +83 -0
- data/lib/bundler/spinel/verifier.rb +10 -0
- data/lib/bundler/spinel/version.rb +1 -1
- data/lib/bundler/spinel.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12d700f606c1cf8b69ae4fe623c16293bfbc560fe09b59c8fadeb4529a8a02f6
|
|
4
|
+
data.tar.gz: e08a61f93c03930e4c85b0e1effbaf3dfb2e05f3bd1d1979459fe4e667de4f4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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"
|
|
41
|
-
gem "
|
|
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 "
|
|
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.
|
|
204
|
-
#
|
|
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
|
-
#
|
|
223
|
-
#
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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"
|
data/lib/bundler/spinel.rb
CHANGED
|
@@ -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.
|
|
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
|