beni 0.0.0 → 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 +4 -4
- data/CHANGELOG.md +54 -0
- data/README.md +141 -14
- data/lib/beni/build_config.rb +45 -0
- data/lib/beni/builder.rb +141 -0
- data/lib/beni/configuration.rb +12 -0
- data/lib/beni/dsl/context.rb +108 -0
- data/lib/beni/dsl/definition_context.rb +49 -0
- data/lib/beni/dsl/target_context.rb +35 -0
- data/lib/beni/dsl.rb +25 -0
- data/lib/beni/selected_toolchain.rb +11 -0
- data/lib/beni/target.rb +9 -0
- data/lib/beni/tasks.rb +159 -0
- data/lib/beni/toolchain_definition.rb +9 -0
- data/lib/beni/vendor/checksum.rb +68 -0
- data/lib/beni/vendor/downloader.rb +78 -0
- data/lib/beni/vendor/tarball.rb +72 -0
- data/lib/beni/vendor/toolchain.rb +107 -0
- data/lib/beni/vendor/toolchains/wasi.rake +30 -0
- data/lib/beni/vendor.rb +139 -0
- data/lib/beni/version.rb +1 -1
- data/lib/beni.rb +9 -1
- data/sig/beni/build_config.rbs +9 -0
- data/sig/beni/builder.rbs +39 -0
- data/sig/beni/configuration.rbs +10 -0
- data/sig/beni/dsl/context.rbs +39 -0
- data/sig/beni/dsl/definition_context.rbs +19 -0
- data/sig/beni/dsl/target_context.rbs +15 -0
- data/sig/beni/dsl.rbs +5 -0
- data/sig/beni/selected_toolchain.rbs +9 -0
- data/sig/beni/target.rbs +8 -0
- data/sig/beni/tasks.rbs +36 -0
- data/sig/beni/toolchain_definition.rbs +9 -0
- data/sig/beni/vendor/checksum.rbs +22 -0
- data/sig/beni/vendor/downloader.rbs +26 -0
- data/sig/beni/vendor/tarball.rbs +24 -0
- data/sig/beni/vendor/toolchain.rbs +33 -0
- data/sig/beni/vendor.rbs +20 -0
- data/sig/beni.rbs +3 -1
- data/sig/patches/open-uri.rbs +10 -0
- metadata +59 -9
- data/Rakefile +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c331de89e1d8d6996c41ba290800042b985d932ffc9c0ef0d957753144c195b0
|
|
4
|
+
data.tar.gz: 3d4e9478ee1b7d4e5fcd91f0f47b49d750e491b062580ad8be902ca68548b35b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '093f8eeb0ab8d91e94c01a74808716d875c112d9d0351b31cb98d4db27891147f6b11cca04fc655e4f50f0bca90dbca3be1c81b907b4bacbdd4419ee58050f56'
|
|
7
|
+
data.tar.gz: 384eeb4227d265d66c4eaa4a35aab0a1988ad99af1fce770e73a0faf9c32d2a3e15444d4cea9f1b579064a430e57f2a9f26015ef1cf1583a0bd96ece6fdede41
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-06-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* require Ruby >= 3.3
|
|
9
|
+
* **beni-sys:** align bindgen with the archive via libmruby.flags.mak
|
|
10
|
+
* **beni:** default the build to mruby's upstream config, not the repo's
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* add Beni::Builder driving mruby's own rake ([2be29d3](https://github.com/elct9620/beni/commit/2be29d3d1fa353804413b92203758232796fad3b))
|
|
15
|
+
* add Beni::Tasks exposing the beni:* rake namespace ([2833c23](https://github.com/elct9620/beni/commit/2833c23a7a01efea185cfcd165f4499e5ffc0cf7))
|
|
16
|
+
* add RBS type checking and Claude Code quality hooks ([707e692](https://github.com/elct9620/beni/commit/707e6920f26b21ff4a67f8a31691fc21e1d5dd81))
|
|
17
|
+
* **beni-sys:** align bindgen with the archive via libmruby.flags.mak ([8d019dc](https://github.com/elct9620/beni/commit/8d019dc40fb0c6f7cf5b4139ac27b6a3b7d1459a))
|
|
18
|
+
* **beni:** compile the full wrapper API surface in placeholder mode ([00a429d](https://github.com/elct9620/beni/commit/00a429dfe524b7daaeb72765841136e2e92ed83a))
|
|
19
|
+
* **beni:** generate self-contained build configs via rake beni:config ([51c04c2](https://github.com/elct9620/beni/commit/51c04c214a1cc5b2766b68a1e54937cf00a5a69d))
|
|
20
|
+
* **beni:** register typed Rust functions with method! and seal the panic boundary ([b1706a2](https://github.com/elct9620/beni/commit/b1706a2aa2aa9c8550846ea2e4d139720ec1aa34))
|
|
21
|
+
* **beni:** ship Ruby surfaces as Gem implementations ([37b6260](https://github.com/elct9620/beni/commit/37b6260b235b4510d145ce8dd933e2f62bf265d8))
|
|
22
|
+
* **beni:** surface mruby rejections as Err through magnus-shaped handles ([ccd2809](https://github.com/elct9620/beni/commit/ccd280974700e3c3943e541c4bf72260eaad74be))
|
|
23
|
+
* **config:** generate the build config from the staged upstream default ([5429c54](https://github.com/elct9620/beni/commit/5429c542ceb58456be8b529c1422d4efe18ded07))
|
|
24
|
+
* **dsl:** add the declarative configuration vocabulary and resolution ([a8cbe48](https://github.com/elct9620/beni/commit/a8cbe483e7ea877ae59277056ffae6f282c5fe54))
|
|
25
|
+
* port kobako's rake chain as the compile-verification harness ([a0e69f2](https://github.com/elct9620/beni/commit/a0e69f20609fb293b317f4ee206169dd3f543f37))
|
|
26
|
+
* port the vendor pipeline into Beni::Vendor ([4387e6b](https://github.com/elct9620/beni/commit/4387e6be8432975ebf69cdcd2c35bd02d38060d4))
|
|
27
|
+
* ship the mruby build configs with the gem ([88d79d4](https://github.com/elct9620/beni/commit/88d79d4bbc4f28c12a767ae078d8ea6775e0fdc3))
|
|
28
|
+
* **sys:** make archive discovery env-driven with no vendor fallback ([389d608](https://github.com/elct9620/beni/commit/389d6088644559ac3f53505b4fe9bfcc5f25b298))
|
|
29
|
+
* **tasks:** switch Beni::Tasks to the declarative DSL ([7eaf07f](https://github.com/elct9620/beni/commit/7eaf07f8f2b6b57f6cb93aa37edebe680ee0b7b9))
|
|
30
|
+
* **test:** add default_host consumer-scenario harness ([9d7d6d3](https://github.com/elct9620/beni/commit/9d7d6d3b339bcda266643ce3535efeb9b720cf0d))
|
|
31
|
+
* **test:** exercise the generate-config chain as a consumer scenario ([2df1490](https://github.com/elct9620/beni/commit/2df1490ee19b161d9d8fb171d69669c7016c8fdd))
|
|
32
|
+
* **vendor:** stage the wasi toolchain file into the mruby tree ([e081b69](https://github.com/elct9620/beni/commit/e081b694dfbe45b35ac58e45eb22fd5fe2997447))
|
|
33
|
+
* **vendor:** vendor built-in version-checksum pairs and inject selection into toolchains ([b3edf10](https://github.com/elct9620/beni/commit/b3edf103351814f8e67d7d2a3f4fbf5fff6ec223))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Bug Fixes
|
|
37
|
+
|
|
38
|
+
* **beni:** gate Mrb's repr(transparent) on mruby_linked, not wasm32 ([7d8c949](https://github.com/elct9620/beni/commit/7d8c9499e1365e6bb2c6551d1e918738163db1cb))
|
|
39
|
+
* **beni:** prefix the remaining runtime errors for CI log grepping ([ffd799a](https://github.com/elct9620/beni/commit/ffd799a67bcdc1ca9dbb92d106c14f333afe14b8))
|
|
40
|
+
* **beni:** treat init-failed mruby states and missing configs as errors ([e4c0bf5](https://github.com/elct9620/beni/commit/e4c0bf5c406717c7129901a11d5fb2791b33bf3e))
|
|
41
|
+
* **beni:** type mrb_get_args count out-params as mrb_int, not c_int ([b21ee22](https://github.com/elct9620/beni/commit/b21ee225577d6b2bba8176836e426e7e25611b93))
|
|
42
|
+
* **gemspec:** give source_code_uri its own URL ([3d3da60](https://github.com/elct9620/beni/commit/3d3da6092d0c7f222558a822f3b7c5c1660d0ff0))
|
|
43
|
+
* **gemspec:** ship only the consumer-facing files and link the changelog ([176f18a](https://github.com/elct9620/beni/commit/176f18a77fb01f18502a0e0b6c1b237470e0d0fc))
|
|
44
|
+
* **release:** pin the first release to 0.1.0 and drop the dead lock updaters ([5bd6106](https://github.com/elct9620/beni/commit/5bd61065d61f3e00cfbd6918d7dd803f2e9e11b4))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Miscellaneous Chores
|
|
48
|
+
|
|
49
|
+
* require Ruby >= 3.3 ([de636e8](https://github.com/elct9620/beni/commit/de636e87163474f84f19e7163f22c51a1ec9dc22))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Code Refactoring
|
|
53
|
+
|
|
54
|
+
* **beni:** default the build to mruby's upstream config, not the repo's ([0cafae1](https://github.com/elct9620/beni/commit/0cafae18e17350c0cbbd5d0048c1131e450cd573))
|
data/README.md
CHANGED
|
@@ -1,20 +1,131 @@
|
|
|
1
1
|
# Beni
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
> the package names while the build chain and binding crates are extracted
|
|
9
|
-
> from kobako; no usable API ships yet.
|
|
3
|
+
beni gives Rust developers a magnus-like experience for mruby: a Ruby gem
|
|
4
|
+
manages the mruby build chain, and Rust crates expose a safe, typed API over
|
|
5
|
+
the resulting `libmruby.a`. Extracted from the
|
|
6
|
+
[kobako](https://github.com/elct9620/kobako) project; APIs follow 0.x semver
|
|
7
|
+
semantics and may still evolve between minor versions.
|
|
10
8
|
|
|
11
9
|
## Packages
|
|
12
10
|
|
|
11
|
+
All three packages release in lockstep under a single version.
|
|
12
|
+
|
|
13
13
|
| Package | Registry | Role |
|
|
14
14
|
|---|---|---|
|
|
15
|
-
| `beni` gem | rubygems.org |
|
|
15
|
+
| `beni` gem | rubygems.org | Rake tasks + DSL config that download mruby and build `libmruby.a` |
|
|
16
16
|
| `beni-sys` crate | crates.io | bindgen FFI surface over the mruby C API |
|
|
17
|
-
| `beni` crate | crates.io | typed
|
|
17
|
+
| `beni` crate | crates.io | safe typed wrapper over `beni-sys`, aligned with magnus idioms |
|
|
18
|
+
|
|
19
|
+
## Getting started
|
|
20
|
+
|
|
21
|
+
### Build `libmruby.a` with the gem
|
|
22
|
+
|
|
23
|
+
Add `beni` to your Gemfile and install the task library in your Rakefile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
require "beni/tasks"
|
|
27
|
+
|
|
28
|
+
Beni::Tasks.new
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
rake beni:build
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This downloads the pinned mruby release, builds it with mruby's untouched
|
|
36
|
+
upstream default config, and stages `vendor/mruby/build/host/lib/` with
|
|
37
|
+
`libmruby.a` and its `libmruby.flags.mak` compile-flags sidecar — everything
|
|
38
|
+
the crates need.
|
|
39
|
+
|
|
40
|
+
To tune the build, declare a config path and generate the seed:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
Beni::Tasks.new do
|
|
44
|
+
build_config "build_config/mruby.rb"
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`rake beni:config` writes a self-contained copy of the upstream default
|
|
49
|
+
config to that path. The file is yours to edit — add targets, gems, or
|
|
50
|
+
defines; beni never rewrites it.
|
|
51
|
+
|
|
52
|
+
### Embed mruby from Rust
|
|
53
|
+
|
|
54
|
+
Add the `beni` crate to your Cargo.toml, then point archive discovery at
|
|
55
|
+
the vendor tree the gem staged:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
BENI_VENDOR_DIR=$PWD/vendor cargo build
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A crate ships its Ruby surface as a `Gem` and installs it during
|
|
62
|
+
interpreter setup:
|
|
63
|
+
|
|
64
|
+
```rust
|
|
65
|
+
use beni::{method, Error, Gem, Module, Mrb, Value};
|
|
66
|
+
|
|
67
|
+
fn answer(_mrb: &Mrb, _self: Value) -> i32 {
|
|
68
|
+
42
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
struct WidgetGem;
|
|
72
|
+
|
|
73
|
+
impl Gem for WidgetGem {
|
|
74
|
+
fn init(mrb: &Mrb) -> Result<(), Error> {
|
|
75
|
+
let widget = mrb.define_class(c"Widget", mrb.object_class())?;
|
|
76
|
+
widget.define_method(mrb, c"answer", method!(answer, 0))?;
|
|
77
|
+
Ok(())
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn main() {
|
|
82
|
+
let mrb = Mrb::open().expect("mruby interpreter");
|
|
83
|
+
mrb.init_gem::<WidgetGem>().expect("Widget surface");
|
|
84
|
+
// Widget#answer now returns 42 to any Ruby code the interpreter runs.
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
With no archive discovery variable set, a host build compiles in
|
|
89
|
+
placeholder mode: `cargo check` passes, no FFI surface is exported, and
|
|
90
|
+
`Mrb::open` returns an error — so `beni` is safe to take as a transitive
|
|
91
|
+
dependency. Any C API the typed wrapper does not cover stays reachable
|
|
92
|
+
through the unsafe `beni::sys` escape hatch.
|
|
93
|
+
|
|
94
|
+
### Cross-compile for wasm32-wasip1
|
|
95
|
+
|
|
96
|
+
Declare a `wasi` target referencing the `wasi-sdk` toolchain:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
Beni::Tasks.new do
|
|
100
|
+
build_config "build_config/mruby.rb"
|
|
101
|
+
|
|
102
|
+
target :host
|
|
103
|
+
target :wasi do
|
|
104
|
+
toolchain "wasi-sdk"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
and append the cross build to the generated config:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
MRuby::CrossBuild.new("wasi") do |conf|
|
|
113
|
+
conf.toolchain :wasi
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`conf.toolchain :wasi` resolves to the wasi toolchain file
|
|
118
|
+
`beni:vendor:setup` stages into the mruby tree whenever `wasi-sdk` is
|
|
119
|
+
selected — the cross-compile settings ship with beni and update with it.
|
|
120
|
+
After `rake beni:build`, name the staged archive and the wasi-sdk root
|
|
121
|
+
explicitly for the cargo side (a cross-compiled cargo target never reads
|
|
122
|
+
the vendor tree on its own):
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
MRUBY_LIB_DIR=$PWD/vendor/mruby/build/wasi/lib \
|
|
126
|
+
WASI_SDK_PATH=$PWD/vendor/wasi-sdk \
|
|
127
|
+
cargo build --target wasm32-wasip1
|
|
128
|
+
```
|
|
18
129
|
|
|
19
130
|
## Toolchain
|
|
20
131
|
|
|
@@ -28,12 +139,28 @@ are unaffected by the pairing.
|
|
|
28
139
|
|
|
29
140
|
## Development
|
|
30
141
|
|
|
31
|
-
After checking out the repo, run `bin/setup` to install dependencies
|
|
32
|
-
|
|
33
|
-
|
|
142
|
+
After checking out the repo, run `bin/setup` to install dependencies, then
|
|
143
|
+
`bundle exec rake` for the default gate (tests + RuboCop + Steep). The repo
|
|
144
|
+
dogfoods its own gem: the Rakefile wires `Beni::Tasks` with the validation
|
|
145
|
+
config `build_config/mruby.rb` (host + wasi targets), and a repo-local rake
|
|
146
|
+
chain verifies the crates compile against a real `libmruby.a` on both the
|
|
147
|
+
host target and wasm32-wasip1:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
bundle exec rake rust:verify # beni:build + check/test (host) + check (wasm32)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Behavior contracts live in `SPEC.md` — the source of truth the
|
|
154
|
+
implementation follows.
|
|
155
|
+
|
|
156
|
+
## Releasing
|
|
34
157
|
|
|
35
|
-
|
|
36
|
-
|
|
158
|
+
Releases are cut by release-please: merging the release PR tags the
|
|
159
|
+
version and publishes the gem and both crates in lockstep through OIDC
|
|
160
|
+
trusted publishing. One-time cleanup: after 0.1.0 ships, remove the
|
|
161
|
+
`release-as` line (and the then-stale `last-release-sha`) from
|
|
162
|
+
`release-please-config.json` — left in place it pins every subsequent
|
|
163
|
+
release to 0.1.0.
|
|
37
164
|
|
|
38
165
|
## Contributing
|
|
39
166
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Beni
|
|
6
|
+
# Generates the consumer's editable mruby build config: a verbatim
|
|
7
|
+
# copy of the staged mruby source's own +build_config/default.rb+ —
|
|
8
|
+
# the configured version's upstream default. Exposed as
|
|
9
|
+
# +rake beni:config+ by +Beni::Tasks+; the generated file belongs to
|
|
10
|
+
# the consuming project and seeds further customization.
|
|
11
|
+
module BuildConfig
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# Copy the staged source's default config to +dest+, creating
|
|
15
|
+
# missing parent directories. Requires +version+'s mruby source
|
|
16
|
+
# staged under +mruby_dir+ and refuses to clobber an existing
|
|
17
|
+
# (likely hand-tuned) config. Returns +dest+.
|
|
18
|
+
def generate(dest, mruby_dir:, version:)
|
|
19
|
+
raise Error, "[beni] #{dest} already exists — delete it first to regenerate" if File.exist?(dest)
|
|
20
|
+
|
|
21
|
+
source = staged_default_config(mruby_dir, version)
|
|
22
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
23
|
+
FileUtils.cp(source, dest)
|
|
24
|
+
dest
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# The staged source's upstream default config path, verified to be
|
|
28
|
+
# +version+'s: the +.beni-version+ marker +Vendor::Tarball#prepare+
|
|
29
|
+
# stamps must match, so a stale tree never seeds a config for the
|
|
30
|
+
# wrong release.
|
|
31
|
+
def staged_default_config(mruby_dir, version)
|
|
32
|
+
source = File.join(mruby_dir, "build_config", "default.rb")
|
|
33
|
+
return source if staged_version(mruby_dir) == version && File.exist?(source)
|
|
34
|
+
|
|
35
|
+
raise Error,
|
|
36
|
+
"[beni] mruby #{version}'s source is not staged at #{mruby_dir} — " \
|
|
37
|
+
"run `rake beni:vendor:setup` first"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def staged_version(mruby_dir)
|
|
41
|
+
marker = File.join(mruby_dir, Vendor::Tarball::VERSION_MARKER)
|
|
42
|
+
File.read(marker).strip if File.exist?(marker)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/beni/builder.rb
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
|
|
6
|
+
module Beni
|
|
7
|
+
# Drives the vendored mruby tree's build to produce the per-target
|
|
8
|
+
# +libmruby.a+ archives, by running mruby's own +Rakefile+ — the
|
|
9
|
+
# documented build entry point (doc/guides/compile.md). With no
|
|
10
|
+
# +build_config+, mruby falls back to its own
|
|
11
|
+
# +build_config/default.rb+; an explicit config is wired in via the
|
|
12
|
+
# +MRUBY_CONFIG+ env var, which mruby resolves as an absolute path.
|
|
13
|
+
class Builder
|
|
14
|
+
# mruby's anonymous +MRuby::Build.new+ names its target "host"
|
|
15
|
+
# (lib/mruby/build.rb), so the upstream default config produces
|
|
16
|
+
# +build/host/lib/libmruby.a+. A custom build config with
|
|
17
|
+
# different target names supplies its own list via +Beni::Tasks+.
|
|
18
|
+
DEFAULT_TARGETS = %w[host].freeze
|
|
19
|
+
|
|
20
|
+
attr_reader :vendor_dir, :build_config, :targets
|
|
21
|
+
|
|
22
|
+
def initialize(vendor_dir:, build_config: nil, targets: DEFAULT_TARGETS)
|
|
23
|
+
@vendor_dir = vendor_dir
|
|
24
|
+
@build_config = build_config
|
|
25
|
+
@targets = targets
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def mruby_dir
|
|
29
|
+
File.join(vendor_dir, "mruby")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def libmruby_path(target)
|
|
33
|
+
File.join(mruby_dir, "build", target, "lib", "libmruby.a")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def libmruby_paths
|
|
37
|
+
targets.map { |target| libmruby_path(target) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# True when every target's artifacts — +libmruby.a+ plus the
|
|
41
|
+
# +libmruby.flags.mak+ sidecar +beni-sys+ parses for ABI alignment
|
|
42
|
+
# — are already present, letting callers skip the build without
|
|
43
|
+
# spawning a subprocess. An archive without its sidecar (e.g. a
|
|
44
|
+
# tree built before flags.mak joined the contract) triggers a
|
|
45
|
+
# rebuild, which is incremental and only emits the missing file.
|
|
46
|
+
def built?
|
|
47
|
+
artifact_paths.all? { |path| File.exist?(path) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Idempotent build entry point for +rake beni:build+: skip with a
|
|
51
|
+
# note when every artifact is already present, otherwise build and
|
|
52
|
+
# report readiness. A declared config that does not exist aborts
|
|
53
|
+
# before the skip check — stale artifacts must not mask it.
|
|
54
|
+
def ensure_built
|
|
55
|
+
check_build_config!
|
|
56
|
+
if built?
|
|
57
|
+
puts "[beni] libmruby.a already present for #{targets.join(" + ")} — skipping"
|
|
58
|
+
return
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
build
|
|
62
|
+
puts "[beni] libmruby.a ready for #{targets.join(" + ")}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Run mruby's rake and raise unless every target's +libmruby.a+
|
|
66
|
+
# exists afterwards. Alongside the default task, each target's
|
|
67
|
+
# +libmruby.flags.mak+ file task is requested explicitly — mruby's
|
|
68
|
+
# embedder interface recording the exact compile flags, which
|
|
69
|
+
# +beni-sys+'s build script parses to keep bindgen's view of the
|
|
70
|
+
# ABI aligned with the archive. (The file task is defined per
|
|
71
|
+
# target but not part of mruby's default products.) The underlying
|
|
72
|
+
# build is make-style incremental, so re-running on a partially
|
|
73
|
+
# built tree only compiles what is missing.
|
|
74
|
+
def build
|
|
75
|
+
check_build_config!
|
|
76
|
+
cmd = [RbConfig.ruby, "-S", "rake", "default", *flags_mak_paths]
|
|
77
|
+
puts "[beni] cd #{mruby_dir} && #{env.map { |k, v| "#{k}=#{v}" }.join(" ")} #{cmd.join(" ")}"
|
|
78
|
+
run_mruby_rake(env, cmd)
|
|
79
|
+
verify_artifacts!
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Remove each target's build tree (keeps the vendored mruby source).
|
|
83
|
+
def clean
|
|
84
|
+
targets.each do |target|
|
|
85
|
+
dir = File.join(mruby_dir, "build", target)
|
|
86
|
+
FileUtils.rm_rf(dir)
|
|
87
|
+
puts "[beni] removed #{dir}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# Every artifact the build must leave behind: the per-target
|
|
94
|
+
# archive and its flags.mak sidecar.
|
|
95
|
+
def artifact_paths
|
|
96
|
+
libmruby_paths + flags_mak_paths
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Spawn mruby's rake with the parent environment plus the +env+
|
|
100
|
+
# overlay. Extracted as a seam so tests can fake the subprocess
|
|
101
|
+
# while observing the full env + cmd contract.
|
|
102
|
+
def run_mruby_rake(env, cmd)
|
|
103
|
+
system(env, *cmd, chdir: mruby_dir, exception: true)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Environment for the mruby build subprocess. +BENI_VENDOR_DIR+
|
|
107
|
+
# lets build configs resolve the vendor tree without knowing where
|
|
108
|
+
# the consuming project lives on disk. +MRUBY_CONFIG+ is only set
|
|
109
|
+
# for an explicit config — absent, mruby falls back to its own
|
|
110
|
+
# +build_config/default.rb+ (lib/mruby/build.rb#mruby_config_path).
|
|
111
|
+
def env
|
|
112
|
+
env = { "BENI_VENDOR_DIR" => vendor_dir }
|
|
113
|
+
env["MRUBY_CONFIG"] = build_config if build_config
|
|
114
|
+
env
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Per-target +libmruby.flags.mak+ file-task paths, matching the
|
|
118
|
+
# task names mruby defines in tasks/libmruby.rake (absolute,
|
|
119
|
+
# anchored on the default +build/<target>+ layout).
|
|
120
|
+
def flags_mak_paths
|
|
121
|
+
targets.map { |target| File.join(mruby_dir, "build", target, "lib", "libmruby.flags.mak") }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The declared config belongs to the consumer; a path that does
|
|
125
|
+
# not exist is a configuration error named before anything spawns.
|
|
126
|
+
def check_build_config!
|
|
127
|
+
return if build_config.nil? || File.exist?(build_config)
|
|
128
|
+
|
|
129
|
+
raise Error, "[beni] build config #{build_config} does not exist"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Report every missing artifact at once, so a multi-target build
|
|
133
|
+
# failure shows the whole gap instead of one path per run.
|
|
134
|
+
def verify_artifacts!
|
|
135
|
+
missing = artifact_paths.reject { |path| File.exist?(path) }
|
|
136
|
+
return if missing.empty?
|
|
137
|
+
|
|
138
|
+
raise Error, "[beni] build completed but artifacts are missing:\n #{missing.join("\n ")}"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Beni
|
|
4
|
+
# Resolution output of the Tasks DSL — immutable, defaults applied.
|
|
5
|
+
# The only input the task-definition phase reads: +vendor_dir+ fully
|
|
6
|
+
# resolved (declaration > +BENI_VENDOR_DIR+ > +vendor/+), +build_config+
|
|
7
|
+
# as an absolute path or +nil+ for mruby's untouched upstream default,
|
|
8
|
+
# +targets+ as the declared set (or +["host"]+), and +toolchains+ as
|
|
9
|
+
# the reference-driven selection.
|
|
10
|
+
class Configuration < Data.define(:vendor_dir, :build_config, :targets, :toolchains)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Beni
|
|
4
|
+
module DSL
|
|
5
|
+
# Top-level vocabulary of the +Beni::Tasks.new+ block. Collects the
|
|
6
|
+
# scalar settings, target declarations, and toolchain definitions —
|
|
7
|
+
# validating each declaration eagerly — and resolves them into the
|
|
8
|
+
# +Configuration+ the task-definition phase consumes.
|
|
9
|
+
class Context
|
|
10
|
+
def initialize
|
|
11
|
+
@settings = {}
|
|
12
|
+
@targets = {}
|
|
13
|
+
@definitions = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def version(value)
|
|
17
|
+
declare_scalar(:version, value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build_config(path)
|
|
21
|
+
declare_scalar(:build_config, path)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def vendor_dir(path)
|
|
25
|
+
declare_scalar(:vendor_dir, path)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# A +target <name>+ declaration; the optional block holds the
|
|
29
|
+
# target's toolchain references.
|
|
30
|
+
def target(name, &block)
|
|
31
|
+
key = name.to_s
|
|
32
|
+
raise Error, "duplicate `target` declaration #{key.inspect}" if @targets.key?(key)
|
|
33
|
+
|
|
34
|
+
# @type var references: Array[String]
|
|
35
|
+
references = block ? TargetContext.collect(&block) : []
|
|
36
|
+
@targets[key] = Target.new(name: key, references: references)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# A top-level +toolchain <name>+ — always a definition, so the
|
|
40
|
+
# block is part of the grammar and +mruby+ is never definable.
|
|
41
|
+
def toolchain(name, &block)
|
|
42
|
+
raise Error, "top-level `toolchain #{name.inspect}` must carry a definition block" unless block
|
|
43
|
+
if name == "mruby"
|
|
44
|
+
raise Error, "a toolchain definition never names \"mruby\" — select it with the `version` setting"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
DSL.assert_known_toolchain!(name)
|
|
48
|
+
raise Error, "duplicate toolchain definition #{name.inspect}" if @definitions.key?(name)
|
|
49
|
+
|
|
50
|
+
@definitions[name] = DefinitionContext.collect(name, &block)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Resolve the collected declarations (SPEC.md Behaviors: selection
|
|
54
|
+
# is reference-driven; defaults fall to the built-in pairs).
|
|
55
|
+
def configuration
|
|
56
|
+
Configuration.new(
|
|
57
|
+
vendor_dir: resolved_vendor_dir,
|
|
58
|
+
build_config: resolved_build_config,
|
|
59
|
+
targets: resolved_targets,
|
|
60
|
+
toolchains: selected_names.map { |name| selected_toolchain(name) }
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def declare_scalar(key, value)
|
|
67
|
+
raise Error, "duplicate `#{key}` declaration" if @settings.key?(key)
|
|
68
|
+
|
|
69
|
+
@settings[key] = value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def resolved_vendor_dir
|
|
73
|
+
File.expand_path(@settings[:vendor_dir] || ENV.fetch("BENI_VENDOR_DIR", nil) || "vendor")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def resolved_build_config
|
|
77
|
+
path = @settings[:build_config]
|
|
78
|
+
path && File.expand_path(path)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def resolved_targets
|
|
82
|
+
return Builder::DEFAULT_TARGETS.dup if @targets.empty?
|
|
83
|
+
|
|
84
|
+
@targets.keys
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# References plus their transitive dependencies; +mruby+ is always
|
|
88
|
+
# selected and leads the set so it stages first.
|
|
89
|
+
def selected_names
|
|
90
|
+
references = @targets.values.flat_map(&:references)
|
|
91
|
+
dependencies = references.flat_map { |name| Vendor::DEPENDENCIES.fetch(name, []) }
|
|
92
|
+
(%w[mruby] + references + dependencies).uniq
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def selected_toolchain(name)
|
|
96
|
+
definition = @definitions[name]
|
|
97
|
+
return SelectedToolchain.new(name: name, version: definition.version, sha256: definition.sha256) if definition
|
|
98
|
+
|
|
99
|
+
version = name == "mruby" ? mruby_version : Vendor::BUILT_IN_PAIRS.fetch(name).fetch(:version)
|
|
100
|
+
SelectedToolchain.new(name: name, version: version, sha256: Vendor.built_in_sha256(name, version))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def mruby_version
|
|
104
|
+
@settings[:version] || Vendor::BUILT_IN_PAIRS.fetch("mruby").fetch(:version)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Beni
|
|
4
|
+
module DSL
|
|
5
|
+
# Vocabulary inside a top-level +toolchain <name> do … end+ block —
|
|
6
|
+
# the +(version, sha256)+ pair and nothing else. Both fields are
|
|
7
|
+
# required exactly once; the check runs when the block returns.
|
|
8
|
+
class DefinitionContext
|
|
9
|
+
# Run +block+ on a fresh context and return the collected
|
|
10
|
+
# ToolchainDefinition.
|
|
11
|
+
def self.collect(name, &)
|
|
12
|
+
context = new(name)
|
|
13
|
+
context.instance_exec(&)
|
|
14
|
+
context.to_definition
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(name)
|
|
18
|
+
@name = name
|
|
19
|
+
@version = nil
|
|
20
|
+
@sha256 = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def version(value)
|
|
24
|
+
raise Error, "duplicate `version` in toolchain #{@name.inspect} definition" if @version
|
|
25
|
+
|
|
26
|
+
@version = value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def sha256(value)
|
|
30
|
+
raise Error, "duplicate `sha256` in toolchain #{@name.inspect} definition" if @sha256
|
|
31
|
+
|
|
32
|
+
@sha256 = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The collected definition; raises when the block left +version+
|
|
36
|
+
# or +sha256+ undeclared.
|
|
37
|
+
def to_definition
|
|
38
|
+
version = @version
|
|
39
|
+
sha256 = @sha256
|
|
40
|
+
if version.nil? || sha256.nil?
|
|
41
|
+
missing = [("version" if version.nil?), ("sha256" if sha256.nil?)].compact
|
|
42
|
+
raise Error, "toolchain #{@name.inspect} definition missing #{missing.join(" and ")}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
ToolchainDefinition.new(name: @name, version: version, sha256: sha256)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Beni
|
|
4
|
+
module DSL
|
|
5
|
+
# Vocabulary inside a +target <name> do … end+ block — block-less
|
|
6
|
+
# toolchain references only. References are set-semantic: repeats
|
|
7
|
+
# collapse, and referencing +mruby+ is legal redundancy.
|
|
8
|
+
class TargetContext
|
|
9
|
+
# Run +block+ on a fresh context and return the collected
|
|
10
|
+
# reference names.
|
|
11
|
+
def self.collect(&)
|
|
12
|
+
context = new
|
|
13
|
+
context.instance_exec(&)
|
|
14
|
+
context.references
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :references
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@references = []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def toolchain(name, &block)
|
|
24
|
+
if block
|
|
25
|
+
raise Error,
|
|
26
|
+
"`toolchain #{name.inspect}` inside a target block must not carry a block — " \
|
|
27
|
+
"definitions live at the top level"
|
|
28
|
+
end
|
|
29
|
+
DSL.assert_known_toolchain!(name)
|
|
30
|
+
|
|
31
|
+
@references << name unless @references.include?(name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/beni/dsl.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Beni
|
|
4
|
+
# Declarative configuration vocabulary for +Beni::Tasks+. Three
|
|
5
|
+
# contexts each expose exactly the declarations legal at their
|
|
6
|
+
# position — top level (+Context+), inside a target block
|
|
7
|
+
# (+TargetContext+), and inside a toolchain definition block
|
|
8
|
+
# (+DefinitionContext+) — so a malformed declaration fails in the
|
|
9
|
+
# method itself, at definition time.
|
|
10
|
+
module DSL
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# A toolchain name outside the Vendor registry fails at the
|
|
14
|
+
# declaration that names it, never mid-build.
|
|
15
|
+
def assert_known_toolchain!(name)
|
|
16
|
+
return if Vendor::TOOLCHAIN_FACTORIES.key?(name)
|
|
17
|
+
|
|
18
|
+
raise Error, "unknown toolchain #{name.inspect} (known: #{Vendor::TOOLCHAIN_FACTORIES.keys.join(", ")})"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
require_relative "dsl/context"
|
|
24
|
+
require_relative "dsl/target_context"
|
|
25
|
+
require_relative "dsl/definition_context"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Beni
|
|
4
|
+
# One selected toolchain with its resolved +(version, sha256)+ pair.
|
|
5
|
+
# +sha256+ is concrete at resolution time — a definition's declared
|
|
6
|
+
# value or the built-in checksum +Beni::Vendor+ resolves. +nil+ has
|
|
7
|
+
# exactly one reachable meaning: mruby at a non-default version, where
|
|
8
|
+
# verification falls to the TOFU sidecar path.
|
|
9
|
+
class SelectedToolchain < Data.define(:name, :version, :sha256)
|
|
10
|
+
end
|
|
11
|
+
end
|