ocran 1.4.6 → 1.5.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.txt +82 -0
  3. data/README.md +519 -15
  4. data/ext/extconf.rb +5 -1
  5. data/lib/ocran/aot_toolchain.rb +154 -0
  6. data/lib/ocran/build_helper.rb +7 -0
  7. data/lib/ocran/command_output.rb +8 -1
  8. data/lib/ocran/compatibility_scan.rb +414 -0
  9. data/lib/ocran/cosmo_toolchain.rb +29 -7
  10. data/lib/ocran/dir_builder.rb +88 -38
  11. data/lib/ocran/direction.rb +292 -44
  12. data/lib/ocran/ed_resource.rb +113 -0
  13. data/lib/ocran/ed_resource_builder.rb +199 -0
  14. data/lib/ocran/env_hook.rb +98 -0
  15. data/lib/ocran/file_path_set.rb +9 -5
  16. data/lib/ocran/gem_spec_queryable.rb +13 -5
  17. data/lib/ocran/host_config_helper.rb +11 -1
  18. data/lib/ocran/inno_setup_script_builder.rb +24 -14
  19. data/lib/ocran/launcher_batch_builder.rb +8 -11
  20. data/lib/ocran/npm_package.rb +106 -0
  21. data/lib/ocran/option.rb +465 -35
  22. data/lib/ocran/pe_imports.rb +96 -0
  23. data/lib/ocran/pe_resources.rb +226 -0
  24. data/lib/ocran/refine_pathname.rb +29 -32
  25. data/lib/ocran/roundhouse_builder.rb +316 -0
  26. data/lib/ocran/runner.rb +52 -11
  27. data/lib/ocran/runtime_sdk.rb +259 -0
  28. data/lib/ocran/spinel_builder.rb +183 -0
  29. data/lib/ocran/spinel_compatibility.rb +149 -0
  30. data/lib/ocran/static_runtime/boot.rb +443 -0
  31. data/lib/ocran/static_runtime/build_static_ext.rb +86 -0
  32. data/lib/ocran/static_runtime_builder.rb +814 -0
  33. data/lib/ocran/stub_builder.rb +70 -25
  34. data/lib/ocran/version.rb +1 -1
  35. data/lib/ocran/wasm_builder.rb +570 -0
  36. data/lib/ocran/wasm_compatibility.rb +198 -0
  37. data/lib/ocran/windows_command_escaping.rb +21 -0
  38. data/lib/ocran/zip_payload_builder.rb +18 -9
  39. data/lib/ocran/zip_writer.rb +48 -9
  40. data/src/Makefile +18 -2
  41. data/src/Makefile.mswin +65 -0
  42. data/src/inst_dir.c +34 -6
  43. data/src/path_utils.c +144 -0
  44. data/src/script_info.c +5 -0
  45. data/src/stub.c +77 -19
  46. data/src/stub.rc +1 -1
  47. data/src/system_utils.c +276 -155
  48. data/src/system_utils.h +46 -3
  49. data/src/system_utils_posix.c +243 -148
  50. data/src/unpack.c +9 -0
  51. metadata +25 -4
data/README.md CHANGED
@@ -1,12 +1,29 @@
1
- # OCRAN
1
+ # OCRAN — turn Ruby scripts into standalone executables (.exe) for Windows, Linux and macOS
2
2
 
3
- ![OCRAN: ship your Ruby app as one file, no Ruby needed to run it](docs/social/twitter-card.png)
3
+ ![OCRAN: ship your Ruby app as one file, no Ruby needed to run it](https://raw.githubusercontent.com/Largo/ocran/master/docs/social/twitter-card.png)
4
4
 
5
- [OCRAN (One-Click Ruby Application Next)](https://github.com/largo/ocran) packages Ruby applications for
5
+ [![Gem Version](https://badge.fury.io/rb/ocran.svg)](https://rubygems.org/gems/ocran)
6
+
7
+ **OCRAN (One-Click Ruby Application Next) is a Ruby-to-executable packager:
8
+ `ocran app.rb` turns a Ruby script into a single `.exe` (Windows) or native
9
+ binary (Linux, macOS) that runs on machines without Ruby installed.** It is
10
+ the maintained successor of [OCRA](https://github.com/larsch/ocra) for
11
+ Ruby 3.3 and newer.
12
+
13
+ [OCRAN](https://github.com/largo/ocran) packages Ruby applications for
6
14
  distribution. It bundles your script, the Ruby interpreter, gems, and native
7
15
  libraries into a single self-contained artifact that runs without requiring
8
16
  Ruby to be installed on the target machine.
9
17
 
18
+ Quick start:
19
+
20
+ ```
21
+ gem install ocran
22
+ ocran app.rb # → app.exe on Windows, ./app on Linux/macOS
23
+ ```
24
+
25
+ See the [FAQ](#faq) and the [comparison with other Ruby packagers](#comparison-with-other-ruby-packagers).
26
+
10
27
  OCRAN supports four output formats, all cross-platform:
11
28
 
12
29
  * **Self-extracting executable** — bundles everything into a single binary that unpacks and runs transparently, with no Ruby installation required. Produces a `.exe` on Windows, and a native executable on Linux and macOS.
@@ -15,7 +32,7 @@ OCRAN supports four output formats, all cross-platform:
15
32
  * **Zip archive** (`--output-zip`) — same as directory output, packed into a `.zip`.
16
33
 
17
34
  OCRAN is a fork of [OCRA](https://github.com/larsch/ocra) maintained for
18
- Ruby 3.2+ compatibility.
35
+ Ruby 3.3+ compatibility.
19
36
 
20
37
  If you run into errors while using OCRAN, please check the [OCRAN issues](https://github.com/largo/ocran/issues) first.
21
38
 
@@ -76,7 +93,7 @@ This repository includes `lzma.exe` from the
76
93
  [official ip7z/7zip release](https://github.com/ip7z/7zip/releases)
77
94
  (version 22.01, from `lzma2201.7z`), used to compress Windows executables.
78
95
 
79
- `stub.exe`, `stubw.exe`, and `edicon.exe` are compiled from source in this
96
+ `stub.exe`, `stubw.exe` and the Linux/macOS `stub` are compiled from source in this
80
97
  repository.
81
98
 
82
99
  ## Installation
@@ -153,7 +170,7 @@ These options control which files from included gems are added to the output.
153
170
  * `--gem-guess[=gem1,..]`: Include loaded scripts and a best guess of other needed files (DEFAULT).
154
171
  * `--gem-all[=gem1,..]`: Include all scripts and important files from the gem.
155
172
  * `--gem-full[=gem1,..]`: Include every file in the gem directory.
156
- * `--gem-spec[=gem1,..]`: Include files listed in the gemspec (not compatible with newer RubyGems).
173
+ * `--gem-spec[=gem1,..]`: Include the files listed in the gemspec.
157
174
 
158
175
  Fine-tuning flags:
159
176
 
@@ -163,7 +180,7 @@ Fine-tuning flags:
163
180
 
164
181
  #### Auto-detection options:
165
182
 
166
- * `--no-dep-run`: Skip running the script to detect dependencies. Use this if your script has side effects during load or if you are manually specifying all dependencies. Requires `--add-all-core` and `--gem-full`.
183
+ * `--no-dep-run`: Skip running the script to detect dependencies. Use this if your script has side effects during load or if you are manually specifying all dependencies. Nothing the script loads is detected then, so you usually need `--add-all-core` for the standard library and `--gemfile` with `--gem-full` (or `--gem-all`) for gems; OCRAN warns when they are missing.
167
184
  * `--no-autoload`: Do not attempt to load `autoload`ed constants.
168
185
  * `--no-autodll`: Disable automatic detection of runtime DLL dependencies.
169
186
 
@@ -188,8 +205,126 @@ Fine-tuning flags:
188
205
  * `--debug`: Enable verbose output when the generated executable runs.
189
206
  * `--debug-extract`: Unpack to a local directory and do not delete after execution (useful for troubleshooting).
190
207
 
208
+ #### Windows resource options:
209
+
210
+ These set the PE resources of the generated executable, with the same names
211
+ as [rcedit](https://github.com/electron/rcedit). They apply to the
212
+ self-extracting `.exe` and to the wrapper `.exe` of `--output-dir`,
213
+ `--output-zip` and `--innosetup`. All are opt-in: without them the
214
+ executable is built as before. (Windows only)
215
+
216
+ * `--set-version-string <key> <value>` (alias `--version-string`): Add a `StringFileInfo` entry such as `CompanyName`, `ProductName`, `FileDescription` or `LegalCopyright`. Repeat for each key.
217
+ * `--set-file-version <x.y.z.w>` (alias `--file-version`): Set the numeric and string file version.
218
+ * `--set-product-version <x.y.z.w>` (alias `--product-version`): Set the numeric and string product version.
219
+ * `--set-resource-string <id> <value>` (alias `--resource-string`): Set an entry of the `RT_STRING` table by its numeric id.
220
+ * `--set-requested-execution-level <level>` (alias `--uac-level`): Set the UAC level in the manifest: `asInvoker` (the default), `highestAvailable` or `requireAdministrator`.
221
+ * `--application-manifest <file>` (alias `--manifest`): Replace the built-in application manifest with the given file. `--set-requested-execution-level` still applies to it.
222
+
223
+ ```
224
+ ocran app.rb --set-version-string CompanyName "Example Ltd" \
225
+ --set-version-string ProductName "Example App" \
226
+ --set-version-string FileDescription "Example App" \
227
+ --set-file-version 1.2.0.0 --set-product-version 1.2.0.0
228
+ ```
229
+
230
+ An unsigned executable with no version information, which extracts files
231
+ to `%TEMP%` and runs them, is what antivirus and EDR heuristics expect of a
232
+ malware packer. Version information, an icon and a code signature give it
233
+ an identity instead. [docs/av-edr-false-positives.md](docs/av-edr-false-positives.md)
234
+ goes through these steps in order of impact.
235
+
191
236
  #### Experimental options:
192
237
 
238
+ * `--static-runtime <runtime-dir>`: Build a single executable for Windows,
239
+ Linux or macOS that runs the application **without extracting anything**:
240
+ no temporary directory, no DLLs or shared libraries written to disk. The
241
+ executable is a statically linked Ruby (the *static runtime*) with the
242
+ application as a plain ZIP archive: appended on Windows and Linux, in a
243
+ section of its own on macOS (`__OCRAN,__zip`), where appended data would
244
+ break the code signature. The Linux runtime is linked against musl and
245
+ fully static, so it runs on any x86_64 distribution.
246
+
247
+ ```
248
+ ocran app.rb --static-runtime path/to/runtime # app.exe (Windows) or app (Linux, macOS)
249
+ unzip -l app.exe # lists exactly what will run (Linux: app)
250
+ unzip -p app.exe ocran/manifest.sha256 # SHA-256 of the runtime and every file
251
+
252
+ segedit app -extract __OCRAN __zip app.zip # macOS: the archive is a section
253
+ unzip -l app.zip
254
+ ```
255
+
256
+ The runtime directory holds `ocran-runtime(.exe)`, its own standard
257
+ library and bundled gems (`lib/ruby/`) and `runtime.json`; build one with
258
+ `runtime/build-runtime.sh` (Windows, MinGW, on Linux), the scripts in
259
+ `runtime/mswin/` (Windows, Visual Studio Build Tools) or
260
+ `runtime/linux/build-runtime.sh` (Linux x86_64, musl) or
261
+ `runtime/macos/build-runtime.sh` (macOS, on a Mac). The Windows
262
+ runtimes import only DLLs that ship with Windows and need no VC++
263
+ redistributable; the Linux runtime imports nothing at all; the macOS
264
+ runtime links only libraries of macOS itself (libSystem, libiconv,
265
+ CoreFoundation). On macOS every executable is linked at packaging time,
266
+ with its archive, so packaging for macOS needs a Mac with clang (Xcode or
267
+ the Command Line Tools); the linker signs the result ad hoc, and it can
268
+ be signed again with `codesign`. Which system
269
+ the executable is for follows from the runtime, not from the build host:
270
+ a Linux build host can package for Windows and the other way round, as
271
+ long as the application does not depend on platform-specific gems. See
272
+ [runtime/README.md](runtime/README.md) for how it works and how it is
273
+ built. CI builds and tests the three runtimes and publishes each as a
274
+ reproducible archive (a zip for Windows, a tarball otherwise) with a
275
+ signed build provenance attestation
276
+ (`gh attestation verify <archive> -R Largo/ocran`); releases from 1.5.1
277
+ on have them attached.
278
+
279
+ Dependency detection runs as usual, under the build host's Ruby, which
280
+ must have the runtime's API version (e.g. 4.0.0). The standard library
281
+ and the gems the runtime ships come from the runtime, so that they match
282
+ its interpreter; OCRAN says when one replaces a different version on the
283
+ build host. C extensions are linked into the executable, since nothing
284
+ can be loaded from inside it: those the runtime has (openssl, psych,
285
+ zlib, fiddle, win32ole, bigdecimal, ...) are used as they are; any other
286
+ gem's extension is built from the gem's source against the runtime's
287
+ SDK (`sdk/` in the runtime directory) and linked into a runtime of the
288
+ application's own. That needs a compiler for the runtime: for a MinGW
289
+ runtime `x86_64-w64-mingw32ucrt-gcc` on Linux, or on Windows
290
+ RubyInstaller's MSYS2 GCC named with `OCRAN_RUNTIME_CC=gcc` (with
291
+ `ucrt64\bin` and `usr\bin` on PATH); for an mswin runtime `cl` and
292
+ `nmake` of Visual Studio Build Tools, from a developer prompt; for a
293
+ Linux runtime `musl-gcc`; for macOS clang. Gems that
294
+ need a C library get the one the SDK provides: nokogiri uses its
295
+ libxml2/libxslt, tiny_tds its FreeTDS. A precompiled platform gem is
296
+ built from its source gem of the same version, which OCRAN fetches from
297
+ the configured gem sources into `~/.cache/ocran/gems`, together with the
298
+ gems its build needs that are not installed (nokogiri's extconf.rb needs
299
+ mini_portile2), installed there for the build only. Failing that, it is
300
+ built from the C sources it carries, if any (`gem install <gem>
301
+ --platform ruby` installs the source gem).
302
+
303
+ **Licenses:** the executable is statically linked - Ruby, OpenSSL and
304
+ the other libraries are inside it, and their license texts are packed
305
+ into it as `ocran/licenses/`. Two optional libraries are under the LGPL
306
+ (GNU libiconv, FreeTDS: used by nokogiri and tiny_tds), which asks that
307
+ users can replace them; `--runtime-dll iconv,sybdb` ships them as DLLs
308
+ next to the executable instead. Read
309
+ [runtime/LICENSES.md](runtime/LICENSES.md) before distributing.
310
+
311
+ OpenSSL verifies certificates against the Windows certificate store, as
312
+ Windows itself does, and on Linux and macOS against the system's CA
313
+ bundle - no CA file is packed or needed. The application reads its own files as
314
+ usual (`require`, `require_relative`, `autoload`, `File.read`,
315
+ `Dir.glob`, `__dir__`, `DATA`); their paths lie inside the executable
316
+ (`C:/app/tool.exe/src/tool.rb`) and cannot be written. Files next to the
317
+ executable are found through `ENV["OCRAN_EXECUTABLE"]`. `--windows` makes
318
+ a GUI-subsystem executable; `--icon` and the version-resource options
319
+ work as for the stub, on any build host (OCRAN writes the resources in
320
+ Ruby). `--dll` and the output-directory formats do not apply.
321
+ Experimental.
322
+
323
+ * `--runtime-dll <lib,...>`: With `--static-runtime`, link these C libraries of the
324
+ runtime's SDK as DLLs shipped next to the executable rather than into it
325
+ (the SDK has DLL builds of `iconv` and `sybdb`, the LGPL libraries).
326
+ OCRAN copies them, and the DLLs they need, next to the executable.
327
+
193
328
  * `--cosmo-ruby <ruby.com>`: Package a cosmopolitan-built Ruby (an APE)
194
329
  as the bundled interpreter instead of the host Ruby. The produced
195
330
  `.com` contains no host-native code at all and runs on Linux, Windows
@@ -256,7 +391,8 @@ Fine-tuning flags:
256
391
  `__dir__` for that — it never meant "next to the exe" in either
257
392
  mode, but here it is obviously wrong instead of subtly wrong.
258
393
  * `--chdir-first` changes into the directory containing the
259
- executable, since the application directory does not exist on disk.
394
+ executable, since the application directory does not exist on disk;
395
+ `--chdir-exe-dir` does the same.
260
396
  * The whole command line reaches `ARGV`, unchanged. The interpreter
261
397
  claims none of it, so `app.com --version`, `app.com -v` and
262
398
  `app.com -- x` behave exactly as they would for a natively compiled
@@ -345,6 +481,147 @@ Fine-tuning flags:
345
481
  launcher stub, not to the bundled application. See
346
482
  `docs/cosmocc-port-plan.md` for status.
347
483
 
484
+ #### Native compilation (experimental):
485
+
486
+ Instead of packaging the interpreter, these options compile the program
487
+ ahead of time with [Spinel](https://github.com/matz/spinel), Matz's Ruby
488
+ AOT compiler. The result is a native executable that needs no Ruby at all.
489
+ Spinel compiles a *subset* of Ruby (no `eval` of strings, no
490
+ `method_missing`, no runtime class building, only the standard library it
491
+ ships itself), so this works for some programs and not others. When it
492
+ does not, OCRAN tells you why.
493
+
494
+ * `--spinel`: Compile `script.rb` with Spinel:
495
+
496
+ ```
497
+ ocran app.rb --spinel # -> ./app (app.exe on Windows)
498
+ ocran app.rb --spinel --output myapp
499
+ ```
500
+
501
+ The script is not run at build time. OCRAN scans the program statically
502
+ (the script, the files it requires, and the source of every gem it
503
+ requires) and passes Spinel the `-I` roots it needs for your `lib/` and
504
+ for pure-Ruby gems. If Spinel cannot compile the program, OCRAN prints
505
+ the compiler's error, then a report of what stands in the way, grouped
506
+ into your code and each gem. Gems with C extensions and standard
507
+ libraries Spinel does not provide are named as incompatible. If
508
+ `spinel-doctor` is installed next to the compiler, its report is shown
509
+ too. If Spinel is not installed, OCRAN explains how to install it and
510
+ still shows the report, so you can see whether installing it is worth
511
+ it. Use `--spinel-opt <arg>` (repeatable) to pass flags to the compiler,
512
+ for example `--spinel-opt --int-overflow=promote` or
513
+ `--spinel-opt --defer-refusals`. With `--debug` the binary is built
514
+ with `-g`. Only code is compiled in; data files have to ship next to
515
+ the executable.
516
+
517
+ * `--roundhouse`: Compile a **Rails application** into a native server
518
+ binary with [Roundhouse](https://github.com/rubys/roundhouse), which
519
+ lowers the app to the Ruby subset Spinel compiles. OCRAN runs the
520
+ transpile, the generated project's asset step (`make assets`, with
521
+ only the Gemfile's assets group bundled) and `spin build`:
522
+
523
+ ```
524
+ ocran --roundhouse path/to/rails/app # -> ./app-spinel/
525
+ ```
526
+
527
+ The output is a directory (`<app>-spinel` by default, or `--output` /
528
+ `--output-dir`) that contains the binary plus the `static/`, `public/`,
529
+ `db/` and `config/` files it reads at run time, and a `storage/` for
530
+ the SQLite database. On the first build the database is created from
531
+ `db/seed.sql` when the `sqlite3` command is available. Rebuilding keeps
532
+ `storage/`. If Roundhouse does not cover the app yet, OCRAN shows the
533
+ useful parts of `roundhouse check --continue`: the gem census (which of
534
+ the app's gems Roundhouse does not model), the first errors, and the
535
+ list of unsupported constructs. Pass flags to Roundhouse with
536
+ `--roundhouse-opt <arg>`, for example
537
+ `--roundhouse-opt --survey --roundhouse-opt --allow-unsupported`.
538
+
539
+ **Installing the compilers.** Neither is a gem. OCRAN looks for `spinel`,
540
+ `spin` and `roundhouse` in the `SPINEL`, `SPIN` and `ROUNDHOUSE`
541
+ environment variables (the command or the directory that contains it), then
542
+ in `PATH`, then in their usual install locations (`~/.local/bin`,
543
+ `/usr/local/bin`, `~/.cargo/bin`). When one is missing, OCRAN prints how to
544
+ install it:
545
+
546
+ ```
547
+ git clone https://github.com/matz/spinel && cd spinel
548
+ make deps && make && make install PREFIX=$HOME/.local
549
+
550
+ curl --proto '=https' --tlsv1.2 -LsSf \
551
+ https://github.com/rubys/roundhouse/releases/latest/download/roundhouse-installer.sh | sh
552
+ ```
553
+
554
+ Apps compiled with Roundhouse also need the SQLite and jemalloc
555
+ development headers (`libsqlite3-dev libjemalloc-dev`, or
556
+ `brew install sqlite jemalloc`). OCRAN puts the directory `pkg-config`
557
+ reports for the allocator on `LIBRARY_PATH` for the build, so a Homebrew
558
+ jemalloc on Apple Silicon (under `/opt/homebrew/lib`, which the linker does
559
+ not search by default) links. The asset step needs Bundler, and Node.js
560
+ with npm when the app builds Tailwind.
561
+
562
+ **Windows.** Spinel does not build natively on Windows yet, and
563
+ Roundhouse's Windows build is untested. For now, run OCRAN inside WSL,
564
+ where both work as they do on Linux; you get a Linux binary. Nothing in
565
+ OCRAN is tied to a platform here: once the compilers run on Windows,
566
+ `--spinel` and `--roundhouse` work there as they are and produce `.exe`
567
+ files.
568
+
569
+ #### WebAssembly export (experimental):
570
+
571
+ `--wasm` exports the application as a web page that runs it in the browser.
572
+ The output is a folder (`<scriptname>-wasm` by default, or `--output-dir`)
573
+ or a zip archive (`--output-zip`, or an `--output` name that ends in
574
+ `.zip`). It contains `index.html`, the runtime and your program. The page
575
+ shows the program's standard output and errors. Use the `js` library to
576
+ work with the page itself. Browsers only load WebAssembly over HTTP, so
577
+ serve the folder instead of opening the file:
578
+
579
+ ```
580
+ ocran app.rb --wasm # -> app-wasm/
581
+ ocran app.rb --wasm=picoruby --output app.zip
582
+ python3 -m http.server -d app-wasm 8000 # then open http://localhost:8000
583
+ ```
584
+
585
+ There are two runtimes:
586
+
587
+ * `--wasm` (or `--wasm=ruby`): [ruby.wasm](https://github.com/ruby/ruby.wasm),
588
+ CRuby compiled to WebAssembly, with its whole standard library (about
589
+ 30 MB). It needs `rbwasm` from the `ruby_wasm` gem (`gem install ruby_wasm`;
590
+ OCRAN also uses `RBWASM` when it is set).
591
+ * OCRAN packs your files into the prebuilt interpreter from npm, which
592
+ takes seconds. Gems must be listed in the application's Gemfile and
593
+ installed (`bundle install`); pure-Ruby gems are packed in beside your
594
+ files, at `/gems`, and put on the load path in place of
595
+ `bundler/setup`, so `require "bundler/setup"` in your program is fine.
596
+ * Only when a gem has a C extension does OCRAN run `rbwasm build`. Its
597
+ first run compiles CRuby and the gems for WebAssembly, which takes
598
+ several minutes, and is cached in `~/.cache/ocran/ruby_wasm`. The
599
+ extensions are cross-compiled for WASI, and the ones that link system
600
+ libraries usually fail.
601
+ * The Ruby version follows the Ruby you run OCRAN with. Use
602
+ `--wasm-ruby 3.4` to choose another one. Pass extra flags to
603
+ `rbwasm build` with `--wasm-opt <arg>`.
604
+ * `--wasm=picoruby` (or `--picoruby`): [PicoRuby](https://github.com/picoruby/picoruby),
605
+ the mruby-based Ruby for microcontrollers.
606
+ * Its runtime is about 2 MB, and nothing needs to be installed.
607
+ * PicoRuby has its own small library (`js`, `json`, `yaml`, `base64`, ...)
608
+ and no RubyGems. In the browser it cannot read your files, so OCRAN
609
+ bundles the program's files into one script, in the order they are
610
+ required.
611
+
612
+ In both cases the script is not run at build time. OCRAN scans the program
613
+ and reports what may not work on the chosen runtime, grouped into your code
614
+ and each gem:
615
+
616
+ * gems the runtime cannot load;
617
+ * threads, processes, sockets and `gets`;
618
+ * for PicoRuby, also CRuby standard libraries it does not have.
619
+
620
+ The build goes ahead anyway, since the program only fails if it reaches
621
+ those parts. The runtimes are downloaded from the npm registry once, checked
622
+ against the integrity hash the registry publishes, and cached in
623
+ `~/.cache/ocran/npm`.
624
+
348
625
  ### Compilation:
349
626
 
350
627
  * OCRAN runs your script (using `Kernel#load`) and builds the output when it exits.
@@ -384,6 +661,41 @@ On Windows, run the batch file:
384
661
 
385
662
  myapp\script.bat
386
663
 
664
+ ### Running as a Windows service
665
+
666
+ The self-extracting `.exe`, the wrapper `.exe` and the launch script all
667
+ start the packed `ruby.exe` as a child process. The Windows service control
668
+ manager accepts the service dispatcher only from the process it started
669
+ itself, so a service registered through any of them fails with error 1063
670
+ ("The service process could not connect to the service controller").
671
+
672
+ Build with `--output-dir` (or `--innosetup`) instead and register the packed
673
+ interpreter directly, e.g. with `sc create` or the
674
+ [win32-service](https://github.com/chef/win32-service) gem's
675
+ `Win32::Service.create`:
676
+
677
+ ocran service.rb --output-dir C:\MyService
678
+ sc create MyService binPath= "C:\MyService\bin\ruby.exe C:\MyService\src\service.rb"
679
+
680
+ That bare `ruby.exe` still gets the package's environment: OCRAN packs a
681
+ RubyGems startup hook (`lib/ruby/site_ruby/<version>/rubygems/defaults/operating_system.rb`)
682
+ that sets `GEM_HOME`, `GEM_PATH`, `RUBYLIB`, `SSL_CERT_FILE` and the other
683
+ variables of the launch script to the directory's actual location, unless
684
+ they are already set, clears those the launch script clears
685
+ (`BUNDLE_GEMFILE`), and then loads the Ruby installation's own one
686
+ (RubyInstaller's). OCRAN leaves the hook out, and warns, when the building
687
+ Ruby has such a file in its `site_ruby` already or keeps `site_ruby` outside
688
+ its installation directory; then set those variables in the service's
689
+ environment yourself. The hook does not change the working directory (a service
690
+ starts in `C:\Windows\System32`), so find your files from `__dir__`. If the
691
+ script the launch script runs is a generated `ocran-rubyopt-launcher.rb`
692
+ (`-I`/`-r` in `RUBYOPT`), register that one.
693
+
694
+ OCRAN runs the script at build time, where the service's `mainloop` cannot
695
+ work: skip it with `defined?(Ocran)` (see "Detecting OCRAN at build time")
696
+ and `require` there what the service only loads later, such as `net/http`,
697
+ so that it gets packed.
698
+
387
699
  ### Pitfalls:
388
700
 
389
701
  * Avoid modifying load paths at runtime. Use `-I` or `RUBYLIB` if needed, but don't expect OCRAN to preserve them for runtime. OCRAN may pack sources into other directories than you
@@ -500,10 +812,10 @@ Notes:
500
812
 
501
813
  ## Requirements
502
814
 
503
- * Ruby 3.2+
504
- * For building Windows `.exe`: Windows with [RubyInstaller DevKit](https://rubyinstaller.org/downloads/) (mingw-w64), or Wine on Linux/macOS
815
+ * Ruby 3.3+
816
+ * For building Windows `.exe`: Windows with [RubyInstaller DevKit](https://rubyinstaller.org/downloads/) (mingw-w64), or Wine on Linux/macOS. An mswin Ruby (built with Visual C++, see [mswin Ruby](#mswin-ruby-visual-c)) works as well.
505
817
  * For building Linux and MacOS binaries: the respective build tools
506
- * For `--output-dir` / `--output-zip`: any platform with Ruby 3.2+
818
+ * For `--output-dir` / `--output-zip`: any platform with Ruby 3.3+
507
819
  * For `--output-zip` on Linux/macOS: the `zip` command must be available
508
820
  * For `--output-zip` on Windows: PowerShell (included in Windows 8+)
509
821
 
@@ -515,6 +827,32 @@ The architecture of the generated executable matches the Ruby interpreter used t
515
827
 
516
828
  * **Windows on ARM (Windows 11 ARM)** — RubyInstaller recently started shipping ARM64 builds; Installing the ARM64 RubyInstaller on a Windows ARM machine and running OCRAN from it will produce an ARM64 `.exe`. These run on ARM Windows natively. Installing the standard x86-64 RubyInstaller on a Windows ARM machine and running OCRAN from it will (probably?) produce an x86-64 `.exe`. These run on ARM Windows via the built-in x86-64 emulation layer, so the executables work on both ARM and x86-64 Windows targets. If you use `--no-lzma`, note that `lzma.exe` (the bundled compressor) is also x86-64 and relies on the same emulation.
517
829
 
830
+ ### mswin Ruby (Visual C++)
831
+
832
+ OCRAN also packages with an mswin Ruby (`x64-mswin64_140`), the Ruby built
833
+ with Microsoft Visual C++ instead of MinGW, e.g. the official builds from
834
+ <https://cache.ruby-lang.org/pub/ruby/binaries/mswin64/> or
835
+ `ruby/setup-ruby` with `ruby-version: mswin`. There is no MSYS2 devkit:
836
+
837
+ * The source gem (and `rake build` in a checkout) compiles the stubs with
838
+ `nmake -f src/Makefile.mswin`, so run it from a Visual Studio developer
839
+ prompt (`vcvars64.bat`, Build Tools with the C++ workload), as for any
840
+ native gem on mswin. The stubs link the C runtime statically.
841
+ * The Ruby DLL of an mswin Ruby links the Visual C++ runtime
842
+ (`vcruntime140.dll`), which is not part of Windows: without the
843
+ [VC++ Redistributable](https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist)
844
+ `ruby.exe` cannot start. OCRAN bundles the VC++ runtime DLLs next to the
845
+ packed `ruby.exe` (app-local deployment), so the executable runs on a
846
+ machine without the Redistributable.
847
+ * The libraries an mswin Ruby uses from vcpkg (gmp, libffi, OpenSSL,
848
+ libyaml, zlib) live next to `ruby.exe`. OCRAN reads the import tables of
849
+ `ruby.exe`, the Ruby DLL and the packed extensions and bundles the DLLs
850
+ they need from there, even with `--no-autodll` (as it packs RubyInstaller's
851
+ `ruby_builtin_dlls`).
852
+ * If the packed interpreter still cannot load a DLL, the executable says so
853
+ (`The Ruby interpreter could not start: a DLL it needs was not found`)
854
+ instead of exiting silently with status 0xC0000135.
855
+
518
856
  ## Development
519
857
 
520
858
  ### Quick start
@@ -536,7 +874,7 @@ The architecture of the generated executable matches the Ruby interpreter used t
536
874
 
537
875
  | Task | Purpose |
538
876
  |--------------|------------------------------------------------------------------------|
539
- | `rake build` | Compile stub(.exe) (requires MSVC or mingw-w64 + DevKit or Unix build tools) |
877
+ | `rake build` | Compile stub(.exe) (requires mingw-w64 + DevKit, MSVC with an mswin Ruby, or Unix build tools) |
540
878
  | `rake clean` | Remove generated binaries |
541
879
  | `rake test` | Execute all unit & integration tests |
542
880
 
@@ -1035,10 +1373,176 @@ actually encrypts stay out of reach until CosmoRuby's `openssl` grows a
1035
1373
  cipher surface — mbedtls has AES, GCM and PKCS5, so it is a bounded job;
1036
1374
  it is tracked in `PORTING-NOTES.md` in the CosmoRuby repository.
1037
1375
 
1038
- ## See elsewhere
1376
+ ## FAQ
1377
+
1378
+ ### How do I convert a Ruby script into an .exe?
1039
1379
 
1040
- - [State of Ruby Packagers](https://gist.github.com/YOU54F/3775e66e6090e0371c11601e6b75c305)
1041
- - [Traveling Ruby](https://github.com/trubygems/traveling-ruby)
1380
+ Install OCRAN on Windows and run it on your entry script:
1381
+
1382
+ gem install ocran
1383
+ ocran app.rb
1384
+
1385
+ The result is `app.exe`, which contains your script, the Ruby interpreter,
1386
+ the gems it loaded and the DLLs they need. On Linux and macOS the same
1387
+ command produces a native executable `./app`.
1388
+
1389
+ ### Does the target machine need Ruby installed?
1390
+
1391
+ No. The executable carries its own Ruby interpreter, standard library,
1392
+ gems and native libraries.
1393
+
1394
+ ### Can I build a Windows .exe on Linux or macOS?
1395
+
1396
+ Not with the default mode: OCRAN bundles the Ruby of the machine it runs
1397
+ on, so build a Windows `.exe` on Windows (for example on a GitHub Actions
1398
+ Windows runner, see
1399
+ [Building for multiple platforms](#building-for-multiple-platforms-with-github-actions)),
1400
+ or run a Windows Ruby and OCRAN under Wine on Linux/macOS.
1401
+ Two experimental modes package without the target's own Ruby:
1402
+ `--static-runtime` with a Windows runtime builds a Windows `.exe` on
1403
+ Linux (and the other way round), and `--cosmo-ruby` packages a
1404
+ cosmopolitan Ruby into one file that runs on Windows, Linux and macOS.
1405
+
1406
+ ### What is the difference between OCRA and OCRAN?
1407
+
1408
+ OCRAN is a maintained fork of OCRA. OCRA targets Windows only and is no
1409
+ longer updated for current Ruby versions; OCRAN supports Ruby 3.3+, builds
1410
+ for Windows, Linux and macOS, and adds directory, zip, macOS `.app` and
1411
+ Inno Setup output. To migrate, call `ocran` instead of `ocra` and replace
1412
+ `OCRA_EXECUTABLE` with `OCRAN_EXECUTABLE` in your code.
1413
+
1414
+ ### Which gems and native extensions are supported?
1415
+
1416
+ OCRAN runs your script once at build time and records every file that
1417
+ `require` and `load` pull in, including compiled extensions and the DLLs
1418
+ or shared libraries they link. In the default mode, gems with native extensions
1419
+ (for example `sqlite3`, `pg`, `nokogiri`) are packaged along with their
1420
+ libraries; the experimental `--cosmo-ruby` mode only supports the native
1421
+ extensions built into its interpreter. Use
1422
+ `--gemfile` to include everything from a Bundler `Gemfile`, and the
1423
+ [gem detection modes](#gem-content-detection-modes) to include extra files.
1424
+
1425
+ ### Can I package a Rails, GUI or command-line app?
1426
+
1427
+ Yes. Command-line tools work out of the box, GUI apps can use
1428
+ `--windows` to hide the console on Windows, and Rails apps with SQLite are
1429
+ covered in [Packaging a Rails application](#packaging-a-rails-application).
1430
+
1431
+ ### Why does my packaged app start slowly?
1432
+
1433
+ The default self-extracting executable unpacks Ruby to a temporary
1434
+ directory on every launch. Use `--output-dir`, `--output-zip` or
1435
+ `--innosetup` to install the files once, `--static-runtime` for a single
1436
+ executable that runs without extracting, or `--cosmo-ruby` with an
1437
+ interpreter that supports ZIP packaging (CosmoRuby `v4.0.6-cosmo3` or
1438
+ newer) to run without extracting; with other interpreters `--cosmo-ruby`
1439
+ falls back to a launcher stub that extracts on every start.
1440
+
1441
+ ### My antivirus flags the generated .exe. What can I do?
1442
+
1443
+ Self-extracting executables that unpack an interpreter are a common
1444
+ heuristic trigger. Sign the executable (OCRAN output is code-signing
1445
+ compatible, e.g. with `signtool` on Windows), ship an
1446
+ installer built with `--innosetup`, or ship a plain directory with
1447
+ `--output-dir`.
1448
+
1449
+ ## Comparison with other Ruby packagers
1450
+
1451
+ | Tool | Output | Platforms | Single file | Maintained for current Ruby |
1452
+ |------|--------|-----------|-------------|--------------------------|
1453
+ | **OCRAN** | `.exe` / native binary, directory, zip, macOS `.app`, Inno Setup installer | Windows, Linux, macOS | Yes | Yes |
1454
+ | [OCRA](https://github.com/larsch/ocra) | `.exe` | Windows only | Yes | No |
1455
+ | [Traveling Ruby](https://github.com/trubygems/traveling-ruby) | Prebuilt Ruby runtime you ship next to your app | Windows, Linux, macOS | No | Yes |
1456
+ | [Tebako](https://github.com/tamatebako/tebako) | Single binary with an embedded filesystem | Windows, Linux, macOS | Yes | Yes |
1457
+ | [ruby-packer](https://github.com/pmq20/ruby-packer) | Single binary with Ruby compiled from source | Windows, Linux, macOS | Yes | No |
1458
+
1459
+ OCRAN's distinguishing features are that, in its default mode, it needs no
1460
+ C compiler to package an app (it reuses your installed Ruby), detects dependencies by running
1461
+ your script, and can produce installers and macOS app bundles directly.
1462
+
1463
+ See also: [State of Ruby Packagers](https://gist.github.com/YOU54F/3775e66e6090e0371c11601e6b75c305).
1464
+
1465
+ ## Security, dependencies and reproducibility
1466
+
1467
+ A packager decides which interpreter, libraries and gems end up on your
1468
+ users' machines, so where those come from matters as much as the
1469
+ packaging itself. OCRAN is built so that nothing reaches the executable
1470
+ that you did not install, run and test yourself, and so that you can
1471
+ check what went in.
1472
+
1473
+ **No hidden downloads.** In its default mode OCRAN makes no network
1474
+ requests: it does not fetch an interpreter, prebuilt runtime, patched
1475
+ Ruby source or library tarball while packaging. The executables it
1476
+ produces, in every mode, never download a runtime when they start. Some
1477
+ packaging approaches ship a small launcher that pulls the actual runtime
1478
+ from the internet on first launch; the app then depends on a server
1479
+ being reachable and trustworthy long after you released it. An OCRAN
1480
+ executable carries the interpreter, gems and libraries it runs; whether
1481
+ your app uses the network is up to your own code. The opt-in modes that
1482
+ do fetch say so and verify what they get:
1483
+
1484
+ * `--wasm` downloads the ruby.wasm or PicoRuby package from the npm
1485
+ registry once, checks it against the SHA-512 integrity the registry
1486
+ publishes, and caches it, so later builds run offline.
1487
+ * `--static-runtime` fetches the source gem of a precompiled platform gem
1488
+ from your configured gem sources when it has to build that gem's
1489
+ extension for the runtime.
1490
+
1491
+ **Your Ruby, from the source you already trust.** By default OCRAN
1492
+ packages the Ruby interpreter installed on the build machine —
1493
+ RubyInstaller, a distribution package, `ruby-build`, `ruby/setup-ruby` in
1494
+ CI, or your own build. It does not substitute a separately built or
1495
+ patched interpreter from a third party. Security fixes reach your app the
1496
+ same way they reach your development machine: update Ruby, rebuild.
1497
+ Packagers that embed their own copy of Ruby and its C libraries (OpenSSL,
1498
+ zlib, libyaml, libffi, ...) freeze those versions at whatever the
1499
+ packager last shipped, and fall behind on security fixes when the
1500
+ packager stops being updated. The `--static-runtime` runtimes are built
1501
+ by scripts in this repository (`runtime/`) that you can run yourself;
1502
+ OCRAN checks a runtime against the SHA-256 recorded in its
1503
+ `runtime.json`.
1504
+
1505
+ **Exactly the gems and libraries you tested.** Dependencies are not
1506
+ resolved again at packaging time. OCRAN runs your script, records the
1507
+ files Ruby actually loaded, and packs those files from your installed
1508
+ gems — the versions pinned in your `Gemfile.lock` when you build under
1509
+ Bundler. Native libraries are taken from the same installation your
1510
+ extensions were loaded from. There is no second dependency resolution
1511
+ that could pick a different version than the one you ran.
1512
+
1513
+ **Reproducible builds.** The bundled interpreter and dependencies are
1514
+ determined by three things you control: the Ruby installation (or static
1515
+ runtime), the installed gems (or `Gemfile.lock`) and the OCRAN version;
1516
+ the rest of the artifact follows from your application source and the
1517
+ OCRAN options you pass. Pin them — e.g. a fixed `ruby-version` in
1518
+ `ruby/setup-ruby` plus a committed `Gemfile.lock` — and every build
1519
+ packages the same interpreter, gems and libraries. OCRAN's own stubs are
1520
+ built reproducibly (no timestamps in the PE header), the Windows, Linux
1521
+ and macOS static runtimes are reproducible, and `--static-runtime`
1522
+ executables are byte-reproducible on all three systems; CI packages a
1523
+ test application twice and requires the same bytes.
1524
+
1525
+ **Inspect what went in.** Build with `--output-dir` and look at the
1526
+ directory, or run an executable with `--debug-extract`. A
1527
+ `--static-runtime` executable is a plain ZIP archive behind the runtime:
1528
+ `unzip -l app.exe` lists exactly what will run, and
1529
+ `ocran/manifest.sha256` inside it records the SHA-256 of the runtime and
1530
+ of every file.
1531
+
1532
+ **Verifiable OCRAN releases.** The launcher stubs (`stub.exe`,
1533
+ `stubw.exe` and the Linux/macOS `stub`) are about 4,000 lines of C in
1534
+ `src/`, compiled from this repository by GitHub Actions. Released gems
1535
+ and the CI-built static runtimes carry signed build provenance
1536
+ attestations (`gh attestation verify <file> -R Largo/ocran`); from 1.5.1
1537
+ on, each GitHub release has the three runtimes attached next to the gems,
1538
+ and gems are pushed to rubygems.org with Sigstore attestations. The only
1539
+ third-party binary is `lzma.exe`, taken unmodified from the official
1540
+ 7-Zip release (see [Safety](#safety)).
1541
+
1542
+ The trade-off: keeping the bundled Ruby patched is your job, just as it
1543
+ is for the Ruby you develop with. The experimental `--cosmo-ruby` mode
1544
+ packages a cosmopolitan interpreter file you choose, such as a CosmoRuby
1545
+ release, as given.
1042
1546
 
1043
1547
  ## Credits
1044
1548
 
data/ext/extconf.rb CHANGED
@@ -2,7 +2,11 @@ gem_root = File.expand_path('..', __dir__)
2
2
  src_dir = File.join(gem_root, 'src')
3
3
  stub_dir = File.join(gem_root, 'share', 'ocran')
4
4
 
5
- if Gem.win_platform?
5
+ if RUBY_PLATFORM.include?('mswin')
6
+ # Visual C++ (needs a Visual Studio developer prompt, as any mswin gem build)
7
+ Dir.chdir(src_dir) { system('nmake', '-nologo', '-f', 'Makefile.mswin', 'install') } ||
8
+ abort('Build failed (run from a Visual Studio developer prompt / vcvars64.bat)')
9
+ elsif Gem.win_platform?
6
10
  system('ridk', 'exec', 'make', '-C', src_dir, 'install') ||
7
11
  system('make', '-C', src_dir, 'install') ||
8
12
  abort('Build failed')