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.
- checksums.yaml +7 -0
- data/README.md +195 -0
- data/exe/portable-mruby +6 -0
- data/lib/portable_mruby/binary_manager.rb +225 -0
- data/lib/portable_mruby/builder.rb +97 -0
- data/lib/portable_mruby/bytecode_compiler.rb +19 -0
- data/lib/portable_mruby/c_generator.rb +94 -0
- data/lib/portable_mruby/cli.rb +136 -0
- data/lib/portable_mruby/version.rb +6 -0
- data/lib/portable_mruby.rb +15 -0
- data/vendor/mruby/bin/mrbc.com +0 -0
- data/vendor/mruby/include/mrbconf.h +230 -0
- data/vendor/mruby/include/mruby/array.h +303 -0
- data/vendor/mruby/include/mruby/boxing_nan.h +169 -0
- data/vendor/mruby/include/mruby/boxing_no.h +59 -0
- data/vendor/mruby/include/mruby/boxing_word.h +251 -0
- data/vendor/mruby/include/mruby/class.h +104 -0
- data/vendor/mruby/include/mruby/common.h +118 -0
- data/vendor/mruby/include/mruby/compile.h +185 -0
- data/vendor/mruby/include/mruby/data.h +76 -0
- data/vendor/mruby/include/mruby/debug.h +75 -0
- data/vendor/mruby/include/mruby/dump.h +159 -0
- data/vendor/mruby/include/mruby/endian.h +44 -0
- data/vendor/mruby/include/mruby/error.h +132 -0
- data/vendor/mruby/include/mruby/gc.h +72 -0
- data/vendor/mruby/include/mruby/gems/mruby-dir/include/dir_hal.h +79 -0
- data/vendor/mruby/include/mruby/gems/mruby-io/include/io_hal.h +451 -0
- data/vendor/mruby/include/mruby/gems/mruby-io/include/mruby/ext/io.h +76 -0
- data/vendor/mruby/include/mruby/gems/mruby-socket/include/socket_hal.h +83 -0
- data/vendor/mruby/include/mruby/gems/mruby-time/include/mruby/time.h +27 -0
- data/vendor/mruby/include/mruby/hash.h +234 -0
- data/vendor/mruby/include/mruby/internal.h +274 -0
- data/vendor/mruby/include/mruby/irep.h +142 -0
- data/vendor/mruby/include/mruby/istruct.h +50 -0
- data/vendor/mruby/include/mruby/khash.h +455 -0
- data/vendor/mruby/include/mruby/mempool.h +19 -0
- data/vendor/mruby/include/mruby/numeric.h +174 -0
- data/vendor/mruby/include/mruby/object.h +45 -0
- data/vendor/mruby/include/mruby/opcode.h +69 -0
- data/vendor/mruby/include/mruby/ops.h +120 -0
- data/vendor/mruby/include/mruby/presym/disable.h +72 -0
- data/vendor/mruby/include/mruby/presym/enable.h +39 -0
- data/vendor/mruby/include/mruby/presym/id.h +1423 -0
- data/vendor/mruby/include/mruby/presym/scanning.h +81 -0
- data/vendor/mruby/include/mruby/presym/table.h +2847 -0
- data/vendor/mruby/include/mruby/presym.h +41 -0
- data/vendor/mruby/include/mruby/proc.h +186 -0
- data/vendor/mruby/include/mruby/range.h +77 -0
- data/vendor/mruby/include/mruby/re.h +16 -0
- data/vendor/mruby/include/mruby/string.h +428 -0
- data/vendor/mruby/include/mruby/throw.h +57 -0
- data/vendor/mruby/include/mruby/value.h +471 -0
- data/vendor/mruby/include/mruby/variable.h +108 -0
- data/vendor/mruby/include/mruby/version.h +143 -0
- data/vendor/mruby/include/mruby.h +1614 -0
- data/vendor/mruby/lib/libmruby.a +0 -0
- 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
|
data/exe/portable-mruby
ADDED
|
@@ -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
|