portable_mruby 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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +195 -0
  3. data/exe/portable-mruby +6 -0
  4. data/lib/portable_mruby/binary_manager.rb +225 -0
  5. data/lib/portable_mruby/builder.rb +97 -0
  6. data/lib/portable_mruby/bytecode_compiler.rb +19 -0
  7. data/lib/portable_mruby/c_generator.rb +94 -0
  8. data/lib/portable_mruby/cli.rb +136 -0
  9. data/lib/portable_mruby/version.rb +6 -0
  10. data/lib/portable_mruby.rb +15 -0
  11. data/vendor/mruby/bin/mrbc.com +0 -0
  12. data/vendor/mruby/include/mrbconf.h +230 -0
  13. data/vendor/mruby/include/mruby/array.h +303 -0
  14. data/vendor/mruby/include/mruby/boxing_nan.h +169 -0
  15. data/vendor/mruby/include/mruby/boxing_no.h +59 -0
  16. data/vendor/mruby/include/mruby/boxing_word.h +251 -0
  17. data/vendor/mruby/include/mruby/class.h +104 -0
  18. data/vendor/mruby/include/mruby/common.h +118 -0
  19. data/vendor/mruby/include/mruby/compile.h +185 -0
  20. data/vendor/mruby/include/mruby/data.h +76 -0
  21. data/vendor/mruby/include/mruby/debug.h +75 -0
  22. data/vendor/mruby/include/mruby/dump.h +159 -0
  23. data/vendor/mruby/include/mruby/endian.h +44 -0
  24. data/vendor/mruby/include/mruby/error.h +132 -0
  25. data/vendor/mruby/include/mruby/gc.h +72 -0
  26. data/vendor/mruby/include/mruby/gems/mruby-dir/include/dir_hal.h +79 -0
  27. data/vendor/mruby/include/mruby/gems/mruby-io/include/io_hal.h +451 -0
  28. data/vendor/mruby/include/mruby/gems/mruby-io/include/mruby/ext/io.h +76 -0
  29. data/vendor/mruby/include/mruby/gems/mruby-socket/include/socket_hal.h +83 -0
  30. data/vendor/mruby/include/mruby/gems/mruby-time/include/mruby/time.h +27 -0
  31. data/vendor/mruby/include/mruby/hash.h +234 -0
  32. data/vendor/mruby/include/mruby/internal.h +274 -0
  33. data/vendor/mruby/include/mruby/irep.h +142 -0
  34. data/vendor/mruby/include/mruby/istruct.h +50 -0
  35. data/vendor/mruby/include/mruby/khash.h +455 -0
  36. data/vendor/mruby/include/mruby/mempool.h +19 -0
  37. data/vendor/mruby/include/mruby/numeric.h +174 -0
  38. data/vendor/mruby/include/mruby/object.h +45 -0
  39. data/vendor/mruby/include/mruby/opcode.h +69 -0
  40. data/vendor/mruby/include/mruby/ops.h +120 -0
  41. data/vendor/mruby/include/mruby/presym/disable.h +72 -0
  42. data/vendor/mruby/include/mruby/presym/enable.h +39 -0
  43. data/vendor/mruby/include/mruby/presym/id.h +1423 -0
  44. data/vendor/mruby/include/mruby/presym/scanning.h +81 -0
  45. data/vendor/mruby/include/mruby/presym/table.h +2847 -0
  46. data/vendor/mruby/include/mruby/presym.h +41 -0
  47. data/vendor/mruby/include/mruby/proc.h +186 -0
  48. data/vendor/mruby/include/mruby/range.h +77 -0
  49. data/vendor/mruby/include/mruby/re.h +16 -0
  50. data/vendor/mruby/include/mruby/string.h +428 -0
  51. data/vendor/mruby/include/mruby/throw.h +57 -0
  52. data/vendor/mruby/include/mruby/value.h +471 -0
  53. data/vendor/mruby/include/mruby/variable.h +108 -0
  54. data/vendor/mruby/include/mruby/version.h +143 -0
  55. data/vendor/mruby/include/mruby.h +1614 -0
  56. data/vendor/mruby/lib/libmruby.a +0 -0
  57. metadata +102 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 19d331ca17ea2863151576372cf313359d5387517051f939d63493725c67a8ab
4
+ data.tar.gz: fbbaf32d29d7d1029fbb7c8c6dc25f3f62b24d737228b6630b658a3bc89cae1a
5
+ SHA512:
6
+ metadata.gz: 04a0bc4997bbd9a9cbd4c2bf6b51388f85cac64e0aa50934a7c4e7c0e95e94b04d9cce372e1edc264436d1f08f4256f15b239a9828376c9f3a48e3d350031452
7
+ data.tar.gz: b87405af2e6281d25a5f6ef51ea007f07fae6d37ea45f8361edbcf8f87544aa81dc633a81be30d3a714d0a12bb9061a412031e4c16f2928195f1335d528c7546
data/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # portable_mruby
2
+
3
+ Build truly portable Ruby executables that run on Linux, macOS, Windows, FreeBSD, OpenBSD, and NetBSD from a single binary.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Install
9
+ gem install portable_mruby
10
+
11
+ # Create a Ruby program
12
+ echo 'puts "Hello from #{RUBY_ENGINE}!"' > hello.rb
13
+
14
+ # Build portable executable
15
+ portable-mruby build -e hello.rb -o hello.com
16
+
17
+ # Run it (works on Linux, macOS, Windows, FreeBSD, OpenBSD, NetBSD)
18
+ ./hello.com
19
+ ```
20
+
21
+ ## How it Works
22
+
23
+ portable_mruby uses [mruby](https://mruby.org/) (embedded Ruby) compiled with [Cosmopolitan Libc](https://github.com/jart/cosmopolitan) to create "Actually Portable Executables" (APE). These are single binaries that run natively on multiple operating systems and CPU architectures without any dependencies.
24
+
25
+ ## Supported Platforms
26
+
27
+ A single binary runs on:
28
+ - Linux (x86_64, ARM64)
29
+ - macOS (x86_64, ARM64)
30
+ - Windows (x86_64)
31
+ - FreeBSD (x86_64)
32
+ - OpenBSD (x86_64)
33
+ - NetBSD (x86_64)
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ gem install portable_mruby
39
+ ```
40
+
41
+ On first build, the Cosmopolitan toolchain (~60MB) will be automatically downloaded to `~/.portable-mruby/cosmocc/`.
42
+
43
+ ## Usage
44
+
45
+ ### Basic Usage
46
+
47
+ ```bash
48
+ # Build all .rb files in current directory
49
+ portable-mruby build -o myapp.com
50
+
51
+ # Build all .rb files in a directory
52
+ portable-mruby build -d src/ -o myapp.com
53
+
54
+ # Run on any supported platform
55
+ ./myapp.com
56
+ ```
57
+
58
+ ### With Entry Point
59
+
60
+ ```bash
61
+ # Specify an entry file (runs last, after all other files)
62
+ portable-mruby build -e main.rb -d src/ -o myapp.com
63
+ ```
64
+
65
+ All `.rb` files are compiled and executed in sorted order. If `--entry` is specified, that file runs last.
66
+
67
+ ### Options
68
+
69
+ ```
70
+ Usage: portable-mruby build [options]
71
+
72
+ Options:
73
+ -e, --entry FILE Entry point Ruby file (runs last)
74
+ -d, --dir DIR Source directory (default: .)
75
+ -o, --output FILE Output binary name (default: app.com)
76
+ --mruby-source DIR Build mruby from custom source directory
77
+ -v, --verbose Verbose output
78
+ -h, --help Show help
79
+
80
+ Environment Variables:
81
+ COSMO_ROOT Path to cosmocc toolchain (auto-downloaded if not set)
82
+ ```
83
+
84
+ ### Using Custom mruby
85
+
86
+ If you need custom mruby gems or configuration, you can build from source:
87
+
88
+ ```bash
89
+ # Clone mruby with your customizations
90
+ git clone https://github.com/mruby/mruby.git ~/mruby
91
+
92
+ # Build using your custom mruby
93
+ portable-mruby build -e main.rb --mruby-source ~/mruby -o myapp.com
94
+ ```
95
+
96
+ ## Example
97
+
98
+ Create a simple Ruby program:
99
+
100
+ ```ruby
101
+ # main.rb
102
+ name = ARGV[0] || 'World'
103
+ puts "Hello, #{name}!"
104
+ puts "Running on: #{RUBY_ENGINE} #{RUBY_VERSION}"
105
+ puts "Time: #{Time.now}"
106
+ ```
107
+
108
+ Build it:
109
+
110
+ ```bash
111
+ portable-mruby build --entry main.rb --output hello.com
112
+ ```
113
+
114
+ Run it anywhere:
115
+
116
+ ```bash
117
+ $ ./hello.com Alice
118
+ Hello, Alice!
119
+ Running on: mruby 3.4
120
+ Time: 2025-12-09 12:00:00 +0000
121
+ ```
122
+
123
+ ## Multi-file Example
124
+
125
+ ```
126
+ myapp/
127
+ lib/
128
+ greeter.rb
129
+ main.rb
130
+ ```
131
+
132
+ ```ruby
133
+ # lib/greeter.rb
134
+ class Greeter
135
+ def initialize(name)
136
+ @name = name
137
+ end
138
+
139
+ def greet
140
+ "Hello, #{@name}!"
141
+ end
142
+ end
143
+ ```
144
+
145
+ ```ruby
146
+ # main.rb
147
+ greeter = Greeter.new(ARGV[0] || 'World')
148
+ puts greeter.greet
149
+ ```
150
+
151
+ ```bash
152
+ portable-mruby build -e main.rb -d myapp/ -o greeter.com
153
+ ./greeter.com Ruby # => Hello, Ruby!
154
+ ```
155
+
156
+ ## Build Process
157
+
158
+ 1. Ruby source files are compiled to mruby bytecode using `mrbc`
159
+ 2. Bytecode is embedded in a C source file as byte arrays
160
+ 3. A minimal C runtime initializes mruby and loads the bytecode
161
+ 4. Everything is compiled with Cosmopolitan's `cosmocc` compiler
162
+ 5. The result is an APE binary - a polyglot that runs on all platforms
163
+
164
+ ## Troubleshooting
165
+
166
+ ### "Bundled mrbc not found"
167
+
168
+ The gem installation may be corrupted. Reinstall:
169
+
170
+ ```bash
171
+ gem uninstall portable_mruby
172
+ gem install portable_mruby
173
+ ```
174
+
175
+ ### Build fails with cosmocc errors
176
+
177
+ Ensure you have enough disk space (~300MB for cosmocc). You can also manually install cosmocc:
178
+
179
+ ```bash
180
+ mkdir -p ~/.portable-mruby/cosmocc
181
+ cd ~/.portable-mruby/cosmocc
182
+ wget https://cosmo.zip/pub/cosmocc/cosmocc.zip
183
+ unzip cosmocc.zip
184
+ ```
185
+
186
+ ### Using a pre-installed cosmocc
187
+
188
+ ```bash
189
+ export COSMO_ROOT=/path/to/cosmocc
190
+ portable-mruby build -e main.rb -o app.com
191
+ ```
192
+
193
+ ## License
194
+
195
+ MIT License
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "portable_mruby"
5
+
6
+ PortableMruby::CLI.run
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "open-uri"
5
+
6
+ module PortableMruby
7
+ class BinaryManager
8
+ COSMOCC_URL = "https://cosmo.zip/pub/cosmocc/cosmocc.zip"
9
+
10
+ class << self
11
+ def ensure_available(mruby_source: nil)
12
+ ensure_cosmocc
13
+ if mruby_source
14
+ build_mruby_from_source(mruby_source)
15
+ else
16
+ ensure_bundled_mruby
17
+ end
18
+ end
19
+
20
+ def mrbc_path
21
+ @mrbc_path || bundled_mrbc_path
22
+ end
23
+
24
+ def cosmocc_path
25
+ File.join(cosmocc_dir, "bin", "cosmocc")
26
+ end
27
+
28
+ def libmruby_path
29
+ @libmruby_path || bundled_libmruby_path
30
+ end
31
+
32
+ def include_path
33
+ @include_path || bundled_include_path
34
+ end
35
+
36
+ def lib_path
37
+ @lib_path || bundled_lib_path
38
+ end
39
+
40
+ def cosmocc_dir
41
+ ENV["COSMO_ROOT"] || default_cosmocc_dir
42
+ end
43
+
44
+ def reset_paths!
45
+ @mrbc_path = nil
46
+ @libmruby_path = nil
47
+ @include_path = nil
48
+ @lib_path = nil
49
+ end
50
+
51
+ private
52
+
53
+ def bundled_dir
54
+ File.expand_path("../../vendor/mruby", __dir__)
55
+ end
56
+
57
+ def bundled_mrbc_path
58
+ File.join(bundled_dir, "bin", "mrbc.com")
59
+ end
60
+
61
+ def bundled_libmruby_path
62
+ File.join(bundled_dir, "lib", "libmruby.a")
63
+ end
64
+
65
+ def bundled_include_path
66
+ File.join(bundled_dir, "include")
67
+ end
68
+
69
+ def bundled_lib_path
70
+ File.join(bundled_dir, "lib")
71
+ end
72
+
73
+ def default_cosmocc_dir
74
+ File.join(ENV.fetch("HOME"), ".portable-mruby", "cosmocc")
75
+ end
76
+
77
+ def cache_dir
78
+ File.join(ENV.fetch("HOME"), ".portable-mruby", "cache")
79
+ end
80
+
81
+ def ensure_bundled_mruby
82
+ unless File.exist?(bundled_mrbc_path)
83
+ raise BuildError, "Bundled mrbc not found at #{bundled_mrbc_path}. Gem may be corrupted."
84
+ end
85
+
86
+ unless File.exist?(bundled_libmruby_path)
87
+ raise BuildError, "Bundled libmruby.a not found at #{bundled_libmruby_path}. Gem may be corrupted."
88
+ end
89
+
90
+ unless File.exist?(File.join(bundled_include_path, "mruby.h"))
91
+ raise BuildError, "Bundled mruby headers not found. Gem may be corrupted."
92
+ end
93
+ end
94
+
95
+ def ensure_cosmocc
96
+ return if File.exist?(cosmocc_path)
97
+
98
+ if ENV["COSMO_ROOT"]
99
+ raise BuildError, "COSMO_ROOT is set but cosmocc not found at #{cosmocc_path}"
100
+ end
101
+
102
+ puts "Cosmopolitan toolchain not found."
103
+ puts "Downloading cosmocc (~60MB)..."
104
+
105
+ download_cosmocc
106
+ end
107
+
108
+ def download_cosmocc
109
+ FileUtils.mkdir_p(default_cosmocc_dir)
110
+ zip_path = File.join(default_cosmocc_dir, "cosmocc.zip")
111
+
112
+ download_with_progress(COSMOCC_URL, zip_path)
113
+
114
+ puts "Extracting cosmocc..."
115
+ Dir.chdir(default_cosmocc_dir) do
116
+ system("unzip", "-q", "cosmocc.zip") or raise BuildError, "Failed to extract cosmocc"
117
+ end
118
+
119
+ FileUtils.rm(zip_path)
120
+ puts "cosmocc installed to #{default_cosmocc_dir}"
121
+ end
122
+
123
+ def build_mruby_from_source(source_path)
124
+ source_path = File.expand_path(source_path)
125
+
126
+ unless File.directory?(source_path)
127
+ raise BuildError, "mruby source directory not found: #{source_path}"
128
+ end
129
+
130
+ unless File.exist?(File.join(source_path, "Rakefile"))
131
+ raise BuildError, "Invalid mruby source directory (no Rakefile): #{source_path}"
132
+ end
133
+
134
+ puts "Building mruby from source: #{source_path}"
135
+
136
+ build_config_path = File.join(source_path, "build_config", "portable_mruby.rb")
137
+ File.write(build_config_path, generate_build_config)
138
+
139
+ Dir.chdir(source_path) do
140
+ env = { "COSMO_ROOT" => cosmocc_dir }
141
+
142
+ unless system(env, "rake", "deep_clean", exception: false)
143
+ system(env, "rake", "clean")
144
+ end
145
+
146
+ system(env, "rake", "MRUBY_CONFIG=portable_mruby") or
147
+ raise BuildError, "Failed to build mruby"
148
+ end
149
+
150
+ build_dir = File.join(source_path, "build", "host")
151
+ @mrbc_path = File.join(build_dir, "bin", "mrbc.com")
152
+ @libmruby_path = File.join(build_dir, "lib", "libmruby.a")
153
+ @lib_path = File.join(build_dir, "lib")
154
+
155
+ combined_include = File.join(cache_dir, "include")
156
+ FileUtils.rm_rf(combined_include)
157
+ FileUtils.mkdir_p(combined_include)
158
+ FileUtils.cp_r(File.join(source_path, "include", "."), combined_include)
159
+ FileUtils.cp_r(File.join(build_dir, "include", "."), combined_include)
160
+ @include_path = combined_include
161
+
162
+ puts "mruby built successfully from #{source_path}"
163
+ end
164
+
165
+ def generate_build_config
166
+ <<~RUBY
167
+ COSMO_ROOT = ENV['COSMO_ROOT']
168
+
169
+ unless COSMO_ROOT && File.directory?(COSMO_ROOT)
170
+ raise "COSMO_ROOT environment variable must point to cosmocc directory"
171
+ end
172
+
173
+ MRuby::Build.new do |conf|
174
+ conf.cc do |cc|
175
+ cc.command = "\#{COSMO_ROOT}/bin/cosmocc"
176
+ cc.flags = %w[-Os -fno-omit-frame-pointer]
177
+ end
178
+
179
+ conf.cxx do |cxx|
180
+ cxx.command = "\#{COSMO_ROOT}/bin/cosmoc++"
181
+ cxx.flags = conf.cc.flags.dup
182
+ end
183
+
184
+ conf.linker do |linker|
185
+ linker.command = "\#{COSMO_ROOT}/bin/cosmocc"
186
+ linker.flags = %w[-static]
187
+ end
188
+
189
+ conf.archiver do |archiver|
190
+ archiver.command = "\#{COSMO_ROOT}/bin/cosmoar"
191
+ end
192
+
193
+ conf.exts.executable = '.com'
194
+
195
+ conf.gem core: 'hal-posix-io'
196
+ conf.gem core: 'hal-posix-socket'
197
+ conf.gem core: 'hal-posix-dir'
198
+
199
+ conf.gembox 'stdlib'
200
+ conf.gembox 'stdlib-ext'
201
+ conf.gembox 'stdlib-io'
202
+ conf.gembox 'math'
203
+ conf.gembox 'metaprog'
204
+
205
+ conf.gem core: 'mruby-bin-mrbc'
206
+ end
207
+ RUBY
208
+ end
209
+
210
+ def download_with_progress(url, dest)
211
+ URI.open(url, # rubocop:disable Security/Open
212
+ content_length_proc: ->(total) { @total_size = total },
213
+ progress_proc: lambda { |size|
214
+ if @total_size
215
+ percent = (size.to_f / @total_size * 100).round(1)
216
+ print "\rDownloading: #{percent}% (#{size / 1024 / 1024}MB / #{@total_size / 1024 / 1024}MB)"
217
+ end
218
+ }) do |remote|
219
+ File.open(dest, "wb") { |f| f.write(remote.read) }
220
+ end
221
+ puts
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+ require "fileutils"
5
+
6
+ module PortableMruby
7
+ class Builder
8
+ attr_reader :entry_file, :source_dir, :output, :verbose, :mruby_source
9
+
10
+ def initialize(entry_file: nil, source_dir: ".", output: "app.com", verbose: false, mruby_source: nil)
11
+ @entry_file = entry_file
12
+ @source_dir = File.expand_path(source_dir)
13
+ @output = output
14
+ @verbose = verbose
15
+ @mruby_source = mruby_source
16
+ @temp_dir = nil
17
+ end
18
+
19
+ def build
20
+ BinaryManager.ensure_available(mruby_source: @mruby_source)
21
+
22
+ @temp_dir = Dir.mktmpdir("portable-mruby")
23
+
24
+ ruby_files = collect_ruby_files
25
+ log "Found #{ruby_files.size} Ruby file(s)"
26
+
27
+ bytecode_files = compile_to_bytecode(ruby_files)
28
+ log "Compiled #{bytecode_files.size} bytecode file(s)"
29
+
30
+ c_source = CGenerator.new(bytecode_files).generate
31
+ c_file = File.join(@temp_dir, "app.c")
32
+ File.write(c_file, c_source)
33
+ log "Generated C source (#{c_source.size} bytes)"
34
+
35
+ compile_binary(c_file)
36
+ log "Built: #{@output}"
37
+
38
+ @output
39
+ ensure
40
+ FileUtils.rm_rf(@temp_dir) if @temp_dir
41
+ BinaryManager.reset_paths!
42
+ end
43
+
44
+ private
45
+
46
+ def log(message)
47
+ puts message if @verbose
48
+ end
49
+
50
+ def collect_ruby_files
51
+ pattern = File.join(@source_dir, "**", "*.rb")
52
+ files = Dir.glob(pattern).sort
53
+
54
+ raise BuildError, "No Ruby files found in #{@source_dir}" if files.empty?
55
+
56
+ if @entry_file
57
+ entry_path = File.absolute_path?(@entry_file) ? @entry_file : File.join(@source_dir, @entry_file)
58
+ raise BuildError, "Entry file not found: #{entry_path}" unless File.exist?(entry_path)
59
+
60
+ files.delete(entry_path)
61
+ files << entry_path
62
+ end
63
+
64
+ files
65
+ end
66
+
67
+ def compile_to_bytecode(ruby_files)
68
+ compiler = BytecodeCompiler.new
69
+
70
+ ruby_files.map do |rb_file|
71
+ relative_path = rb_file.sub("#{@source_dir}/", "")
72
+ mrb_name = relative_path.gsub("/", "_").sub(/\.rb$/, ".mrb")
73
+ mrb_path = File.join(@temp_dir, mrb_name)
74
+
75
+ compiler.compile(rb_file, mrb_path)
76
+
77
+ { mrb_path: mrb_path, name: relative_path }
78
+ end
79
+ end
80
+
81
+ def compile_binary(c_file)
82
+ cmd = [
83
+ BinaryManager.cosmocc_path,
84
+ "-Os", "-static",
85
+ "-o", @output,
86
+ c_file,
87
+ "-I#{BinaryManager.include_path}",
88
+ "-L#{BinaryManager.lib_path}",
89
+ "-lmruby", "-lm"
90
+ ]
91
+
92
+ log "Running: #{cmd.join(' ')}"
93
+
94
+ raise BuildError, "Failed to compile binary" unless system(*cmd)
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PortableMruby
4
+ class BytecodeCompiler
5
+ def initialize(mrbc_path: nil)
6
+ @mrbc_path = mrbc_path || BinaryManager.mrbc_path
7
+ end
8
+
9
+ def compile(input_rb, output_mrb = nil)
10
+ output_mrb ||= input_rb.sub(/\.rb$/, ".mrb")
11
+
12
+ unless system(@mrbc_path, "-o", output_mrb, input_rb)
13
+ raise CompileError, "Failed to compile #{input_rb}"
14
+ end
15
+
16
+ output_mrb
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PortableMruby
4
+ class CGenerator
5
+ RUNTIME_TEMPLATE = <<~'C'
6
+ #include <mruby.h>
7
+ #include <mruby/array.h>
8
+ #include <mruby/compile.h>
9
+ #include <mruby/irep.h>
10
+ #include <mruby/string.h>
11
+ #include <mruby/variable.h>
12
+ #include <stdio.h>
13
+ #include <stdlib.h>
14
+
15
+ {{BYTECODE_ARRAYS}}
16
+
17
+ static const struct {
18
+ const uint8_t *data;
19
+ size_t size;
20
+ const char *name;
21
+ } bytecode_files[] = {
22
+ {{BYTECODE_ENTRIES}}
23
+ {NULL, 0, NULL}
24
+ };
25
+
26
+ int main(int argc, char **argv) {
27
+ mrb_state *mrb;
28
+ mrb_value ARGV_val;
29
+ int i;
30
+ int return_value = 0;
31
+
32
+ mrb = mrb_open();
33
+ if (!mrb) {
34
+ fprintf(stderr, "Error: Failed to initialize mruby\n");
35
+ return 1;
36
+ }
37
+
38
+ ARGV_val = mrb_ary_new_capa(mrb, argc > 1 ? argc - 1 : 0);
39
+ for (i = 1; i < argc; i++) {
40
+ mrb_ary_push(mrb, ARGV_val, mrb_str_new_cstr(mrb, argv[i]));
41
+ }
42
+ mrb_define_global_const(mrb, "ARGV", ARGV_val);
43
+ mrb_gv_set(mrb, mrb_intern_lit(mrb, "$0"), mrb_str_new_cstr(mrb, argv[0]));
44
+
45
+ for (i = 0; bytecode_files[i].data != NULL; i++) {
46
+ mrb_load_irep(mrb, bytecode_files[i].data);
47
+
48
+ if (mrb->exc) {
49
+ mrb_print_error(mrb);
50
+ return_value = 1;
51
+ break;
52
+ }
53
+ }
54
+
55
+ mrb_close(mrb);
56
+ return return_value;
57
+ }
58
+ C
59
+
60
+ def initialize(bytecode_files)
61
+ @bytecode_files = bytecode_files
62
+ end
63
+
64
+ def generate
65
+ RUNTIME_TEMPLATE
66
+ .gsub("{{BYTECODE_ARRAYS}}", generate_bytecode_arrays)
67
+ .gsub("{{BYTECODE_ENTRIES}}", generate_bytecode_entries)
68
+ end
69
+
70
+ private
71
+
72
+ def generate_bytecode_arrays
73
+ @bytecode_files.each_with_index.map do |file_info, idx|
74
+ bytes = File.binread(file_info[:mrb_path]).bytes
75
+ byte_lines = bytes.each_slice(16).map do |slice|
76
+ " " + slice.map { |b| format("0x%02x", b) }.join(", ")
77
+ end
78
+
79
+ <<~C
80
+ static const uint8_t bytecode_#{idx}[] = {
81
+ #{byte_lines.join(",\n")}
82
+ };
83
+ C
84
+ end.join("\n")
85
+ end
86
+
87
+ def generate_bytecode_entries
88
+ @bytecode_files.each_with_index.map do |file_info, idx|
89
+ size = File.size(file_info[:mrb_path])
90
+ %( {bytecode_#{idx}, #{size}, "#{file_info[:name]}"},)
91
+ end.join("\n")
92
+ end
93
+ end
94
+ end