ocran 1.4.2 → 1.4.4
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 +26 -0
- data/README.md +119 -24
- data/lib/ocran/build_facade.rb +2 -0
- data/lib/ocran/dir_builder.rb +3 -0
- data/lib/ocran/direction.rb +245 -23
- data/lib/ocran/ed_icon.rb +157 -0
- data/lib/ocran/gem_spec_queryable.rb +24 -3
- data/lib/ocran/inno_setup_script_builder.rb +15 -1
- data/lib/ocran/launcher_event_recorder.rb +29 -0
- data/lib/ocran/option.rb +7 -0
- data/lib/ocran/runner.rb +4 -2
- data/lib/ocran/stub_builder.rb +21 -6
- data/lib/ocran/version.rb +1 -1
- data/src/Makefile +2 -4
- data/src/inst_dir.c +44 -2
- data/src/inst_dir.h +14 -0
- data/src/stub.c +19 -8
- data/src/system_utils.c +26 -0
- data/src/system_utils.h +14 -0
- data/src/system_utils_posix.c +6 -0
- data/src/unpack.c +4 -0
- data/src/unpack.h +9 -0
- metadata +12 -25
- data/src/edicon.c +0 -161
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79aa33b2625758b6cce7b1175d45d73c5c60209b996a6d5ab03a4df55acbae28
|
|
4
|
+
data.tar.gz: 3d53ff8c8b7a31448080f105f33af4968dea8f9a8fd559aa92a9e87db31db0dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e9975e649595c77397e49649544fad56b209917a55f7ea6d7ff7acf748a14608165013fbe1e43908946b0cba1badbae22da7b73aac4ba2661fb092162d333386
|
|
7
|
+
data.tar.gz: 67007f4112f91a8bb6a01564a8b5b4b54edd2b852a629f78d1bc12bd9062e3a71771fe0a802bc9f3133560cd0aa7da18fa912f0eb0f2f8337288ea226be308b2
|
data/CHANGELOG.txt
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
=== 1.4.4
|
|
2
|
+
- Fix crash on distro-packaged and Homebrew Ruby when a default gem (e.g. fiddle, singleton) has a gemspec but no materialized gem directory: file collection now treats the missing directory as an empty set instead of raising Errno::ENOENT from Find.find (issue #44). The gem's files, which live in the stdlib, are still packed via the load path.
|
|
3
|
+
- Pack the real Ruby interpreter when bindir/ruby is a dispatcher script (e.g. Fedora's rubypick): the running interpreter binary (/proc/self/exe) is packed under the expected name instead, so the packed executable no longer requires Ruby on the target system.
|
|
4
|
+
- Build runtime RUBYLIB from the build Ruby's actual default load path in addition to the RbConfig directories, covering distro-specific compiled-in entries such as Fedora's /usr/share/rubygems where rubygems.rb lives outside rubylibdir.
|
|
5
|
+
- Include every Gem.path entry under the Ruby prefix in the runtime GEM_PATH (not just Gem.default_dir), so RubyGems can activate gems from split layouts like Fedora's /usr/share/gems + /usr/local/share/gems.
|
|
6
|
+
- Make default gems activatable at runtime: their gemspecs (from specifications/default/, which RubyGems only scans under the compiled-in Gem.default_dir) are also copied into the regular specifications directory of the same packed base directory.
|
|
7
|
+
- Handle distros that expose gem files under additional symlinked paths (e.g. Fedora symlinks /usr/share/ruby/psych.rb into the psych gem directory): loaded features are resolved by realpath so they are packed inside the gem directory, and duplicate stdlib-level copies that would shadow the packed gem at runtime are no longer packed.
|
|
8
|
+
- Pack native extension files that distros build outside the gem's base_dir (e.g. Fedora's /usr/lib64/gems/ruby) into the extension directory RubyGems computes at runtime, and recreate the gem.build_complete marker there so the gem activates instead of being ignored ("its extensions are not built").
|
|
9
|
+
- Linux: bundle detected non-glibc shared libraries (libyaml, libssl, libcrypt, libgmp, libz, ...) next to libruby, with soname symlink aliases, so packed executables also run on other distros where these are missing or have different sonames. Core glibc libraries, the loader, and libnss_* plugins are never bundled; the target's glibc must be at least as new as the build host's. Disable with --no-autodll.
|
|
10
|
+
- CI: new isolated workflow test-aspera-cli.yml that packages the aspera-cli gem with distro Ruby on Fedora and verifies the executable runs on clean Fedora and Ubuntu containers without Ruby. Kept separate from the release pipeline with a read-only token; the third-party gem only ever runs inside throwaway containers.
|
|
11
|
+
- Restore the OCRA-compatible wrapper executable for Inno Setup builds: `--innosetup` now additionally builds a small stub executable named after `--output` and installs it into `{app}` alongside the application files. The wrapper runs the installed application directly from its own directory (new RUN_IN_EXE_DIR stub mode) — no extraction, no cleanup of the application directory. This behavior existed in OCRA and ocran <= 1.3.x but was lost in the 1.4.0 rewrite, breaking user ISS scripts that reference the executable, e.g. in [Run]/[UninstallRun] entries or for Windows service registration (`myapp.exe --register-service`). The launcher.bat is still generated and installed as before.
|
|
12
|
+
- Add `run_in_exe_dir` option to StubBuilder and a RUN_IN_EXE_DIR (0x20) stub header flag. In this mode the stub never deletes its directory (double-guarded in the stub itself, since the directory is the real application directory).
|
|
13
|
+
- Allow `--innosetup` on POSIX hosts when an ISCC command is available in PATH (e.g. a Wine wrapper); previously it was rejected on non-Windows unconditionally. Fix the ISCC presence check to use `command -v` instead of the Windows-only `where`/`>NUL` on POSIX.
|
|
14
|
+
- Inno Setup builds from POSIX hosts no longer crash on symlink entries (symlinks cannot be expressed in an installer script and are skipped; they are only generated for POSIX shared-library aliases).
|
|
15
|
+
- `--output-dir` and `--output-zip` now also include the wrapper executable (named after `--output`) next to the launch script, so the unpacked output has a doubleclickable, pinnable, signable entry point that can even be registered as a Windows service. The launch script (`.bat`/`.sh`) is still generated.
|
|
16
|
+
- New `--no-wrapper-exe` option: omit the wrapper executable from installer, directory and zip output.
|
|
17
|
+
- Always create the packed prefix gem directories (e.g. lib/ruby/gems/<api>) even when empty: RubyGems probes the default gem dir for writability at startup and printed "Can't determine writability of default gem path" on stderr for every packed executable on newer RubyGems.
|
|
18
|
+
- Stub: normalize the extraction/application directory to its long path form on Windows (GetLongPathName). With an 8.3 short TEMP path (e.g. C:\Users\RUNNER~1) the same file could be loaded under two spellings, defeating $LOADED_FEATURES deduplication and causing "already initialized constant" warnings.
|
|
19
|
+
|
|
20
|
+
=== 1.4.3
|
|
21
|
+
- Migrate edicon from C to Ruby: replace edicon.exe with a pure-Ruby implementation (ed_icon.rb), fixing incorrect BeginUpdateResource error checking and wrong GroupIconSize calculation. Shortens build times and removes the C binary from the build.
|
|
22
|
+
- Include Gem.default_dir in GEM_PATH on all platforms: previously the exec_prefix gem directory was excluded on Windows, causing native-extension gems (e.g. fxruby/fox16) to fail at runtime with a LoadError even though they were bundled correctly.
|
|
23
|
+
- Add FXRuby (fox16) test: verify that native extension DLLs are bundled correctly so the packaged executable does not raise a LoadError at runtime.
|
|
24
|
+
- CI: start a virtual display (xpra + Xorg dummy driver with GLX) on Linux before running tests so FXRuby's X11 requirement is satisfied.
|
|
25
|
+
- CI: install XQuartz on macOS runners and add it to the portability test job so FXRuby can connect to a display on both test and portability jobs.
|
|
26
|
+
|
|
1
27
|
=== 1.4.2
|
|
2
28
|
- Auto-bundle OpenSSL and all its transitive dependencies (openssl.rb, digest.so, etc.) when net/http is loaded but no HTTPS request was made during the dependency scan. OpenSSL is now required inside the OCRAN build process so every file it pulls in appears in the bundled executable.
|
|
3
29
|
- Add companion DLL scan on windows: when a native extension (.so) within the Ruby installation is loaded, all DLLs in the same directory are proactively included. This ensures libssl-3-x64.dll, libcrypto-3-x64.dll, libwinpthread-1.dll, and libyaml-0-2.dll are bundled even when no HTTPS connection is made during the build scan.
|
data/README.md
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OCRAN
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
issues :: http://github.com/largo/ocran/issues
|
|
6
|
-
|
|
7
|
-
## Description
|
|
8
|
-
|
|
9
|
-
OCRAN (One-Click Ruby Application Next) packages Ruby applications for
|
|
3
|
+
[OCRAN (One-Click Ruby Application Next)](https://github.com/largo/ocran) packages Ruby applications for
|
|
10
4
|
distribution. It bundles your script, the Ruby interpreter, gems, and native
|
|
11
5
|
libraries into a single self-contained artifact that runs without requiring
|
|
12
6
|
Ruby to be installed on the target machine.
|
|
@@ -21,20 +15,24 @@ OCRAN supports four output formats, all cross-platform:
|
|
|
21
15
|
OCRAN is a fork of [OCRA](https://github.com/larsch/ocra) maintained for
|
|
22
16
|
Ruby 3.2+ compatibility.
|
|
23
17
|
|
|
18
|
+
If you run into errors while using OCRAN, please check the [OCRAN issues](https://github.com/largo/ocran/issues) first.
|
|
19
|
+
|
|
24
20
|
## Recommended usage
|
|
25
21
|
|
|
26
|
-
The most common use-case is shipping a program to Windows
|
|
27
|
-
who do not have Ruby installed. By default, each time the `.exe` is opened it
|
|
22
|
+
The most common use-case is shipping a program to users running Windows / Linux / macOS
|
|
23
|
+
who do not have Ruby installed. By default, each time the `.exe` / executable is opened it
|
|
28
24
|
extracts the Ruby interpreter and your code to a temporary directory and runs
|
|
29
25
|
them from there.
|
|
30
26
|
|
|
31
|
-
Because extraction takes time on each launch,
|
|
27
|
+
Because extraction takes time on each launch, use `--output-dir` or
|
|
28
|
+
`--output-zip` to produce a portable directory/archive that runs with the
|
|
29
|
+
bundled Ruby on Linux, macOS, or Windows.
|
|
30
|
+
If using Windows you can use the Inno Setup
|
|
32
31
|
option (`--innosetup`) to produce a proper installer that extracts once to a
|
|
33
32
|
permanent directory.
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
bundled Ruby on Linux, macOS, or Windows.
|
|
34
|
+
You can easily generate binaries for the supported Operating Systems with GitHub
|
|
35
|
+
Actions — see [Building for multiple platforms with GitHub Actions](#building-for-multiple-platforms-with-github-actions).
|
|
38
36
|
|
|
39
37
|
## Features
|
|
40
38
|
|
|
@@ -74,12 +72,12 @@ https://github.com/largo/ocran/releases/.
|
|
|
74
72
|
|
|
75
73
|
## Synopsis
|
|
76
74
|
|
|
77
|
-
### Building
|
|
75
|
+
### Building an executable:
|
|
78
76
|
|
|
79
77
|
ocran script.rb
|
|
80
78
|
|
|
81
79
|
Packages `script.rb`, the Ruby interpreter, and all dependencies (gems and
|
|
82
|
-
DLLs) into `script.exe
|
|
80
|
+
DLLs) into `script.exe` or `script` on Linux or macOS.
|
|
83
81
|
|
|
84
82
|
### Building a portable directory (Linux / macOS / Windows):
|
|
85
83
|
|
|
@@ -238,12 +236,98 @@ OCRAN on the same platform (and architecture) as the intended target**:
|
|
|
238
236
|
There is no support for building a Windows `.exe` from a Linux or macOS host,
|
|
239
237
|
or vice versa. If you need builds for multiple platforms, run OCRAN in CI on
|
|
240
238
|
each target platform separately (e.g., a Windows runner for `.exe` builds and
|
|
241
|
-
a Linux runner for Linux builds).
|
|
239
|
+
a Linux runner for Linux builds). See
|
|
240
|
+
[Building for multiple platforms with GitHub Actions](#building-for-multiple-platforms-with-github-actions)
|
|
241
|
+
for a ready-made setup.
|
|
242
|
+
|
|
243
|
+
### Linux binary portability (glibc)
|
|
244
|
+
|
|
245
|
+
On Linux, OCRAN bundles the shared libraries your application loaded during
|
|
246
|
+
the build (libyaml, libssl, libcrypt, libgmp, libz, ...) next to the bundled
|
|
247
|
+
Ruby, so the executable also runs on distributions where those libraries are
|
|
248
|
+
missing or have different sonames. It works with distro-packaged Ruby too
|
|
249
|
+
(e.g. `dnf install ruby` on Fedora), including its split gem layout and
|
|
250
|
+
`rubypick` wrapper.
|
|
251
|
+
|
|
252
|
+
One thing can never be bundled: **glibc itself** (and its dynamic loader).
|
|
253
|
+
Those always come from the target system, and glibc is only
|
|
254
|
+
backward-compatible. In practice:
|
|
255
|
+
|
|
256
|
+
* A Linux binary runs on any distribution whose glibc is **at least as new**
|
|
257
|
+
as the build machine's.
|
|
258
|
+
* Build on the **oldest** distribution you want to support. For example, a
|
|
259
|
+
binary built on the oldest available `ubuntu-*` GitHub runner runs on that
|
|
260
|
+
Ubuntu version and everything newer, while a binary built on a
|
|
261
|
+
cutting-edge distribution (e.g. `fedora:latest`) will not run on older
|
|
262
|
+
Debian/Ubuntu releases (`` version `GLIBC_2.4x' not found ``).
|
|
263
|
+
|
|
264
|
+
Use `--no-autodll` to disable shared library bundling.
|
|
265
|
+
|
|
266
|
+
## Building for multiple platforms with GitHub Actions
|
|
267
|
+
|
|
268
|
+
Since OCRAN is not a cross-compiler, the recommended way to ship your app for
|
|
269
|
+
Windows, Linux, and macOS is a GitHub Actions matrix that runs OCRAN natively
|
|
270
|
+
on each platform. A minimal workflow:
|
|
271
|
+
|
|
272
|
+
```yaml
|
|
273
|
+
name: Build binaries
|
|
274
|
+
|
|
275
|
+
on:
|
|
276
|
+
push:
|
|
277
|
+
tags: ['v*'] # build releases from version tags
|
|
278
|
+
workflow_dispatch: # allow manual runs
|
|
279
|
+
|
|
280
|
+
jobs:
|
|
281
|
+
build:
|
|
282
|
+
strategy:
|
|
283
|
+
fail-fast: false
|
|
284
|
+
matrix:
|
|
285
|
+
include:
|
|
286
|
+
- os: windows-latest # x86-64 Windows .exe
|
|
287
|
+
- os: ubuntu-22.04 # x86-64 Linux; oldest runner = widest glibc compatibility
|
|
288
|
+
- os: macos-14 # Apple Silicon macOS
|
|
289
|
+
- os: macos-15-intel # Intel macOS
|
|
290
|
+
runs-on: ${{ matrix.os }}
|
|
291
|
+
steps:
|
|
292
|
+
- uses: actions/checkout@v4
|
|
293
|
+
|
|
294
|
+
- uses: ruby/setup-ruby@v1
|
|
295
|
+
with:
|
|
296
|
+
ruby-version: '3.4'
|
|
297
|
+
bundler-cache: true # if your app has a Gemfile
|
|
298
|
+
|
|
299
|
+
- run: gem install ocran
|
|
300
|
+
|
|
301
|
+
# Run OCRAN on your entry script. Add "-- <args>" if your script needs
|
|
302
|
+
# arguments to exit cleanly during the dependency-detection run.
|
|
303
|
+
- run: ocran myapp.rb --output myapp
|
|
304
|
+
# produces myapp.exe on Windows, myapp on Linux/macOS
|
|
305
|
+
|
|
306
|
+
- uses: actions/upload-artifact@v4
|
|
307
|
+
with:
|
|
308
|
+
name: myapp-${{ matrix.os }}
|
|
309
|
+
path: myapp*
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Notes:
|
|
313
|
+
|
|
314
|
+
* Pick the **oldest** `ubuntu-*` runner GitHub offers for the Linux build so
|
|
315
|
+
the binary runs on as many distributions as possible (see
|
|
316
|
+
[Linux binary portability](#linux-binary-portability-glibc)).
|
|
317
|
+
* On macOS, each architecture needs its own build: `macos-14` (and newer
|
|
318
|
+
numbered ARM runners) produce Apple Silicon binaries, `macos-15-intel`
|
|
319
|
+
produces Intel binaries.
|
|
320
|
+
* Swap the `ocran` invocation for `--output-dir`, `--output-zip`,
|
|
321
|
+
`--macosx-bundle`, or `--innosetup` depending on how you want to ship
|
|
322
|
+
(see [Recommended usage](#recommended-usage)).
|
|
323
|
+
* For tag pushes you can attach the artifacts to a GitHub release with a
|
|
324
|
+
follow-up job (e.g. `softprops/action-gh-release`).
|
|
242
325
|
|
|
243
326
|
## Requirements
|
|
244
327
|
|
|
245
328
|
* Ruby 3.2+
|
|
246
|
-
* For building Windows `.exe`: Windows with RubyInstaller DevKit (mingw-w64), or Wine on Linux/macOS
|
|
329
|
+
* For building Windows `.exe`: Windows with [RubyInstaller DevKit](https://rubyinstaller.org/downloads/) (mingw-w64), or Wine on Linux/macOS
|
|
330
|
+
* For building Linux and MacOS binaries: the respective build tools
|
|
247
331
|
* For `--output-dir` / `--output-zip`: any platform with Ruby 3.2+
|
|
248
332
|
* For `--output-zip` on Linux/macOS: the `zip` command must be available
|
|
249
333
|
* For `--output-zip` on Windows: PowerShell (included in Windows 8+)
|
|
@@ -319,8 +403,12 @@ If your script uses `Kernel#autoload`, OCRAN will attempt to load those
|
|
|
319
403
|
constants so they are included in the output. Missing modules are ignored
|
|
320
404
|
with a warning.
|
|
321
405
|
|
|
322
|
-
Dynamic link libraries
|
|
323
|
-
|
|
406
|
+
Dynamic link libraries are detected and included by OCRAN: `.dll` files on
|
|
407
|
+
Windows (for example WxWidgets), and on Linux the non-glibc shared libraries
|
|
408
|
+
the process loaded (libyaml, libssl, libcrypt, ...), which are placed next to
|
|
409
|
+
the bundled Ruby with appropriate soname symlinks (see
|
|
410
|
+
[Linux binary portability](#linux-binary-portability-glibc)). Disable with
|
|
411
|
+
`--no-autodll`.
|
|
324
412
|
|
|
325
413
|
### Including libraries non-automatically
|
|
326
414
|
|
|
@@ -526,12 +614,19 @@ file:
|
|
|
526
614
|
end
|
|
527
615
|
end
|
|
528
616
|
|
|
617
|
+
## See elsewhere
|
|
618
|
+
|
|
619
|
+
- [State of Ruby Packagers](https://gist.github.com/YOU54F/3775e66e6090e0371c11601e6b75c305)
|
|
620
|
+
- [Traveling Ruby](https://github.com/trubygems/traveling-ruby)
|
|
621
|
+
|
|
529
622
|
## Credits
|
|
530
623
|
|
|
531
|
-
|
|
624
|
+
[Andi Idogawa](https://github.com/largo), [Shinokaro](https://github.com/shinokaro)
|
|
625
|
+
[Lars Christensen](https://github.com/larsch) and contributors for the OCRA project which this is forked from.
|
|
626
|
+
|
|
627
|
+
Special thanks to Ruby on Windows maintainers such as [Nobuyoshi Nakada](https://github.com/nobu), [Lars Kanis](https://github.com/larskanis), and [MSP-Greg](https://github.com/MSP-Greg)
|
|
532
628
|
|
|
533
|
-
Kevin Walzer of codebykevin, Maxim Samsonov
|
|
534
|
-
codesigning support.
|
|
629
|
+
[Kevin Walzer](https://github.com/codebykevin) of codebykevin, [Maxim Samsonov](https://github.com/maxirmx), and [John Mair](https://github.com/banister) for codesigning support.
|
|
535
630
|
|
|
536
631
|
Igor Pavlov for the LZMA compressor and decompressor (Public Domain).
|
|
537
632
|
|
|
@@ -545,7 +640,7 @@ http://ruby.morphball.net/vit-ruby-ico_en.html).
|
|
|
545
640
|
(The MIT License)
|
|
546
641
|
|
|
547
642
|
Copyright (c) 2009-2020 Lars Christensen
|
|
548
|
-
Copyright (c) 2020-
|
|
643
|
+
Copyright (c) 2020-2026 The OCRAN Committers Team
|
|
549
644
|
|
|
550
645
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
551
646
|
a copy of this software and associated documentation files (the
|
data/lib/ocran/build_facade.rb
CHANGED
data/lib/ocran/dir_builder.rb
CHANGED
|
@@ -13,6 +13,9 @@ module Ocran
|
|
|
13
13
|
WINDOWS = Gem.win_platform?
|
|
14
14
|
|
|
15
15
|
attr_reader :data_size
|
|
16
|
+
# Recorded launch data (environment variables and exec arguments), used
|
|
17
|
+
# by Direction to build the optional wrapper executable.
|
|
18
|
+
attr_reader :env, :exec_args
|
|
16
19
|
|
|
17
20
|
def initialize(path)
|
|
18
21
|
@path = Pathname(path)
|
data/lib/ocran/direction.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require "rbconfig"
|
|
3
3
|
require "pathname"
|
|
4
|
+
require "set"
|
|
4
5
|
require_relative "refine_pathname"
|
|
5
6
|
require_relative "host_config_helper"
|
|
6
7
|
require_relative "command_output"
|
|
@@ -17,6 +18,12 @@ module Ocran
|
|
|
17
18
|
# - /ruby/vendor_ruby/3.0.0/
|
|
18
19
|
RUBY_LIBRARY_PATH_REGEX = %r{/(ruby/(?:site_ruby/|vendor_ruby/)?\d+\.\d+\.\d+)/?$}i
|
|
19
20
|
|
|
21
|
+
# Core libraries provided by glibc and the dynamic loader. These must
|
|
22
|
+
# never be bundled: they are tightly coupled to the target system's
|
|
23
|
+
# ld.so, and shipping a foreign copy breaks the dynamic linker. The
|
|
24
|
+
# libnss_* plugins are dlopened by the target's glibc and must match it.
|
|
25
|
+
LINUX_SYSTEM_LIBRARY_RE = /\A(?:ld-linux|ld64|libc\.so|libm\.so|libdl\.so|libpthread\.so|librt\.so|libutil\.so|libnsl\.so|libresolv\.so|libmvec\.so|libanl\.so|libBrokenLocale\.so|libnss_)/
|
|
26
|
+
|
|
20
27
|
include BuildConstants, CommandOutput, HostConfigHelper
|
|
21
28
|
|
|
22
29
|
attr_reader :ruby_executable, :rubyopt
|
|
@@ -168,7 +175,20 @@ module Ocran
|
|
|
168
175
|
|
|
169
176
|
# Add the ruby executable and DLL
|
|
170
177
|
say "Adding ruby executable #{ruby_executable}"
|
|
171
|
-
|
|
178
|
+
ruby_source = bindir / ruby_executable
|
|
179
|
+
if !Gem.win_platform? && File.binread(ruby_source, 2) == "#!"
|
|
180
|
+
# On some distros (e.g. Fedora), bindir/ruby is a dispatcher shell
|
|
181
|
+
# script ("rubypick") rather than the interpreter itself, which
|
|
182
|
+
# cannot run on a system without Ruby. Pack the currently running
|
|
183
|
+
# interpreter binary under the expected name instead.
|
|
184
|
+
real_ruby = Pathname("/proc/self/exe")
|
|
185
|
+
raise "#{ruby_source} is a wrapper script and the real interpreter could not be determined" unless real_ruby.exist?
|
|
186
|
+
|
|
187
|
+
say "#{ruby_source} is a wrapper script; packing #{real_ruby.realpath} instead"
|
|
188
|
+
builder.copy_to_bin(real_ruby.realpath, ruby_executable)
|
|
189
|
+
else
|
|
190
|
+
builder.copy_to_bin(ruby_source, ruby_executable)
|
|
191
|
+
end
|
|
172
192
|
if libruby_so
|
|
173
193
|
# On POSIX systems, libruby.so is in libdir; on Windows, it's in bindir
|
|
174
194
|
libruby_src = Gem.win_platform? ? bindir / libruby_so : libdir / libruby_so
|
|
@@ -221,6 +241,44 @@ module Ocran
|
|
|
221
241
|
end
|
|
222
242
|
end
|
|
223
243
|
|
|
244
|
+
# Linux: bundle detected shared libraries (e.g. libyaml, libssl,
|
|
245
|
+
# libcrypt) next to libruby so the executable also runs on systems
|
|
246
|
+
# where they are missing or have different sonames (the stub already
|
|
247
|
+
# points LD_LIBRARY_PATH at the packed bin directory). Core glibc
|
|
248
|
+
# libraries and the loader are never bundled - they must come from the
|
|
249
|
+
# target system. Ruby native extensions are packed as features, not
|
|
250
|
+
# here.
|
|
251
|
+
if RUBY_PLATFORM.include?("linux") && @option.auto_detect_dlls?
|
|
252
|
+
feature_set = features.to_set
|
|
253
|
+
feature_realpaths = features.filter_map { |f| f.realpath rescue nil }.to_set
|
|
254
|
+
# Ruby native extensions live in these directories and are packed as
|
|
255
|
+
# features; they must not be duplicated into bin.
|
|
256
|
+
ruby_arch_dirs = RbConfig::CONFIG.values_at("archdir", "sitearchdir", "vendorarchdir")
|
|
257
|
+
.compact.map { |dir| Pathname(dir) }
|
|
258
|
+
detect_dlls.each do |dll|
|
|
259
|
+
basename = dll.basename.to_s
|
|
260
|
+
next unless basename.match?(/\.so(\.|\z)/)
|
|
261
|
+
next if basename.match?(LINUX_SYSTEM_LIBRARY_RE)
|
|
262
|
+
# libruby is packed with its aliases already
|
|
263
|
+
next if basename.start_with?("libruby")
|
|
264
|
+
next if feature_set.include?(dll) || feature_realpaths.include?(dll)
|
|
265
|
+
next if ruby_arch_dirs.any? { |dir| dll.subpath?(dir) } || dll.to_posix.match?(%r{/gems/})
|
|
266
|
+
next unless dll.file?
|
|
267
|
+
|
|
268
|
+
say "Adding detected shared library #{dll}"
|
|
269
|
+
builder.copy_to_bin(dll, basename)
|
|
270
|
+
# The loader may request a library by a less specific soname than
|
|
271
|
+
# the fully versioned file name recorded in the memory map (e.g.
|
|
272
|
+
# libssl.so.3 for libssl.so.3.2.4). Provide symlink aliases for
|
|
273
|
+
# each shorter version suffix.
|
|
274
|
+
alias_name = basename
|
|
275
|
+
while (shorter = alias_name.sub(/\.\d+[^.]*\z/, "")) != alias_name && shorter.include?(".so")
|
|
276
|
+
builder.symlink_in_bin(basename, shorter)
|
|
277
|
+
alias_name = shorter
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
224
282
|
# Windows-only: Add external manifest and builtin DLLs
|
|
225
283
|
if Gem.win_platform?
|
|
226
284
|
if (manifest = ruby_builtin_manifest)
|
|
@@ -284,6 +342,45 @@ module Ocran
|
|
|
284
342
|
raise "Gem spec #{spec_file} does not exist in the Ruby installation. Don't know where to put it."
|
|
285
343
|
end
|
|
286
344
|
|
|
345
|
+
spec_dir = spec_file.dirname
|
|
346
|
+
default_spec = spec_dir.basename.to_s == "default"
|
|
347
|
+
# The gem's base directory: specs live in <base_dir>/specifications[/default]
|
|
348
|
+
base_dir = default_spec ? spec_dir.dirname.dirname : spec_dir.dirname
|
|
349
|
+
# Relative packed location of base_dir; nil when the gem lives outside
|
|
350
|
+
# the Ruby prefix, in which case its files are packed under GEMDIR
|
|
351
|
+
# with the same layout.
|
|
352
|
+
packed_base = base_dir.subpath?(exec_prefix) ? base_dir.relative_path_from(exec_prefix) : GEMDIR
|
|
353
|
+
|
|
354
|
+
# Default gemspecs live in a "specifications/default" directory, which
|
|
355
|
+
# RubyGems only scans under Gem.default_dir. On rubies with a
|
|
356
|
+
# compiled-in absolute prefix (e.g. distro Ruby on Fedora), that path
|
|
357
|
+
# points to the build host and does not exist on the target system, so
|
|
358
|
+
# activating such a gem (e.g. via a binstub) fails. Also place a copy
|
|
359
|
+
# in the regular specifications directory of the same packed base
|
|
360
|
+
# directory, where GEM_PATH makes it discoverable and its require
|
|
361
|
+
# paths resolve to the packed gem files.
|
|
362
|
+
if !Gem.win_platform? && default_spec
|
|
363
|
+
builder.cp(spec_file, packed_base / "specifications" / spec_file.basename)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Native-extension gems record successful builds in a gem.build_complete
|
|
367
|
+
# marker under <base_dir>/extensions/...; without it RubyGems refuses to
|
|
368
|
+
# activate the gem ("Ignoring x because its extensions are not built").
|
|
369
|
+
# The marker can be absent from the packed tree either because the
|
|
370
|
+
# distro relocates it (e.g. Fedora keeps it under /usr/lib64/gems/ruby
|
|
371
|
+
# via a default_ext_dir_for override) or because the gem is a default
|
|
372
|
+
# gem, which is exempt from the check on the host while its packed
|
|
373
|
+
# regular-spec copy is not. Recreate the marker where RubyGems expects
|
|
374
|
+
# it at runtime.
|
|
375
|
+
if !Gem.win_platform? && spec.extensions.any?
|
|
376
|
+
api_version = Gem.respond_to?(:extension_api_version) ? Gem.extension_api_version : Gem.ruby_api_version
|
|
377
|
+
packed_ext_dir = Pathname("extensions") / Gem::Platform.local.to_s / api_version / spec.full_name
|
|
378
|
+
host_marker = Pathname(spec.gem_build_complete_path)
|
|
379
|
+
if default_spec || (File.exist?(host_marker) && !host_marker.subpath?(base_dir))
|
|
380
|
+
builder.touch(packed_base / packed_ext_dir / "gem.build_complete")
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
287
384
|
# Determine which set of files to include for this particular gem
|
|
288
385
|
include = GemSpecQueryable.gem_inclusion_set(spec.name, @option.gem_options)
|
|
289
386
|
say "Detected gem #{spec.full_name} (#{include.join(", ")})"
|
|
@@ -292,6 +389,9 @@ module Ocran
|
|
|
292
389
|
|
|
293
390
|
verbose "\tgem_dir: #{spec.gem_dir}"
|
|
294
391
|
verbose "\tgem_dir exists: #{File.directory?(spec.gem_dir)}"
|
|
392
|
+
unless File.directory?(spec.gem_dir)
|
|
393
|
+
verbose "\tGem directory does not exist (default gem?); packing loaded files via load path instead"
|
|
394
|
+
end
|
|
295
395
|
loaded_matches = include.include?(:loaded) ? features.select { |f| f.subpath?(spec.gem_dir) } : []
|
|
296
396
|
verbose "\t:loaded candidates in features: #{loaded_matches.size}"
|
|
297
397
|
loaded_matches.each { |f| verbose "\t loaded: #{f}" }
|
|
@@ -313,11 +413,72 @@ module Ocran
|
|
|
313
413
|
end
|
|
314
414
|
end
|
|
315
415
|
|
|
416
|
+
# When a distro builds native extensions outside the gem's base_dir
|
|
417
|
+
# and loads them from there (e.g. Fedora's /usr/lib64/gems/ruby), the
|
|
418
|
+
# loaded .so would be packed at a mirror path that is never on the
|
|
419
|
+
# runtime load path. Pack such loaded extension files ONLY into the
|
|
420
|
+
# extension directory RubyGems computes at runtime, which gem
|
|
421
|
+
# activation puts on the load path. Appended to actual_files after
|
|
422
|
+
# the generic copy above so they are claimed as gem files (and thus
|
|
423
|
+
# excluded from the plain feature packing, where a bare .so on
|
|
424
|
+
# RUBYLIB would shadow the gem) without also being mirrored at their
|
|
425
|
+
# host location. The extension dir and the load location may be
|
|
426
|
+
# aliased via symlinks in either direction, so match features against
|
|
427
|
+
# the extension dir by realpath both ways.
|
|
428
|
+
if !Gem.win_platform? && spec.extensions.any?
|
|
429
|
+
host_ext_dir = Pathname(spec.extension_dir)
|
|
430
|
+
if host_ext_dir.directory? && !host_ext_dir.subpath?(base_dir)
|
|
431
|
+
alias_map = {}
|
|
432
|
+
host_ext_dir.find.select(&:file?).each do |entry|
|
|
433
|
+
real = begin
|
|
434
|
+
entry.realpath
|
|
435
|
+
rescue SystemCallError
|
|
436
|
+
next
|
|
437
|
+
end
|
|
438
|
+
alias_map[real] = entry
|
|
439
|
+
end
|
|
440
|
+
features.each do |feature|
|
|
441
|
+
real = begin
|
|
442
|
+
feature.realpath
|
|
443
|
+
rescue SystemCallError
|
|
444
|
+
feature
|
|
445
|
+
end
|
|
446
|
+
entry = if feature.subpath?(host_ext_dir)
|
|
447
|
+
feature
|
|
448
|
+
elsif real.subpath?(host_ext_dir)
|
|
449
|
+
real
|
|
450
|
+
else
|
|
451
|
+
alias_map[feature] || alias_map[real]
|
|
452
|
+
end
|
|
453
|
+
next unless entry
|
|
454
|
+
|
|
455
|
+
builder.cp(feature, packed_base / packed_ext_dir / entry.relative_path_from(host_ext_dir))
|
|
456
|
+
actual_files << feature
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
316
461
|
actual_files
|
|
317
462
|
end
|
|
318
463
|
gem_files.uniq!
|
|
319
464
|
|
|
320
|
-
|
|
465
|
+
# On some distros parts of a gem are reachable through symlinks at other
|
|
466
|
+
# locations (e.g. Fedora symlinks /usr/share/ruby/psych.rb into the
|
|
467
|
+
# psych gem directory), so a feature and a packed gem file can denote
|
|
468
|
+
# the same file under different paths. Compare realpaths when removing
|
|
469
|
+
# gem files from the feature list; otherwise a stdlib-level duplicate
|
|
470
|
+
# would be packed as well and shadow the packed gem at runtime.
|
|
471
|
+
gem_file_set = (gem_files + gem_files.filter_map { |file| file.realpath rescue nil }).to_set
|
|
472
|
+
features = features.reject do |feature|
|
|
473
|
+
next true if gem_file_set.include?(feature)
|
|
474
|
+
|
|
475
|
+
real = begin
|
|
476
|
+
feature.realpath
|
|
477
|
+
rescue SystemCallError
|
|
478
|
+
nil
|
|
479
|
+
end
|
|
480
|
+
real && gem_file_set.include?(real)
|
|
481
|
+
end
|
|
321
482
|
|
|
322
483
|
# If requested, add all ruby standard libraries
|
|
323
484
|
if @option.add_all_core?
|
|
@@ -500,13 +661,16 @@ module Ocran
|
|
|
500
661
|
# By adding the extract-dir equivalents of rubylibdir, sitelibdir, etc. to
|
|
501
662
|
# RUBYLIB, Ruby can find rubygems and the standard library in the packed tree.
|
|
502
663
|
unless Gem.win_platform?
|
|
503
|
-
|
|
664
|
+
# Use the build Ruby's actual default load path in addition to the
|
|
665
|
+
# RbConfig directories: some distros compile in extra entries that
|
|
666
|
+
# RbConfig does not expose (e.g. Fedora's /usr/share/rubygems, where
|
|
667
|
+
# rubygems.rb lives outside rubylibdir).
|
|
668
|
+
default_load_paths = @pre_env.load_path.map { |dir| Pathname(@pre_env.expand_path(dir)) }
|
|
669
|
+
archdir = Pathname(RbConfig::CONFIG["archdir"])
|
|
670
|
+
core_lib_paths = (default_load_paths + all_core_dir + [archdir])
|
|
504
671
|
.select { |dir| dir.subpath?(exec_prefix) }
|
|
505
672
|
.map { |dir| dir.relative_path_from(exec_prefix) }
|
|
506
|
-
|
|
507
|
-
if archdir.subpath?(exec_prefix)
|
|
508
|
-
core_lib_paths << archdir.relative_path_from(exec_prefix)
|
|
509
|
-
end
|
|
673
|
+
.uniq
|
|
510
674
|
load_path = core_lib_paths + load_path
|
|
511
675
|
end
|
|
512
676
|
|
|
@@ -514,15 +678,25 @@ module Ocran
|
|
|
514
678
|
builder.set_env_path("GEM_HOME", GEMDIR)
|
|
515
679
|
|
|
516
680
|
gem_paths = [GEMDIR]
|
|
517
|
-
#
|
|
518
|
-
#
|
|
519
|
-
#
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
681
|
+
# Gems installed under the Ruby prefix (exec_prefix) have their specs and
|
|
682
|
+
# extension dirs placed there via duplicate_to_exec_prefix. Include every
|
|
683
|
+
# Gem.path entry located under exec_prefix (relative to it) in GEM_PATH so
|
|
684
|
+
# RubyGems can find and activate them at runtime. This is required on both
|
|
685
|
+
# Windows (e.g. fxruby/fox16 whose fox16_c.so lives in extension_dir under
|
|
686
|
+
# the Ruby prefix) and POSIX (e.g. error_highlight default gems). Distros
|
|
687
|
+
# can split gems over several such directories - e.g. Fedora uses
|
|
688
|
+
# /usr/share/gems for RPM-packaged (default) gems and /usr/local/share/gems
|
|
689
|
+
# for user-installed ones - so Gem.default_dir alone is not enough.
|
|
690
|
+
prefix_gem_dirs = (Gem.path.map { |dir| Pathname(dir) } + [Pathname(Gem.default_dir)])
|
|
691
|
+
.select { |dir| dir.subpath?(exec_prefix) }
|
|
692
|
+
.map { |dir| dir.relative_path_from(exec_prefix) }
|
|
693
|
+
.uniq
|
|
694
|
+
gem_paths += prefix_gem_dirs
|
|
695
|
+
# RubyGems probes the default gem dir for writability at startup and
|
|
696
|
+
# prints "Can't determine writability of default gem path" on stderr
|
|
697
|
+
# when the directory does not exist in the packed layout - so always
|
|
698
|
+
# create the packed prefix gem dirs, even when no specs landed there.
|
|
699
|
+
prefix_gem_dirs.each { |dir| builder.mkdir(dir) }
|
|
526
700
|
builder.set_env_path("GEM_PATH", *gem_paths)
|
|
527
701
|
|
|
528
702
|
# Add the opcode to launch the script
|
|
@@ -545,8 +719,13 @@ module Ocran
|
|
|
545
719
|
title: @option.output_executable.basename.sub_ext("")
|
|
546
720
|
)
|
|
547
721
|
|
|
722
|
+
# Record the launch events (exports + exec) so they can be replayed
|
|
723
|
+
# into the wrapper stub executable below.
|
|
724
|
+
require_relative "launcher_event_recorder"
|
|
725
|
+
recorder = LauncherEventRecorder.new(launcher_builder)
|
|
726
|
+
|
|
548
727
|
require_relative "build_facade"
|
|
549
|
-
builder = BuildFacade.new(iss_builder,
|
|
728
|
+
builder = BuildFacade.new(iss_builder, recorder)
|
|
550
729
|
|
|
551
730
|
if @option.icon_filename
|
|
552
731
|
builder.cp(@option.icon_filename, File.basename(@option.icon_filename))
|
|
@@ -559,22 +738,65 @@ module Ocran
|
|
|
559
738
|
verbose File.read(launcher_path)
|
|
560
739
|
builder.cp(launcher_path, "launcher.bat")
|
|
561
740
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
741
|
+
require "tmpdir"
|
|
742
|
+
Dir.mktmpdir("ocran") do |tmpdir|
|
|
743
|
+
if @option.wrapper_exe?
|
|
744
|
+
# Wrapper executable (pre-1.4/OCRA behavior): a small stub without
|
|
745
|
+
# embedded files that runs the installed application directly from
|
|
746
|
+
# {app}. It is installed next to the application files so that user
|
|
747
|
+
# Inno Setup scripts can reference it by the --output name, e.g. in
|
|
748
|
+
# [Run]/[UninstallRun] entries or for Windows service registration.
|
|
749
|
+
wrapper_path = Pathname(tmpdir) / @option.output_executable.basename
|
|
750
|
+
build_wrapper_exe(wrapper_path) { |stub| recorder.replay(stub) }
|
|
751
|
+
builder.cp(wrapper_path, wrapper_path.basename)
|
|
752
|
+
end
|
|
565
753
|
|
|
566
|
-
|
|
567
|
-
|
|
754
|
+
say "Build inno setup script file"
|
|
755
|
+
iss_path = iss_builder.build
|
|
756
|
+
verbose File.read(iss_path)
|
|
757
|
+
|
|
758
|
+
say "Running Inno Setup Command-Line compiler (ISCC)"
|
|
759
|
+
iss_builder.compile(verbose: @option.verbose?)
|
|
760
|
+
end
|
|
568
761
|
|
|
569
762
|
say "Finished building installer file"
|
|
570
763
|
end
|
|
571
764
|
|
|
765
|
+
# Builds the small RUN_IN_EXE_DIR wrapper stub that starts the deployed
|
|
766
|
+
# application directly from the directory the wrapper resides in.
|
|
767
|
+
def build_wrapper_exe(wrapper_path)
|
|
768
|
+
require_relative "stub_builder"
|
|
769
|
+
say "Build wrapper executable #{wrapper_path.basename}"
|
|
770
|
+
StubBuilder.new(wrapper_path,
|
|
771
|
+
chdir_before: @option.chdir_before?,
|
|
772
|
+
debug_mode: @option.enable_debug_mode?,
|
|
773
|
+
gui_mode: @option.windowed?,
|
|
774
|
+
icon_path: @option.icon_filename,
|
|
775
|
+
run_in_exe_dir: true) do |stub|
|
|
776
|
+
yield(stub)
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
|
|
572
780
|
def build_output_dir(path)
|
|
573
781
|
require_relative "dir_builder"
|
|
574
782
|
|
|
575
783
|
path = Pathname(path)
|
|
576
784
|
say "Building directory #{path}"
|
|
577
|
-
DirBuilder.new(path, &to_proc)
|
|
785
|
+
builder = DirBuilder.new(path, &to_proc)
|
|
786
|
+
|
|
787
|
+
if @option.wrapper_exe?
|
|
788
|
+
# Same wrapper as in Inno Setup builds: a doubleclickable executable
|
|
789
|
+
# next to the launch script, usable e.g. for Windows service
|
|
790
|
+
# registration from an unpacked zip. Disable with --no-wrapper-exe.
|
|
791
|
+
build_wrapper_exe(path / @option.output_executable.basename) do |stub|
|
|
792
|
+
builder.env.each { |name, value| stub.export(name, value) }
|
|
793
|
+
if builder.exec_args
|
|
794
|
+
image, script, argv = builder.exec_args
|
|
795
|
+
stub.exec(image, script, *argv)
|
|
796
|
+
end
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
|
|
578
800
|
say "Finished building directory #{path}"
|
|
579
801
|
end
|
|
580
802
|
|