ocran 1.4.6 → 1.5.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.txt +68 -0
- data/README.md +295 -10
- data/ext/extconf.rb +5 -1
- data/lib/ocran/aot_toolchain.rb +154 -0
- data/lib/ocran/command_output.rb +8 -1
- data/lib/ocran/compatibility_scan.rb +414 -0
- data/lib/ocran/cosmo_toolchain.rb +29 -7
- data/lib/ocran/dir_builder.rb +88 -38
- data/lib/ocran/direction.rb +190 -30
- data/lib/ocran/ed_resource.rb +113 -0
- data/lib/ocran/ed_resource_builder.rb +199 -0
- data/lib/ocran/file_path_set.rb +9 -5
- data/lib/ocran/gem_spec_queryable.rb +7 -3
- data/lib/ocran/host_config_helper.rb +11 -1
- data/lib/ocran/inno_setup_script_builder.rb +24 -14
- data/lib/ocran/launcher_batch_builder.rb +8 -11
- data/lib/ocran/npm_package.rb +106 -0
- data/lib/ocran/option.rb +425 -35
- data/lib/ocran/pe_imports.rb +96 -0
- data/lib/ocran/pe_resources.rb +226 -0
- data/lib/ocran/refine_pathname.rb +29 -32
- data/lib/ocran/roundhouse_builder.rb +316 -0
- data/lib/ocran/runner.rb +43 -11
- data/lib/ocran/runtime_sdk.rb +252 -0
- data/lib/ocran/spinel_builder.rb +183 -0
- data/lib/ocran/spinel_compatibility.rb +149 -0
- data/lib/ocran/static_runtime/boot.rb +443 -0
- data/lib/ocran/static_runtime/build_static_ext.rb +86 -0
- data/lib/ocran/static_runtime_builder.rb +814 -0
- data/lib/ocran/stub_builder.rb +70 -25
- data/lib/ocran/version.rb +1 -1
- data/lib/ocran/wasm_builder.rb +570 -0
- data/lib/ocran/wasm_compatibility.rb +198 -0
- data/lib/ocran/windows_command_escaping.rb +21 -0
- data/lib/ocran/zip_payload_builder.rb +18 -9
- data/lib/ocran/zip_writer.rb +48 -9
- data/src/Makefile +18 -2
- data/src/Makefile.mswin +65 -0
- data/src/inst_dir.c +34 -6
- data/src/path_utils.c +144 -0
- data/src/script_info.c +5 -0
- data/src/stub.c +77 -19
- data/src/stub.rc +1 -1
- data/src/system_utils.c +276 -155
- data/src/system_utils.h +46 -3
- data/src/system_utils_posix.c +243 -148
- data/src/unpack.c +9 -0
- metadata +22 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d437febbbe8332ec9c9361a7ab7cd7084d323d1afc92e81105ff73e45217d194
|
|
4
|
+
data.tar.gz: e4cc31077e27c6019037efaae65d552fcb72e3621a510773533629192f512dcb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a67462ca9e1187c8d83afa9c6665b166b0811680baf7a3c798f99cdd9fb7bcb2c3f8555f0acf88725e0fb803bc22c5fde24c40c14cf8142a62f1ffdba25ede1f
|
|
7
|
+
data.tar.gz: 35fcb3c1de9f04c1b21a7de1c9d68c395d9fb6ea8563f3a891c5008f499138f03b533bf3d2546e19a57c3ae22243a705c5d9d4990d2e4dcdd4663d1b9b90074f
|
data/CHANGELOG.txt
CHANGED
|
@@ -1,3 +1,71 @@
|
|
|
1
|
+
=== 1.5.0
|
|
2
|
+
- CI builds all three `--static-runtime` runtimes and tests them before publishing each as a workflow artifact: new static-runtime-windows.yml cross-builds the Windows runtime on Linux (Debian 13, in the official ruby image pinned by digest) and tests it on Windows (DLL imports, executables packaged on Linux run with a minimal PATH, packaging on Windows with nokogiri built by MSYS2's gcc). Each runtime is published as one reproducible archive (runtime/pack-runtime.rb; a zip for Windows, a .tar.gz for Linux and macOS), and a runtime that passed its tests gets a signed build provenance attestation for the archive and its runtime executable: `gh attestation verify <file> -R Largo/ocran`. runtime/smoke/run.rb runs executables with a minimal PATH and without Ruby's environment variables, and can run executables packaged elsewhere (OCRAN_SMOKE_PREBUILT=1).
|
|
3
|
+
- The release workflow attests every gem it publishes (signed build provenance): `gh attestation verify ocran-<version>-<platform>.gem -R Largo/ocran` checks a gem from the GitHub release or from rubygems.org (`gem fetch ocran`).
|
|
4
|
+
- Reproducible builds: the Windows stubs are linked without a build time in their PE header (-Wl,--no-insert-timestamp), so the same sources and toolchain give the same stub.exe, stubw.exe and platform gem; the source gem and the Linux stub already were. `--static-runtime` executables that link gem extensions are reproducible too: OCRAN runs the compiler, linker and strip with SOURCE_DATE_EPOCH set, which binutils' strip otherwise replaced with the current time in the PE header.
|
|
5
|
+
- `--static-runtime`: a runtime directory works after being moved or downloaded. The Linux SDK named the build host's kernel-header directory, which make-sdk.rb now copies into the SDK; the CI artifacts left out sdk/.ext (hidden), where Ruby's fake.rb finds ruby/config.h, so a downloaded runtime could not build gem extensions.
|
|
6
|
+
- `--static-runtime` builds gem extensions on a RubyInstaller host: RubyInstaller's RubyGems defaults, loaded when an extconf.rb requires rubygems (nokogiri's does), set up the build host's Ruby from the target's RbConfig and failed. Packaging nokogiri on Windows works.
|
|
7
|
+
- Error and warning messages come after the progress output that led to them when both go to one log: stdout is flushed first. They were printed before it, and a log excerpt could miss them.
|
|
8
|
+
- mswin Ruby support (x64-mswin64_140, the Ruby built with Visual C++ and vcpkg libraries, without MSYS2). The stubs build with Visual C++ through src/Makefile.mswin (nmake), which `rake build` and the source gem's extconf.rb use on mswin; they link the C runtime statically. The packed Ruby DLL of an mswin Ruby imports the Visual C++ runtime, which Windows does not ship, so OCRAN now bundles the VC++ runtime DLLs (vcruntime140*.dll, msvcp140*.dll, ...) the build process loaded into bin, next to ruby.exe; the executable runs where the VC++ Redistributable is not installed. The DLLs that ruby.exe, the Ruby DLL and the packed native extensions import from the Ruby bin directory (an mswin Ruby keeps gmp, libffi, OpenSSL, libyaml and zlib there) are read from their PE import tables (lib/ocran/pe_imports.rb) and packed even with --no-autodll; the interpreter does not start without gmp. Tested on Windows 11 with the official mswin build of Ruby 4.1.0dev and VS 2022 Build Tools, the executables run on a machine without the Redistributable.
|
|
9
|
+
- Windows: DLLs loaded through a junction, such as the "current" link of rbmanager or scoop, are resolved to their real path, so they are recognized as part of the Ruby installation that RbConfig describes.
|
|
10
|
+
- Windows stub: when ruby.exe dies in the loader (0xC0000135 DLL not found, 0xC0000139 entry point not found, 0xC000007B bad image, ...), the executable reports it instead of exiting silently with that status.
|
|
11
|
+
- Library paths of a development Ruby, whose version directory carries the ABI version (lib/ruby/4.1.0+5), are recognized; --add-all-core aborted with "Unexpected library path format" there.
|
|
12
|
+
- src/system_utils.c no longer includes ntstatus.h and ntdef.h, which redefine winnt.h types under the Windows SDK, and stub.rc quotes the icon file name, which rc.exe reads up to the hyphen.
|
|
13
|
+
- Linux/macOS: packaging works with a Ruby built without --enable-shared. Such a Ruby still reports LIBRUBY_SO (e.g. libruby.so.4.0.6) although only libruby-static.a is installed, and every build aborted with "The file does not exist (.../libruby.so.4.0.6)". When RbConfig's ENABLE_SHARED is "no" the interpreter is self-contained, and no libruby or libruby aliases are packed.
|
|
14
|
+
- `--static-runtime` supports macOS: runtime/macos/build-runtime.sh builds the runtime on a Mac with clang; it links only libraries of macOS itself (libSystem, libiconv, CoreFoundation). Since data appended to a Mach-O executable would break its code signature, OCRAN links every macOS executable at packaging time from the runtime's SDK with the application archive as the section __OCRAN,__zip, and the linker signs it ad hoc; `segedit app -extract __OCRAN __zip app.zip` gives the plain ZIP to audit. Packaging for macOS therefore needs a Mac. New CI workflows static-runtime-macos.yml and static-runtime-linux.yml build the macOS (arm64) and Linux runtimes and run runtime/smoke/ (a small application that checks file access, autoload, DATA, arguments, the exit status and that every loaded file comes from inside the executable, and HTTPS against the system trust store) with it.
|
|
15
|
+
- `--output` for a Windows `--static-runtime` executable gets .exe when the name has no extension, also when packaging on Linux or macOS.
|
|
16
|
+
- The ZIP directory entries OCRAN adds for parent directories (--static-runtime, --cosmo-ruby) carry the time of the file they belong to instead of the current time, so packing the same application twice gives the same bytes.
|
|
17
|
+
- `--static-runtime` (until now `--native`, renamed so that it is not confused with --spinel's native compilation; `--native-dll` is `--runtime-dll`, native/ is runtime/) supports Linux: runtime/linux/build-runtime.sh builds a fully static x86_64 Ruby linked against musl, which runs on any distribution (tested on Debian, Fedora, Alpine and BusyBox), and the executable's system follows from the runtime, not the build host. OpenSSL in the runtimes also loads the system's CA bundle on Linux and macOS (/etc/ssl/certs/ca-certificates.crt, /etc/pki/..., /etc/ssl/cert.pem), since a static OpenSSL knows only its compiled-in paths. nokogiri and tiny_tds work in the Linux runtime as well.
|
|
18
|
+
- New experimental `--static-runtime <runtime-dir>` option: build a single Windows executable that runs the application without extracting anything - no temporary directory, no DLLs written to disk. The output is a statically linked Ruby (a static runtime, built with runtime/build-runtime.sh on Linux or runtime/mswin/ with Visual Studio Build Tools) followed by the application as a plain ZIP archive, so `unzip -l app.exe` lists what will run and `ocran/manifest.sha256` inside it records the SHA-256 of the runtime and of every file. The runtime imports only DLLs that ship with Windows and needs no VC++ redistributable; ocran checks it against the hash in its runtime.json. The standard library and the gems the runtime ships are packed from the runtime, C extensions must be linked into it (anything else fails the build), and packing is byte-reproducible. Code: lib/ocran/static_runtime_builder.rb, lib/ocran/static_runtime/boot.rb (the read-only file layer the packed application runs on), runtime/.
|
|
19
|
+
- `--static-runtime` links the C extensions of the application's gems into the executable: an extension the runtime does not have is built from the gem's source against the runtime's SDK (`sdk/`, written by the runtime build) and linked into a runtime for this application, with MinGW (the Debian cross compiler on Linux, or RubyInstaller's MSYS2 GCC via OCRAN_RUNTIME_CC on Windows) or with Visual Studio Build Tools for an mswin runtime. Precompiled platform gems, which carry no source, are reported with a hint to install the source gem.
|
|
20
|
+
- `--static-runtime` runtimes (MinGW) carry static GNU libiconv, libxml2, libxslt and FreeTDS, so nokogiri and tiny_tds work: their C extensions are built against these libraries at packaging time. A precompiled platform gem (such as nokogiri's and tiny_tds' Linux builds) is built from the source gem of the same version, which OCRAN fetches from the configured gem sources into ~/.cache/ocran/gems (with the gems the source gem depends on that are not installed, such as mini_portile2 for nokogiri's extconf.rb, into ~/.cache/ocran/gems/build-gems, used only for building), and packed as a platform "ruby" gem without the binaries it carries for its platform. The runtime is Ruby 4.0.7.
|
|
21
|
+
- New `--runtime-dll <lib,...>` option: link C libraries of the runtime's SDK as DLLs shipped next to the executable instead of into it. The SDK has DLL builds of the LGPL libraries, GNU libiconv and FreeTDS (`--runtime-dll iconv,sybdb`), so that users can replace them as the LGPL asks; OCRAN copies each DLL and the SDK DLLs it imports.
|
|
22
|
+
- `--static-runtime` applies `--icon` and the version-resource options (`--set-version-string`, `--set-file-version`, `--set-product-version`, `--set-resource-string`, `--set-requested-execution-level`, `--application-manifest`) on every build host: lib/ocran/pe_resources.rb rewrites the resources of the runtime copy in Ruby, into a new section, instead of through the Windows-only UpdateResource API.
|
|
23
|
+
- `--static-runtime` executables contain the license texts of everything linked into the runtime as ocran/licenses/ inside the archive. runtime/LICENSES.md explains what static linking means for the licenses involved, the LGPL in particular.
|
|
24
|
+
- `--static-runtime`: OpenSSL in the runtime verifies certificates against the Windows certificate store (OpenSSL's winstore loader, added to OpenSSL::X509::Store#set_default_paths by a patch to Ruby's openssl extension), so HTTPS works without a CA file. The MinGW runtime's OpenSSL reads its configuration, certificates and engines from C:\Program Files\Common Files\SSL and C:\Program Files\OpenSSL instead of the build machine's prefix, which on Windows would have been a directory any user could create.
|
|
25
|
+
- ZipWriter writes a UTC entry time as given instead of converting it to the build host's local time, so a fixed time packs to the same bytes in every time zone. File times (local) are unchanged.
|
|
26
|
+
- Stub: OCRAN_DEBUG=0 (and an empty OCRAN_DEBUG) no longer switches debug output on; any other value still does. The CI workflow had set it to "0" to keep the stubs quiet and got every test's output interleaved with DEBUG lines.
|
|
27
|
+
- New experimental `--spinel` option: compile the script ahead of time with Spinel (https://github.com/matz/spinel) into a native executable that needs no Ruby, instead of packaging the interpreter. The script is not run at build time. Spinel compiles a subset of Ruby, so before compiling OCRAN scans the program statically with Prism: the script, the files it requires, and the source of every gem it requires. The scan supplies the `-I` roots Spinel needs for the project's lib/ and for pure-Ruby gems. When Spinel rejects the program, or is not installed, OCRAN prints a report grouped into the user's code and each gem. The report names gems with C extensions, standard libraries Spinel does not ship, unresolvable and computed requires, eval of strings, method_missing, computed define_method names, binding objects, ObjectSpace, TracePoint, refinements and Class.new. It also includes spinel-doctor's report when that tool is installed. `--spinel-opt <arg>` passes flags through to the compiler.
|
|
28
|
+
- New experimental `--roundhouse` option: compile a Rails application with Roundhouse (https://github.com/rubys/roundhouse) and Spinel into a native server binary. The output is a directory with the binary, its static/, public/, db/ and config/ files, and a storage/ directory that a rebuild keeps. On the first build the SQLite database is seeded when the sqlite3 command is available. When Roundhouse does not cover the app yet, OCRAN shows the gem census, the first errors and the unsupported-construct list from `roundhouse check --continue`. `--roundhouse-opt <arg>` passes flags through to Roundhouse.
|
|
29
|
+
- `--roundhouse` builds the generated project's static assets (`make assets`, with `BUNDLE_ONLY=assets` so only the Gemfile's assets group is installed) before `spin build`. Without them the binary served every stylesheet and script as a 404. It also puts the directory `pkg-config` reports for the allocator spin.toml names on LIBRARY_PATH for `spin build`, so Homebrew's jemalloc links on Apple Silicon, and a link failure now names the library it could not find.
|
|
30
|
+
- Both options find the compilers through SPINEL / SPIN / ROUNDHOUSE, then PATH, then the usual install locations, and print installation instructions for whichever is missing. Spinel does not support Windows natively yet, so on Windows the instructions point to WSL; nothing in OCRAN is tied to a platform, and the options will work on Windows once the compilers do.
|
|
31
|
+
- New experimental `--wasm[=ruby|picoruby]` option: export the application as a web page that runs it in the browser on WebAssembly. The result is a folder (`<script>-wasm`, or `--output-dir`) or a zip archive (`--output-zip`, or an `--output` name ending in .zip). It holds index.html, the runtime, and the program, and the page mirrors the program's standard output and errors. `--wasm` uses ruby.wasm (CRuby with its standard library). OCRAN packs the program into the prebuilt interpreter from npm with `rbwasm pack` (gem ruby_wasm, or RBWASM), which takes seconds; the pure-Ruby gems of the application's Gemfile.lock are packed in beside it at /gems with a setup file that stands in for bundler/setup. Only a gem with a C extension sends the build through `rbwasm build`, cached in ~/.cache/ocran/ruby_wasm. `--wasm-ruby <x.y>` picks the CRuby version and `--wasm-opt <arg>` passes flags to rbwasm. `--wasm=picoruby` (alias `--picoruby`) uses PicoRuby's 2 MB mruby-based runtime, which needs nothing installed; the program's own files are bundled into one script in require order, since PicoRuby cannot read them in the browser. The script is not run at build time. A static scan, shared with --spinel, reports what may not work on the chosen runtime: gems missing from the Gemfile or unavailable on PicoRuby, threads, processes, sockets, standard input, and CRuby standard libraries PicoRuby lacks. The runtimes come from the npm registry, are verified against its published sha512 integrity, and are cached in ~/.cache/ocran/npm. Zip archives are written with OCRAN's own ZipWriter, so no zip command is needed. The exports are tested against the real runtimes under Node (test/support/run_ruby_wasm.js, run_picoruby.js), in CI by the test-wasm job.
|
|
32
|
+
- Windows: new rcedit-style options set the PE resources of the generated executable and of the wrapper exe of --output-dir, --output-zip and --innosetup: `--set-version-string <key> <value>` (StringFileInfo entries such as CompanyName or ProductName), `--set-file-version` and `--set-product-version` (x.y.z.w), `--set-resource-string <id> <value>` (RT_STRING table), `--set-requested-execution-level` (manifest UAC level) and `--application-manifest <file>`, with the short aliases --version-string, --file-version, --product-version, --resource-string, --uac-level and --manifest. All are opt-in. The requested execution level replaces the level attribute of the manifest's requestedExecutionLevel element, also in a custom manifest, and leaves other level attributes and anything in comments or CDATA sections alone. docs/av-edr-false-positives.md explains how version information, an icon and code signing reduce antivirus and EDR false positives.
|
|
33
|
+
- Ruby 3.3 or newer is now required (was 3.2). Ruby 3.2 reached its end of life in March 2026 and was not tested in CI.
|
|
34
|
+
- A build that failed while writing an LZMA-compressed payload (the default) hung forever instead of reporting the error. The payload is written on a separate thread that feeds the compressor, and an exception there skipped closing the compressor's input, so the main thread waited for output that never came; the thread's exception was only printed. The compressor's input is now always closed and the error is raised in the main thread.
|
|
35
|
+
- Windows: the stub's cleanup of the extraction directory no longer follows junctions and directory symlinks. A link the application had created inside the directory (e.g. with --chdir-first) was recursed into, deleting the contents of the directory it pointed to outside the extraction directory; the link itself is now removed instead.
|
|
36
|
+
- Launch scripts from --output-dir/--output-zip pass build-time values literally. The .sh script embedded environment values and arguments in double quotes, so `$5` expanded to nothing and `$(cmd)` ran cmd at launch; they are now single-quoted. The .bat script set SCRIPT_DIR and the environment unquoted, so unpacking the application to a path containing `&` (e.g. C:\R&D\app) ran the rest of the path as a command; it now quotes every set, quotes each argument and doubles `%`. The Inno Setup launcher.bat doubles `%` as well, which cmd.exe otherwise read as the start of a variable reference.
|
|
37
|
+
- Stub: the parent directory of a path directly below the root is now the root ("/app" gives "/", "C:\app.exe" gives "C:\"), not an empty string or the drive-relative "C:". An executable at the root of the filesystem, common in containers, broke --chdir-exe-dir and the installer wrapper's run-in-exe-dir mode.
|
|
38
|
+
- `--gem-spec` works again. The gemspec lists its files relative to the gem directory, and they were used as given, so every build with the option failed with "Don't know where to put gemfile lib/...". The files are now taken from the gem directory, and listed files the installed gem does not have are skipped.
|
|
39
|
+
- A failed build no longer leaves a `.ocran_stub_<pid>_<time>` file in the output directory. The executable is written under that temporary name next to the output and renamed when complete; an error on the way left the partial file behind, and it is now deleted.
|
|
40
|
+
- `--cosmo-ruby` under `bundle exec`: the probe that asks the payload which features it can resolve itself now runs outside the build's bundle, like the payload query before it. It inherited BUNDLER_SETUP and BUNDLE_GEMFILE, so the payload died setting up the build host's bundle, the probe answered "none", and native gems the payload provides without a gemspec (cgi, pathname) were rejected as incompatible.
|
|
41
|
+
- `--output-zip` replaces an existing archive instead of adding to it. `zip -r` kept entries of the previous build that the new one no longer has, and Compress-Archive on Windows refused to overwrite the file. On POSIX the libruby.so aliases are stored as symlinks (`zip -y`) instead of three copies of the library, and zip no longer lists every file it adds. On Windows the paths reach PowerShell through the environment instead of inside a quoted string, which a `'` in the path (e.g. in the user name of the TEMP directory) broke.
|
|
42
|
+
- The launch script of `--output-dir`/`--output-zip` is named after the application's script even when RUBYOPT carries -I/-r paths that OCRAN translates. The script that runs is then the generated RUBYOPT launcher, and the launch script was named after it (e.g. `bundle exec ocran app.rb --output-dir out` wrote ocran-rubyopt-launcher.sh instead of app.sh).
|
|
43
|
+
- `--chdir-exe-dir` takes effect in the `--cosmo-ruby` ZIP packaging mode, where it was accepted and ignored: the application now starts in the directory containing the executable, as with `--chdir-first` in that mode.
|
|
44
|
+
- The launch scripts of `--output-dir`/`--output-zip` honor `--chdir-first` (change into the directory of the packed script) and `--chdir-exe-dir` (change into the script's own directory, where the application lives), like the executable and the wrapper executable next to them already did; both options used to be ignored there. The .bat script now starts with `setlocal`, so neither its variables nor the new directory change outlive it in the cmd.exe session that ran it.
|
|
45
|
+
- Invalid and conflicting command lines are rejected when they are parsed, before the dependency run, instead of being silently accepted: `--innosetup` with `--output-dir` or `--output-zip` (the directory or zip was not built), `--windows` with `--console` (console won), an unknown `--gem-<group>` (reported only after the whole dependency run) or a negated mode such as `--no-gem-full` (which acted as `--gem-full`), an option given without its argument (`--output`, `--output-dir`, `--output-zip`, `--bundle-id`, `--dll`, `--rubyopt`, `--cosmo`, ...; they were ignored, or reported as "file not found"), and an unknown option, which printed the usage and exited with status 0.
|
|
46
|
+
- `--no-dep-run` warns when nothing else names what the script needs: without `--add-all-core` no standard library is packed, and with `--gemfile` but without `--gem-all`/`--gem-full` the gems are packed without their scripts. README described both options as required, which they are not (a script that loads nothing packages fine), while `--help` did not mention them at all; both now say when they are needed.
|
|
47
|
+
- Windows: packed paths are deduplicated case- and separator-insensitively, as the file system treats them. The case-insensitive Pathname comparison OCRAN defines is a refinement, and refinements do not reach Hash, Set or Array#uniq, so the set of packed files, the gem file list and the check that keeps gem files from being packed a second time as plain features all compared paths case-sensitively; one file reached under two spellings could be packed twice. Behavior on Linux and macOS is unchanged.
|
|
48
|
+
- `--innosetup`: a `{` in a packed path is written as `{{` in the generated [Dirs] Name and [Files] DestDir/DestName entries, where Inno Setup otherwise reads it as the start of a constant such as {app}. A file whose name contains a brace always gets an explicit, escaped DestName.
|
|
49
|
+
- `--cosmo-ruby` ZIP packaging no longer mistakes an interpreter for a ZIP64 archive because the four bytes of the ZIP64 locator signature occur somewhere in its last 64 KiB; only a locator at its fixed position right before the end-of-central-directory record counts.
|
|
50
|
+
- `--cosmo-ruby` ZIP packaging checks the limits of the ZIP format without ZIP64 (65535 entries, 4 GiB) before it writes anything, instead of after the application had already been written into the executable, and refuses such an application right away.
|
|
51
|
+
- `--cosmo`: a compiled stub enters the cache in ~/.cache/ocran by an atomic rename. It was copied into place, so a build running at the same time, or any build after one that was interrupted mid-copy, could package a truncated stub.
|
|
52
|
+
- `--macosx-bundle`: the application name and `--bundle-id` are XML-escaped in the generated Info.plist. A name such as "R&D" produced a property list macOS could not parse.
|
|
53
|
+
- Build errors caused by the input or the build host - an ArgumentError (e.g. an empty argument after `--`, a file too large for the executable format) or a system call error (e.g. an output directory that cannot be created) - are reported as a one-line `ERROR:` message like other build errors, instead of an OCRAN backtrace. `--verbose` adds the backtrace; other exceptions, which indicate a bug, keep Ruby's full report.
|
|
54
|
+
- Stub: the path helpers IsCleanRelativePath, JoinPath and GetParentPath now live once in src/path_utils.c, built into the Windows, POSIX and cosmocc stubs alike. system_utils.c and system_utils_posix.c each carried a copy (the POSIX one with a dead `#ifdef _WIN32` branch), so a fix to one, like the root handling of GetParentPath, had to be made twice to reach every platform.
|
|
55
|
+
- `--cosmo` stubs running on Windows refuse payload names containing a backslash. The cosmopolitan stub checked names for "." and ".." segments splitting at "/" only, but Windows splits the resulting path at "\" as well, so a name like `..\x` passed as one harmless segment and was then written outside the extraction directory. OCRAN never puts a backslash in a name (cosmo stubs are only built on Linux and macOS hosts), so only a tampered executable is affected.
|
|
56
|
+
- Linux/macOS: stopping a packaged application with a signal now stops it and still deletes its extraction directory. The stub ignored SIGINT and SIGTERM for its whole life without passing them on, so `kill`, `docker stop` or a service manager stopping the executable did nothing to the application until the SIGKILL that followed also skipped the cleanup, leaving the extracted application behind in the temp directory; SIGHUP, from closing the terminal, killed the stub before its cleanup. The stub now forwards SIGINT, SIGTERM, SIGHUP and SIGQUIT sent to it to the application (signals a terminal generates reach the application directly and are not sent twice), waits for it, deletes the extraction directory and then dies of the same signal the application died of, so callers see a signal death as before rather than an exit code. As PID 1 of a container, where that is not possible, it exits with 128 + the signal number. Signals that were ignored when the executable started (e.g. SIGHUP under nohup) stay ignored for the application too; the stub used to reset SIGINT and SIGTERM to their default for it. On macOS the kernel does not say whether a signal came from a process or from the terminal, so there the stub forwards a signal when it is not in the foreground process group of a controlling terminal (a service, a container, a background job, CI); in an interactive foreground run only the terminal's own Ctrl+C and hangup, which reach the application directly, stop it.
|
|
57
|
+
- Windows: closing the console window of a running packaged application, or shutting down the system, no longer leaves its extraction directory behind. The stub's console control handler returned TRUE for every event, which for these events lets Windows terminate the stub right away, before its cleanup. The handler now waits (for at most 2.5 seconds, well within the 5 seconds Windows grants) for the application, which received the same event, ends it if it is still running, deletes the extraction directory and only then returns; if the event arrives while the stub is still extracting, the application is not started at all. Ctrl+C and Ctrl+Break are still left to the application, and a logoff event, which only services receive, whenever any user logs off, still does not stop anything. The --innosetup wrapper's directory (RUN_IN_EXE_DIR) is never deleted, from the handler either.
|
|
58
|
+
- Windows: killing a packaged application's executable (Task Manager, `taskkill /F`, a service manager or CI runner ending it) now ends the application too. The stub started ruby.exe as an independent process, which kept running without the stub, orphaned and with its extraction directory in use. The stub now puts ruby.exe into a job object that kills it when the stub goes away. Processes the application starts itself are not in that job and are not affected. When the stub already runs in a job that does not allow this, the application starts as before.
|
|
59
|
+
- Windows: read-only files and directories in the extraction directory, e.g. ones the application copied from a read-only source or a git checkout's object files, no longer stay behind after the application exits. DeleteFileW and RemoveDirectoryW refuse read-only entries, and the fallback of scheduling them for deletion at reboot needs administrator rights, so they, and every directory above them, were left in the temp directory. The stub now clears the read-only attribute first (never on links, whose attributes may be their target's), and reports a failure of anything below the directory instead of only of the directory itself.
|
|
60
|
+
- Linux/macOS: the stub's cleanup no longer gives up at the first entry of the extraction directory it cannot delete. It stopped deleting as soon as one entry failed, leaving everything not yet reached behind as well, and it could not enter directories the application had made read-only or unreadable (e.g. a copied Go module cache), so one such directory kept the whole extracted application in the temp directory. The stub now restores the owner's permissions on such directories, carries on past entries that still fail, and reports cleanup failures only in debug mode (they were printed as FATAL errors after the application had already run), as on Windows.
|
|
61
|
+
- Stub: when normalizing the new extraction directory's path to its long form fails (out of memory), the stub keeps the path it has instead of forgetting the directory. It had already been created, but its path was dropped, so nothing deleted it and it stayed in the temp directory.
|
|
62
|
+
- Linux/macOS: the stub only creates payload symlinks that point at a file in the same directory, and never writes an extracted file through a symlink. It created a link to whatever target the payload named, absolute or climbing out with "..", and then followed such links while creating directories and files, so a tampered executable could have files written anywhere its user can write. OCRAN itself only emits links between files in bin (shared library aliases such as libruby.so -> libruby.so.3.4.1), which keep working.
|
|
63
|
+
- Stub: an LZMA-compressed payload must now decompress to exactly the size its header states. The stub allocated the stated size and parsed all of it as opcodes, whatever the stream actually produced, so an executable whose stream ended early had uninitialized memory interpreted as extraction instructions. Such an executable now fails to start with "LZMA decompressed size mismatch" in the debug output. Executables built by OCRAN always match.
|
|
64
|
+
- Windows: the stub's directory creation can no longer loop forever. Looking for the deepest existing parent of a new directory, it cut the path back one segment at a time in a loop whose condition could never become false, so a path without any existing ancestor (a missing drive, or a relative path) made the executable hang with the CPU busy instead of failing; had it ended, it would also have replaced the first character of the path with a backslash. The walk now stops at the root and creates the first missing directory itself. A directory that already exists by the time it is created (another process was quicker, or a trailing separator) no longer counts as a failure either.
|
|
65
|
+
- Windows: the stub no longer takes a TEMP directory path too long for its 260-character buffer for a valid one. GetTempPathW then returns the size it would need and leaves the buffer undefined, and the stub went on with whatever the buffer held as the temp directory. It now asks for the required size first and fetches the whole path.
|
|
66
|
+
- Stub: running out of memory while preparing the application's command line now fails with an error instead of crashing on a NULL pointer, which also skipped deleting the extraction directory.
|
|
67
|
+
- Stub build: editing a header in src/ now rebuilds the objects that include it. The Makefile listed no header dependencies, so after a change to e.g. unpack.h an incremental `make` (or `rake build`) linked stale objects against new ones, which could produce a stub that disagreed with itself about structure layouts or opcode values until `make clean`. Every object now depends on the stub's headers (and the LZMA headers); explicit prerequisites rather than compiler-generated dependency files, which works the same with gcc, mingw and cosmocc.
|
|
68
|
+
|
|
1
69
|
=== 1.4.6
|
|
2
70
|
- Applications no longer carry gems that only OCRAN itself activated. The gem scan read Gem.loaded_specs while the build was already running instead of the snapshot taken after the dependency run, so whatever OCRAN had activated by then was packed as if the application had loaded it: OCRAN's own gem and its fiddle dependency, activated by the `ocran` command of an installed OCRAN (since OCRA 1.3.2, which started packing every gem in Gem.loaded_specs; about 390 KB of stubs, lzma.exe and sources per application), and since 1.3.16, whose refactoring moved the build ahead of the gem scan, also tempfile, tmpdir, fileutils, delegate and etc from the stub builder and fiddle from DLL detection. The scan now uses the snapshot and leaves out the running OCRAN's gem and its runtime dependencies. Only activation is discounted: a gem the application does load is still packed. The same snapshot decides which gems' SxS assembly manifests are included on Windows.
|
|
3
71
|
- Gems that Bundler installed into a configured path are found without Bundler (issue #61). ruby/setup-ruby with `bundler-cache: true` - the README's GitHub Actions example - installs the Gemfile's gems into vendor/bundle, a place plain RubyGems never looks, so `ocran app.rb` died with a LoadError on the first gem during the dependency run, `--gemfile` included. Before loading the script OCRAN now asks Bundler, in a child process and without evaluating the Gemfile, where the application's bundle is installed, and adds that directory to the gem path when it is not the system gems. The bundle is not set up, so gems the Gemfile does not list stay loadable as before; a bundle installed into the system gems and `bundle exec` are unaffected.
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OCRAN
|
|
2
2
|
|
|
3
|
-

|
|
3
|
+

|
|
4
4
|
|
|
5
5
|
[OCRAN (One-Click Ruby Application Next)](https://github.com/largo/ocran) packages Ruby applications for
|
|
6
6
|
distribution. It bundles your script, the Ruby interpreter, gems, and native
|
|
@@ -15,7 +15,7 @@ OCRAN supports four output formats, all cross-platform:
|
|
|
15
15
|
* **Zip archive** (`--output-zip`) — same as directory output, packed into a `.zip`.
|
|
16
16
|
|
|
17
17
|
OCRAN is a fork of [OCRA](https://github.com/larsch/ocra) maintained for
|
|
18
|
-
Ruby 3.
|
|
18
|
+
Ruby 3.3+ compatibility.
|
|
19
19
|
|
|
20
20
|
If you run into errors while using OCRAN, please check the [OCRAN issues](https://github.com/largo/ocran/issues) first.
|
|
21
21
|
|
|
@@ -76,7 +76,7 @@ This repository includes `lzma.exe` from the
|
|
|
76
76
|
[official ip7z/7zip release](https://github.com/ip7z/7zip/releases)
|
|
77
77
|
(version 22.01, from `lzma2201.7z`), used to compress Windows executables.
|
|
78
78
|
|
|
79
|
-
`stub.exe`, `stubw.exe
|
|
79
|
+
`stub.exe`, `stubw.exe` and the Linux/macOS `stub` are compiled from source in this
|
|
80
80
|
repository.
|
|
81
81
|
|
|
82
82
|
## Installation
|
|
@@ -153,7 +153,7 @@ These options control which files from included gems are added to the output.
|
|
|
153
153
|
* `--gem-guess[=gem1,..]`: Include loaded scripts and a best guess of other needed files (DEFAULT).
|
|
154
154
|
* `--gem-all[=gem1,..]`: Include all scripts and important files from the gem.
|
|
155
155
|
* `--gem-full[=gem1,..]`: Include every file in the gem directory.
|
|
156
|
-
* `--gem-spec[=gem1,..]`: Include files listed in the gemspec
|
|
156
|
+
* `--gem-spec[=gem1,..]`: Include the files listed in the gemspec.
|
|
157
157
|
|
|
158
158
|
Fine-tuning flags:
|
|
159
159
|
|
|
@@ -163,7 +163,7 @@ Fine-tuning flags:
|
|
|
163
163
|
|
|
164
164
|
#### Auto-detection options:
|
|
165
165
|
|
|
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.
|
|
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. 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
167
|
* `--no-autoload`: Do not attempt to load `autoload`ed constants.
|
|
168
168
|
* `--no-autodll`: Disable automatic detection of runtime DLL dependencies.
|
|
169
169
|
|
|
@@ -188,8 +188,125 @@ Fine-tuning flags:
|
|
|
188
188
|
* `--debug`: Enable verbose output when the generated executable runs.
|
|
189
189
|
* `--debug-extract`: Unpack to a local directory and do not delete after execution (useful for troubleshooting).
|
|
190
190
|
|
|
191
|
+
#### Windows resource options:
|
|
192
|
+
|
|
193
|
+
These set the PE resources of the generated executable, with the same names
|
|
194
|
+
as [rcedit](https://github.com/electron/rcedit). They apply to the
|
|
195
|
+
self-extracting `.exe` and to the wrapper `.exe` of `--output-dir`,
|
|
196
|
+
`--output-zip` and `--innosetup`. All are opt-in: without them the
|
|
197
|
+
executable is built as before. (Windows only)
|
|
198
|
+
|
|
199
|
+
* `--set-version-string <key> <value>` (alias `--version-string`): Add a `StringFileInfo` entry such as `CompanyName`, `ProductName`, `FileDescription` or `LegalCopyright`. Repeat for each key.
|
|
200
|
+
* `--set-file-version <x.y.z.w>` (alias `--file-version`): Set the numeric and string file version.
|
|
201
|
+
* `--set-product-version <x.y.z.w>` (alias `--product-version`): Set the numeric and string product version.
|
|
202
|
+
* `--set-resource-string <id> <value>` (alias `--resource-string`): Set an entry of the `RT_STRING` table by its numeric id.
|
|
203
|
+
* `--set-requested-execution-level <level>` (alias `--uac-level`): Set the UAC level in the manifest: `asInvoker` (the default), `highestAvailable` or `requireAdministrator`.
|
|
204
|
+
* `--application-manifest <file>` (alias `--manifest`): Replace the built-in application manifest with the given file. `--set-requested-execution-level` still applies to it.
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
ocran app.rb --set-version-string CompanyName "Example Ltd" \
|
|
208
|
+
--set-version-string ProductName "Example App" \
|
|
209
|
+
--set-version-string FileDescription "Example App" \
|
|
210
|
+
--set-file-version 1.2.0.0 --set-product-version 1.2.0.0
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
An unsigned executable with no version information, which extracts files
|
|
214
|
+
to `%TEMP%` and runs them, is what antivirus and EDR heuristics expect of a
|
|
215
|
+
malware packer. Version information, an icon and a code signature give it
|
|
216
|
+
an identity instead. [docs/av-edr-false-positives.md](docs/av-edr-false-positives.md)
|
|
217
|
+
goes through these steps in order of impact.
|
|
218
|
+
|
|
191
219
|
#### Experimental options:
|
|
192
220
|
|
|
221
|
+
* `--static-runtime <runtime-dir>`: Build a single executable for Windows,
|
|
222
|
+
Linux or macOS that runs the application **without extracting anything**:
|
|
223
|
+
no temporary directory, no DLLs or shared libraries written to disk. The
|
|
224
|
+
executable is a statically linked Ruby (the *static runtime*) with the
|
|
225
|
+
application as a plain ZIP archive: appended on Windows and Linux, in a
|
|
226
|
+
section of its own on macOS (`__OCRAN,__zip`), where appended data would
|
|
227
|
+
break the code signature. The Linux runtime is linked against musl and
|
|
228
|
+
fully static, so it runs on any x86_64 distribution.
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
ocran app.rb --static-runtime path/to/runtime # app.exe (Windows) or app (Linux, macOS)
|
|
232
|
+
unzip -l app.exe # lists exactly what will run (Linux: app)
|
|
233
|
+
unzip -p app.exe ocran/manifest.sha256 # SHA-256 of the runtime and every file
|
|
234
|
+
|
|
235
|
+
segedit app -extract __OCRAN __zip app.zip # macOS: the archive is a section
|
|
236
|
+
unzip -l app.zip
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The runtime directory holds `ocran-runtime(.exe)`, its own standard
|
|
240
|
+
library and bundled gems (`lib/ruby/`) and `runtime.json`; build one with
|
|
241
|
+
`runtime/build-runtime.sh` (Windows, MinGW, on Linux), the scripts in
|
|
242
|
+
`runtime/mswin/` (Windows, Visual Studio Build Tools) or
|
|
243
|
+
`runtime/linux/build-runtime.sh` (Linux x86_64, musl) or
|
|
244
|
+
`runtime/macos/build-runtime.sh` (macOS, on a Mac). The Windows
|
|
245
|
+
runtimes import only DLLs that ship with Windows and need no VC++
|
|
246
|
+
redistributable; the Linux runtime imports nothing at all; the macOS
|
|
247
|
+
runtime links only libraries of macOS itself (libSystem, libiconv,
|
|
248
|
+
CoreFoundation). On macOS every executable is linked at packaging time,
|
|
249
|
+
with its archive, so packaging for macOS needs a Mac with clang (Xcode or
|
|
250
|
+
the Command Line Tools); the linker signs the result ad hoc, and it can
|
|
251
|
+
be signed again with `codesign`. Which system
|
|
252
|
+
the executable is for follows from the runtime, not from the build host:
|
|
253
|
+
a Linux build host can package for Windows and the other way round, as
|
|
254
|
+
long as the application does not depend on platform-specific gems. See
|
|
255
|
+
[runtime/README.md](runtime/README.md) for how it works and how it is
|
|
256
|
+
built. CI builds and tests the three runtimes and publishes each as a
|
|
257
|
+
reproducible archive (a zip for Windows, a tarball otherwise) with a
|
|
258
|
+
signed build provenance attestation
|
|
259
|
+
(`gh attestation verify <archive> -R Largo/ocran`).
|
|
260
|
+
|
|
261
|
+
Dependency detection runs as usual, under the build host's Ruby, which
|
|
262
|
+
must have the runtime's API version (e.g. 4.0.0). The standard library
|
|
263
|
+
and the gems the runtime ships come from the runtime, so that they match
|
|
264
|
+
its interpreter; OCRAN says when one replaces a different version on the
|
|
265
|
+
build host. C extensions are linked into the executable, since nothing
|
|
266
|
+
can be loaded from inside it: those the runtime has (openssl, psych,
|
|
267
|
+
zlib, fiddle, win32ole, bigdecimal, ...) are used as they are; any other
|
|
268
|
+
gem's extension is built from the gem's source against the runtime's
|
|
269
|
+
SDK (`sdk/` in the runtime directory) and linked into a runtime of the
|
|
270
|
+
application's own. That needs a compiler for the runtime: for a MinGW
|
|
271
|
+
runtime `x86_64-w64-mingw32ucrt-gcc` on Linux, or on Windows
|
|
272
|
+
RubyInstaller's MSYS2 GCC named with `OCRAN_RUNTIME_CC=gcc` (with
|
|
273
|
+
`ucrt64\bin` and `usr\bin` on PATH); for an mswin runtime `cl` and
|
|
274
|
+
`nmake` of Visual Studio Build Tools, from a developer prompt; for a
|
|
275
|
+
Linux runtime `musl-gcc`; for macOS clang. Gems that
|
|
276
|
+
need a C library get the one the SDK provides: nokogiri uses its
|
|
277
|
+
libxml2/libxslt, tiny_tds its FreeTDS. A precompiled platform gem is
|
|
278
|
+
built from its source gem of the same version, which OCRAN fetches from
|
|
279
|
+
the configured gem sources into `~/.cache/ocran/gems`, together with the
|
|
280
|
+
gems its build needs that are not installed (nokogiri's extconf.rb needs
|
|
281
|
+
mini_portile2), installed there for the build only. Failing that, it is
|
|
282
|
+
built from the C sources it carries, if any (`gem install <gem>
|
|
283
|
+
--platform ruby` installs the source gem).
|
|
284
|
+
|
|
285
|
+
**Licenses:** the executable is statically linked - Ruby, OpenSSL and
|
|
286
|
+
the other libraries are inside it, and their license texts are packed
|
|
287
|
+
into it as `ocran/licenses/`. Two optional libraries are under the LGPL
|
|
288
|
+
(GNU libiconv, FreeTDS: used by nokogiri and tiny_tds), which asks that
|
|
289
|
+
users can replace them; `--runtime-dll iconv,sybdb` ships them as DLLs
|
|
290
|
+
next to the executable instead. Read
|
|
291
|
+
[runtime/LICENSES.md](runtime/LICENSES.md) before distributing.
|
|
292
|
+
|
|
293
|
+
OpenSSL verifies certificates against the Windows certificate store, as
|
|
294
|
+
Windows itself does, and on Linux and macOS against the system's CA
|
|
295
|
+
bundle - no CA file is packed or needed. The application reads its own files as
|
|
296
|
+
usual (`require`, `require_relative`, `autoload`, `File.read`,
|
|
297
|
+
`Dir.glob`, `__dir__`, `DATA`); their paths lie inside the executable
|
|
298
|
+
(`C:/app/tool.exe/src/tool.rb`) and cannot be written. Files next to the
|
|
299
|
+
executable are found through `ENV["OCRAN_EXECUTABLE"]`. `--windows` makes
|
|
300
|
+
a GUI-subsystem executable; `--icon` and the version-resource options
|
|
301
|
+
work as for the stub, on any build host (OCRAN writes the resources in
|
|
302
|
+
Ruby). `--dll` and the output-directory formats do not apply.
|
|
303
|
+
Experimental.
|
|
304
|
+
|
|
305
|
+
* `--runtime-dll <lib,...>`: With `--static-runtime`, link these C libraries of the
|
|
306
|
+
runtime's SDK as DLLs shipped next to the executable rather than into it
|
|
307
|
+
(the SDK has DLL builds of `iconv` and `sybdb`, the LGPL libraries).
|
|
308
|
+
OCRAN copies them, and the DLLs they need, next to the executable.
|
|
309
|
+
|
|
193
310
|
* `--cosmo-ruby <ruby.com>`: Package a cosmopolitan-built Ruby (an APE)
|
|
194
311
|
as the bundled interpreter instead of the host Ruby. The produced
|
|
195
312
|
`.com` contains no host-native code at all and runs on Linux, Windows
|
|
@@ -256,7 +373,8 @@ Fine-tuning flags:
|
|
|
256
373
|
`__dir__` for that — it never meant "next to the exe" in either
|
|
257
374
|
mode, but here it is obviously wrong instead of subtly wrong.
|
|
258
375
|
* `--chdir-first` changes into the directory containing the
|
|
259
|
-
executable, since the application directory does not exist on disk
|
|
376
|
+
executable, since the application directory does not exist on disk;
|
|
377
|
+
`--chdir-exe-dir` does the same.
|
|
260
378
|
* The whole command line reaches `ARGV`, unchanged. The interpreter
|
|
261
379
|
claims none of it, so `app.com --version`, `app.com -v` and
|
|
262
380
|
`app.com -- x` behave exactly as they would for a natively compiled
|
|
@@ -345,6 +463,147 @@ Fine-tuning flags:
|
|
|
345
463
|
launcher stub, not to the bundled application. See
|
|
346
464
|
`docs/cosmocc-port-plan.md` for status.
|
|
347
465
|
|
|
466
|
+
#### Native compilation (experimental):
|
|
467
|
+
|
|
468
|
+
Instead of packaging the interpreter, these options compile the program
|
|
469
|
+
ahead of time with [Spinel](https://github.com/matz/spinel), Matz's Ruby
|
|
470
|
+
AOT compiler. The result is a native executable that needs no Ruby at all.
|
|
471
|
+
Spinel compiles a *subset* of Ruby (no `eval` of strings, no
|
|
472
|
+
`method_missing`, no runtime class building, only the standard library it
|
|
473
|
+
ships itself), so this works for some programs and not others. When it
|
|
474
|
+
does not, OCRAN tells you why.
|
|
475
|
+
|
|
476
|
+
* `--spinel`: Compile `script.rb` with Spinel:
|
|
477
|
+
|
|
478
|
+
```
|
|
479
|
+
ocran app.rb --spinel # -> ./app (app.exe on Windows)
|
|
480
|
+
ocran app.rb --spinel --output myapp
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
The script is not run at build time. OCRAN scans the program statically
|
|
484
|
+
(the script, the files it requires, and the source of every gem it
|
|
485
|
+
requires) and passes Spinel the `-I` roots it needs for your `lib/` and
|
|
486
|
+
for pure-Ruby gems. If Spinel cannot compile the program, OCRAN prints
|
|
487
|
+
the compiler's error, then a report of what stands in the way, grouped
|
|
488
|
+
into your code and each gem. Gems with C extensions and standard
|
|
489
|
+
libraries Spinel does not provide are named as incompatible. If
|
|
490
|
+
`spinel-doctor` is installed next to the compiler, its report is shown
|
|
491
|
+
too. If Spinel is not installed, OCRAN explains how to install it and
|
|
492
|
+
still shows the report, so you can see whether installing it is worth
|
|
493
|
+
it. Use `--spinel-opt <arg>` (repeatable) to pass flags to the compiler,
|
|
494
|
+
for example `--spinel-opt --int-overflow=promote` or
|
|
495
|
+
`--spinel-opt --defer-refusals`. With `--debug` the binary is built
|
|
496
|
+
with `-g`. Only code is compiled in; data files have to ship next to
|
|
497
|
+
the executable.
|
|
498
|
+
|
|
499
|
+
* `--roundhouse`: Compile a **Rails application** into a native server
|
|
500
|
+
binary with [Roundhouse](https://github.com/rubys/roundhouse), which
|
|
501
|
+
lowers the app to the Ruby subset Spinel compiles. OCRAN runs the
|
|
502
|
+
transpile, the generated project's asset step (`make assets`, with
|
|
503
|
+
only the Gemfile's assets group bundled) and `spin build`:
|
|
504
|
+
|
|
505
|
+
```
|
|
506
|
+
ocran --roundhouse path/to/rails/app # -> ./app-spinel/
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
The output is a directory (`<app>-spinel` by default, or `--output` /
|
|
510
|
+
`--output-dir`) that contains the binary plus the `static/`, `public/`,
|
|
511
|
+
`db/` and `config/` files it reads at run time, and a `storage/` for
|
|
512
|
+
the SQLite database. On the first build the database is created from
|
|
513
|
+
`db/seed.sql` when the `sqlite3` command is available. Rebuilding keeps
|
|
514
|
+
`storage/`. If Roundhouse does not cover the app yet, OCRAN shows the
|
|
515
|
+
useful parts of `roundhouse check --continue`: the gem census (which of
|
|
516
|
+
the app's gems Roundhouse does not model), the first errors, and the
|
|
517
|
+
list of unsupported constructs. Pass flags to Roundhouse with
|
|
518
|
+
`--roundhouse-opt <arg>`, for example
|
|
519
|
+
`--roundhouse-opt --survey --roundhouse-opt --allow-unsupported`.
|
|
520
|
+
|
|
521
|
+
**Installing the compilers.** Neither is a gem. OCRAN looks for `spinel`,
|
|
522
|
+
`spin` and `roundhouse` in the `SPINEL`, `SPIN` and `ROUNDHOUSE`
|
|
523
|
+
environment variables (the command or the directory that contains it), then
|
|
524
|
+
in `PATH`, then in their usual install locations (`~/.local/bin`,
|
|
525
|
+
`/usr/local/bin`, `~/.cargo/bin`). When one is missing, OCRAN prints how to
|
|
526
|
+
install it:
|
|
527
|
+
|
|
528
|
+
```
|
|
529
|
+
git clone https://github.com/matz/spinel && cd spinel
|
|
530
|
+
make deps && make && make install PREFIX=$HOME/.local
|
|
531
|
+
|
|
532
|
+
curl --proto '=https' --tlsv1.2 -LsSf \
|
|
533
|
+
https://github.com/rubys/roundhouse/releases/latest/download/roundhouse-installer.sh | sh
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Apps compiled with Roundhouse also need the SQLite and jemalloc
|
|
537
|
+
development headers (`libsqlite3-dev libjemalloc-dev`, or
|
|
538
|
+
`brew install sqlite jemalloc`). OCRAN puts the directory `pkg-config`
|
|
539
|
+
reports for the allocator on `LIBRARY_PATH` for the build, so a Homebrew
|
|
540
|
+
jemalloc on Apple Silicon (under `/opt/homebrew/lib`, which the linker does
|
|
541
|
+
not search by default) links. The asset step needs Bundler, and Node.js
|
|
542
|
+
with npm when the app builds Tailwind.
|
|
543
|
+
|
|
544
|
+
**Windows.** Spinel does not build natively on Windows yet, and
|
|
545
|
+
Roundhouse's Windows build is untested. For now, run OCRAN inside WSL,
|
|
546
|
+
where both work as they do on Linux; you get a Linux binary. Nothing in
|
|
547
|
+
OCRAN is tied to a platform here: once the compilers run on Windows,
|
|
548
|
+
`--spinel` and `--roundhouse` work there as they are and produce `.exe`
|
|
549
|
+
files.
|
|
550
|
+
|
|
551
|
+
#### WebAssembly export (experimental):
|
|
552
|
+
|
|
553
|
+
`--wasm` exports the application as a web page that runs it in the browser.
|
|
554
|
+
The output is a folder (`<scriptname>-wasm` by default, or `--output-dir`)
|
|
555
|
+
or a zip archive (`--output-zip`, or an `--output` name that ends in
|
|
556
|
+
`.zip`). It contains `index.html`, the runtime and your program. The page
|
|
557
|
+
shows the program's standard output and errors. Use the `js` library to
|
|
558
|
+
work with the page itself. Browsers only load WebAssembly over HTTP, so
|
|
559
|
+
serve the folder instead of opening the file:
|
|
560
|
+
|
|
561
|
+
```
|
|
562
|
+
ocran app.rb --wasm # -> app-wasm/
|
|
563
|
+
ocran app.rb --wasm=picoruby --output app.zip
|
|
564
|
+
python3 -m http.server -d app-wasm 8000 # then open http://localhost:8000
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
There are two runtimes:
|
|
568
|
+
|
|
569
|
+
* `--wasm` (or `--wasm=ruby`): [ruby.wasm](https://github.com/ruby/ruby.wasm),
|
|
570
|
+
CRuby compiled to WebAssembly, with its whole standard library (about
|
|
571
|
+
30 MB). It needs `rbwasm` from the `ruby_wasm` gem (`gem install ruby_wasm`;
|
|
572
|
+
OCRAN also uses `RBWASM` when it is set).
|
|
573
|
+
* OCRAN packs your files into the prebuilt interpreter from npm, which
|
|
574
|
+
takes seconds. Gems must be listed in the application's Gemfile and
|
|
575
|
+
installed (`bundle install`); pure-Ruby gems are packed in beside your
|
|
576
|
+
files, at `/gems`, and put on the load path in place of
|
|
577
|
+
`bundler/setup`, so `require "bundler/setup"` in your program is fine.
|
|
578
|
+
* Only when a gem has a C extension does OCRAN run `rbwasm build`. Its
|
|
579
|
+
first run compiles CRuby and the gems for WebAssembly, which takes
|
|
580
|
+
several minutes, and is cached in `~/.cache/ocran/ruby_wasm`. The
|
|
581
|
+
extensions are cross-compiled for WASI, and the ones that link system
|
|
582
|
+
libraries usually fail.
|
|
583
|
+
* The Ruby version follows the Ruby you run OCRAN with. Use
|
|
584
|
+
`--wasm-ruby 3.4` to choose another one. Pass extra flags to
|
|
585
|
+
`rbwasm build` with `--wasm-opt <arg>`.
|
|
586
|
+
* `--wasm=picoruby` (or `--picoruby`): [PicoRuby](https://github.com/picoruby/picoruby),
|
|
587
|
+
the mruby-based Ruby for microcontrollers.
|
|
588
|
+
* Its runtime is about 2 MB, and nothing needs to be installed.
|
|
589
|
+
* PicoRuby has its own small library (`js`, `json`, `yaml`, `base64`, ...)
|
|
590
|
+
and no RubyGems. In the browser it cannot read your files, so OCRAN
|
|
591
|
+
bundles the program's files into one script, in the order they are
|
|
592
|
+
required.
|
|
593
|
+
|
|
594
|
+
In both cases the script is not run at build time. OCRAN scans the program
|
|
595
|
+
and reports what may not work on the chosen runtime, grouped into your code
|
|
596
|
+
and each gem:
|
|
597
|
+
|
|
598
|
+
* gems the runtime cannot load;
|
|
599
|
+
* threads, processes, sockets and `gets`;
|
|
600
|
+
* for PicoRuby, also CRuby standard libraries it does not have.
|
|
601
|
+
|
|
602
|
+
The build goes ahead anyway, since the program only fails if it reaches
|
|
603
|
+
those parts. The runtimes are downloaded from the npm registry once, checked
|
|
604
|
+
against the integrity hash the registry publishes, and cached in
|
|
605
|
+
`~/.cache/ocran/npm`.
|
|
606
|
+
|
|
348
607
|
### Compilation:
|
|
349
608
|
|
|
350
609
|
* OCRAN runs your script (using `Kernel#load`) and builds the output when it exits.
|
|
@@ -500,10 +759,10 @@ Notes:
|
|
|
500
759
|
|
|
501
760
|
## Requirements
|
|
502
761
|
|
|
503
|
-
* Ruby 3.
|
|
504
|
-
* For building Windows `.exe`: Windows with [RubyInstaller DevKit](https://rubyinstaller.org/downloads/) (mingw-w64), or Wine on Linux/macOS
|
|
762
|
+
* Ruby 3.3+
|
|
763
|
+
* 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
764
|
* For building Linux and MacOS binaries: the respective build tools
|
|
506
|
-
* For `--output-dir` / `--output-zip`: any platform with Ruby 3.
|
|
765
|
+
* For `--output-dir` / `--output-zip`: any platform with Ruby 3.3+
|
|
507
766
|
* For `--output-zip` on Linux/macOS: the `zip` command must be available
|
|
508
767
|
* For `--output-zip` on Windows: PowerShell (included in Windows 8+)
|
|
509
768
|
|
|
@@ -515,6 +774,32 @@ The architecture of the generated executable matches the Ruby interpreter used t
|
|
|
515
774
|
|
|
516
775
|
* **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
776
|
|
|
777
|
+
### mswin Ruby (Visual C++)
|
|
778
|
+
|
|
779
|
+
OCRAN also packages with an mswin Ruby (`x64-mswin64_140`), the Ruby built
|
|
780
|
+
with Microsoft Visual C++ instead of MinGW, e.g. the official builds from
|
|
781
|
+
<https://cache.ruby-lang.org/pub/ruby/binaries/mswin64/> or
|
|
782
|
+
`ruby/setup-ruby` with `ruby-version: mswin`. There is no MSYS2 devkit:
|
|
783
|
+
|
|
784
|
+
* The source gem (and `rake build` in a checkout) compiles the stubs with
|
|
785
|
+
`nmake -f src/Makefile.mswin`, so run it from a Visual Studio developer
|
|
786
|
+
prompt (`vcvars64.bat`, Build Tools with the C++ workload), as for any
|
|
787
|
+
native gem on mswin. The stubs link the C runtime statically.
|
|
788
|
+
* The Ruby DLL of an mswin Ruby links the Visual C++ runtime
|
|
789
|
+
(`vcruntime140.dll`), which is not part of Windows: without the
|
|
790
|
+
[VC++ Redistributable](https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist)
|
|
791
|
+
`ruby.exe` cannot start. OCRAN bundles the VC++ runtime DLLs next to the
|
|
792
|
+
packed `ruby.exe` (app-local deployment), so the executable runs on a
|
|
793
|
+
machine without the Redistributable.
|
|
794
|
+
* The libraries an mswin Ruby uses from vcpkg (gmp, libffi, OpenSSL,
|
|
795
|
+
libyaml, zlib) live next to `ruby.exe`. OCRAN reads the import tables of
|
|
796
|
+
`ruby.exe`, the Ruby DLL and the packed extensions and bundles the DLLs
|
|
797
|
+
they need from there, even with `--no-autodll` (as it packs RubyInstaller's
|
|
798
|
+
`ruby_builtin_dlls`).
|
|
799
|
+
* If the packed interpreter still cannot load a DLL, the executable says so
|
|
800
|
+
(`The Ruby interpreter could not start: a DLL it needs was not found`)
|
|
801
|
+
instead of exiting silently with status 0xC0000135.
|
|
802
|
+
|
|
518
803
|
## Development
|
|
519
804
|
|
|
520
805
|
### Quick start
|
|
@@ -536,7 +821,7 @@ The architecture of the generated executable matches the Ruby interpreter used t
|
|
|
536
821
|
|
|
537
822
|
| Task | Purpose |
|
|
538
823
|
|--------------|------------------------------------------------------------------------|
|
|
539
|
-
| `rake build` | Compile stub(.exe) (requires
|
|
824
|
+
| `rake build` | Compile stub(.exe) (requires mingw-w64 + DevKit, MSVC with an mswin Ruby, or Unix build tools) |
|
|
540
825
|
| `rake clean` | Remove generated binaries |
|
|
541
826
|
| `rake test` | Execute all unit & integration tests |
|
|
542
827
|
|
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
|
|
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')
|