ocran 1.4.3 → 1.4.5
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 +37 -0
- data/README.md +539 -31
- data/lib/ocran/build_facade.rb +2 -0
- data/lib/ocran/cosmo_toolchain.rb +383 -0
- data/lib/ocran/dir_builder.rb +3 -0
- data/lib/ocran/direction.rb +819 -64
- 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 +172 -3
- data/lib/ocran/rubyopt_processor.rb +95 -0
- data/lib/ocran/runner.rb +84 -2
- data/lib/ocran/runtime_environment.rb +19 -1
- data/lib/ocran/stub_builder.rb +38 -6
- data/lib/ocran/version.rb +1 -1
- data/lib/ocran/zip_payload_builder.rb +282 -0
- data/lib/ocran/zip_writer.rb +287 -0
- data/src/Makefile +9 -0
- data/src/inst_dir.c +44 -2
- data/src/inst_dir.h +14 -0
- data/src/script_info.c +15 -2
- data/src/script_info.h +15 -1
- data/src/stub.c +70 -9
- data/src/system_utils.c +26 -0
- data/src/system_utils.h +14 -0
- data/src/system_utils_posix.c +46 -0
- data/src/unpack.c +8 -0
- data/src/unpack.h +19 -0
- metadata +15 -24
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,26 +18,123 @@ 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
|
|
|
29
|
+
# Packed name of the interpreter when a cosmopolitan Ruby payload is
|
|
30
|
+
# used (--cosmo-ruby); the source APE is packed under this name.
|
|
31
|
+
COSMO_RUBY_EXE = "ruby.com"
|
|
32
|
+
|
|
33
|
+
# File name extensions of loadable native binaries that a gem may
|
|
34
|
+
# ship or build. None of them can be loaded by the cosmopolitan Ruby
|
|
35
|
+
# payload, which is statically linked and has no dlopen.
|
|
36
|
+
NATIVE_BINARY_EXTENSIONS = %w[.so .bundle .dll].freeze
|
|
37
|
+
|
|
38
|
+
# Native binaries a gem ships or has had built for it, i.e. the files
|
|
39
|
+
# that make it unusable under the cosmopolitan Ruby payload.
|
|
40
|
+
#
|
|
41
|
+
# spec.extensions alone does NOT identify a native gem: a precompiled
|
|
42
|
+
# platform gem (e.g. sqlite3-2.9.5-x86_64-linux-gnu) has an empty
|
|
43
|
+
# extensions array because nothing is compiled at install time, yet it
|
|
44
|
+
# ships a prebuilt sqlite3_native.so inside its lib directory. Both the
|
|
45
|
+
# gem directory and the extension directory (where RubyGems puts the
|
|
46
|
+
# products of a source build) are scanned.
|
|
47
|
+
def self.gem_native_binaries(spec)
|
|
48
|
+
ext_dir =
|
|
49
|
+
begin
|
|
50
|
+
spec.extension_dir
|
|
51
|
+
rescue StandardError
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
pattern = "**/*{#{NATIVE_BINARY_EXTENSIONS.join(",")}}"
|
|
55
|
+
[spec.gem_dir, ext_dir].compact.uniq.flat_map { |dir|
|
|
56
|
+
next [] unless File.directory?(dir)
|
|
57
|
+
|
|
58
|
+
Dir.glob(pattern, base: dir).map { |rel| Pathname(File.join(dir, rel)) }
|
|
59
|
+
}.uniq
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The feature names a gem is required by, i.e. what has to answer
|
|
63
|
+
# inside the payload for the payload's own copy to serve in place of
|
|
64
|
+
# the host gem. RubyGems' convention maps a dash in a gem name to a
|
|
65
|
+
# directory separator in its primary feature (io-console provides
|
|
66
|
+
# "io/console"), and both spellings are seen in the wild, so both are
|
|
67
|
+
# offered as candidates.
|
|
68
|
+
def self.cosmo_gem_features(spec)
|
|
69
|
+
[spec.name, spec.name.tr("-", "/")].uniq
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Decides how a gem detected on the build host has to be treated when
|
|
73
|
+
# a cosmopolitan Ruby payload is packed (--cosmo-ruby). Returns a pair
|
|
74
|
+
# of a disposition and the gem's native binaries:
|
|
75
|
+
#
|
|
76
|
+
# [:pack, []] pure Ruby gem, pack it as usual
|
|
77
|
+
# [:payload_provides, files] native, but the payload provides the
|
|
78
|
+
# same library itself: skip the host copy
|
|
79
|
+
# and let the payload's own version serve
|
|
80
|
+
# [:incompatible, files] native and not provided by the payload:
|
|
81
|
+
# the build must fail
|
|
82
|
+
#
|
|
83
|
+
# A gem counts as provided by the payload when the payload has a
|
|
84
|
+
# gemspec of that name, OR when the payload can resolve the gem's
|
|
85
|
+
# primary feature (+provides_feature+, normally
|
|
86
|
+
# CosmoToolchain.resolvable_features against the payload). The second
|
|
87
|
+
# test matters because a gemspec is not what makes a library
|
|
88
|
+
# requirable: an extension statically linked into the APE, or a
|
|
89
|
+
# library in its embedded stdlib rather than in /zip/lib/ruby/gems,
|
|
90
|
+
# answers require with no gemspec at all. Keying only on gemspec names
|
|
91
|
+
# reports such a library as incompatible and refuses a build that would
|
|
92
|
+
# have worked - which is what happens to cgi and pathname, both
|
|
93
|
+
# compiled into the interpreter and both regular native gems on a
|
|
94
|
+
# recent host Ruby.
|
|
95
|
+
def self.cosmo_gem_disposition(spec, payload_gem_names, provides_feature = nil)
|
|
96
|
+
native_files = gem_native_binaries(spec)
|
|
97
|
+
return [:pack, native_files] if spec.extensions.empty? && native_files.empty?
|
|
98
|
+
|
|
99
|
+
if payload_gem_names.include?(spec.name) ||
|
|
100
|
+
(provides_feature && provides_feature.call(cosmo_gem_features(spec)))
|
|
101
|
+
[:payload_provides, native_files]
|
|
102
|
+
else
|
|
103
|
+
[:incompatible, native_files]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Human readable reason why a gem counts as native, for build messages.
|
|
108
|
+
def self.cosmo_native_reason(spec, native_files)
|
|
109
|
+
reasons = []
|
|
110
|
+
reasons << "declares native extensions" if spec.extensions.any?
|
|
111
|
+
if native_files.any?
|
|
112
|
+
names = native_files.map { |file| File.basename(file) }.uniq
|
|
113
|
+
reasons << "ships prebuilt binaries (#{names.join(", ")})"
|
|
114
|
+
end
|
|
115
|
+
reasons.join(" and ")
|
|
116
|
+
end
|
|
117
|
+
|
|
22
118
|
attr_reader :ruby_executable, :rubyopt
|
|
23
119
|
|
|
24
120
|
def initialize(post_env, pre_env, option)
|
|
25
121
|
@post_env, @pre_env, @option = post_env, pre_env, option
|
|
26
|
-
@ruby_executable =
|
|
122
|
+
@ruby_executable =
|
|
123
|
+
if @option.cosmo_ruby
|
|
124
|
+
COSMO_RUBY_EXE
|
|
125
|
+
else
|
|
126
|
+
@option.windowed? ? rubyw_exe : ruby_exe
|
|
127
|
+
end
|
|
27
128
|
|
|
28
129
|
# Initializes @rubyopt with the user-intended RUBYOPT environment variable.
|
|
29
130
|
# This ensures that RUBYOPT matches the user's initial settings before any
|
|
30
131
|
# modifications that may occur during script execution.
|
|
132
|
+
#
|
|
133
|
+
# -I and -r entries that refer to build-machine paths (including the
|
|
134
|
+
# `-r<abs path>/bundler/setup` that Bundler adds under `bundle exec`
|
|
135
|
+
# since Ruby 3.2) are translated or removed at build time by
|
|
136
|
+
# RubyoptProcessor in #construct.
|
|
31
137
|
@rubyopt = @option.rubyopt || pre_env.env["RUBYOPT"] || ""
|
|
32
|
-
|
|
33
|
-
# Remove any absolute path to bundler/setup from RUBYOPT.
|
|
34
|
-
# When building under `bundle exec`, RUBYOPT contains `-r/absolute/path/bundler/setup`.
|
|
35
|
-
# That path doesn't exist inside the packed executable's environment, causing Ruby to
|
|
36
|
-
# print "RubyGems were not loaded" / "did_you_mean was not loaded" warnings on startup.
|
|
37
|
-
# We strip the flag regardless of install prefix because the gem may live in a user gem
|
|
38
|
-
# directory that doesn't share a prefix with RbConfig::TOPDIR (e.g. on CI runners).
|
|
39
|
-
@rubyopt = @rubyopt.gsub(/-r\S*\/bundler\/setup/, "").strip
|
|
40
138
|
end
|
|
41
139
|
|
|
42
140
|
# Resolves the common root directory prefix from an array of absolute paths.
|
|
@@ -65,7 +163,16 @@ module Ocran
|
|
|
65
163
|
|
|
66
164
|
def detect_dlls
|
|
67
165
|
if Gem.win_platform?
|
|
68
|
-
|
|
166
|
+
begin
|
|
167
|
+
require_relative "library_detector"
|
|
168
|
+
rescue LoadError => e
|
|
169
|
+
# LibraryDetector needs fiddle, a bundled gem since Ruby 3.5. In a
|
|
170
|
+
# Bundler context (e.g. building with --gemfile) requiring it is
|
|
171
|
+
# refused unless the Gemfile lists fiddle, so degrade to no DLL
|
|
172
|
+
# auto-detection instead of aborting the build.
|
|
173
|
+
warning "DLL auto-detection disabled (#{e.message}). Add fiddle to the Gemfile, or use --dll to include DLLs manually."
|
|
174
|
+
return []
|
|
175
|
+
end
|
|
69
176
|
else
|
|
70
177
|
require_relative "library_detector_posix"
|
|
71
178
|
end
|
|
@@ -84,7 +191,8 @@ module Ocran
|
|
|
84
191
|
end
|
|
85
192
|
end
|
|
86
193
|
if defined?(Gem)
|
|
87
|
-
|
|
194
|
+
foreign = foreign_bundle_gem_names
|
|
195
|
+
specs += Gem.loaded_specs.each_value.reject { |spec| foreign.include?(spec.name) }
|
|
88
196
|
# Now, we also detect gems that are not included in Gem.loaded_specs.
|
|
89
197
|
# Therefore, we look for any loaded file from a gem path.
|
|
90
198
|
specs += GemSpecQueryable.detect_gems_from(features, verbose: @option.verbose?)
|
|
@@ -94,6 +202,83 @@ module Ocran
|
|
|
94
202
|
specs
|
|
95
203
|
end
|
|
96
204
|
|
|
205
|
+
# The gems RubyGems had already activated for a bundle that is not the
|
|
206
|
+
# application's, by the time OCRAN started.
|
|
207
|
+
#
|
|
208
|
+
# `bundle exec` activates every gem of its bundle before the command it
|
|
209
|
+
# runs executes a single line, so Gem.loaded_specs describes the build
|
|
210
|
+
# environment as much as the application. When the two bundles are the
|
|
211
|
+
# same - `bundle exec ocran app.rb` from the application's own directory,
|
|
212
|
+
# the ordinary case - that is exactly right and nothing is dropped. When
|
|
213
|
+
# they differ, packing the build environment's bundle adds tens of
|
|
214
|
+
# megabytes of code the application can never load: the packaged app runs
|
|
215
|
+
# under its own Gemfile, so gems from a foreign bundle are dead weight
|
|
216
|
+
# even when they are packed.
|
|
217
|
+
#
|
|
218
|
+
# Only activation is discounted, not use: anything the dependency run
|
|
219
|
+
# actually loaded is still found through $LOADED_FEATURES by
|
|
220
|
+
# detect_gems_from, and everything the application's Gemfile names is
|
|
221
|
+
# added by the Gemfile scan. This is the same rule that already applies
|
|
222
|
+
# to a build outside Bundler, where --gemfile is what pulls in gems the
|
|
223
|
+
# dependency run does not load.
|
|
224
|
+
def foreign_bundle_gem_names
|
|
225
|
+
@foreign_bundle_gem_names ||=
|
|
226
|
+
if foreign_build_bundle?
|
|
227
|
+
verbose "Ignoring #{@pre_env.activated_gems.size} gems activated by the build environment's bundle " \
|
|
228
|
+
"#{@pre_env.env["BUNDLE_GEMFILE"]}"
|
|
229
|
+
@pre_env.activated_gems.to_set
|
|
230
|
+
else
|
|
231
|
+
Set.new
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Whether OCRAN itself was started under a bundle other than the one the
|
|
236
|
+
# application runs under.
|
|
237
|
+
def foreign_build_bundle?
|
|
238
|
+
return false unless @pre_env.bundler_setup_loaded?
|
|
239
|
+
|
|
240
|
+
build_gemfile = @pre_env.env["BUNDLE_GEMFILE"]
|
|
241
|
+
return false if build_gemfile.nil? || build_gemfile.empty?
|
|
242
|
+
|
|
243
|
+
app_gemfile = @option.application_gemfile
|
|
244
|
+
return true if app_gemfile.nil?
|
|
245
|
+
|
|
246
|
+
!same_file?(build_gemfile, app_gemfile)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def same_file?(a, b)
|
|
250
|
+
File.identical?(a, b) || File.expand_path(a) == File.expand_path(b)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Packed name of the file BUNDLER_SETUP is pointed at.
|
|
254
|
+
BUNDLER_SETUP_NOOP = Pathname("no_bundler_setup.rb")
|
|
255
|
+
|
|
256
|
+
# Keeps the environment of whoever launches the packaged application
|
|
257
|
+
# from dragging Bundler into it.
|
|
258
|
+
#
|
|
259
|
+
# A packaged application carries its own gems and its own Gemfile; the
|
|
260
|
+
# bundle of the machine it is started from means nothing to it, and
|
|
261
|
+
# anything of that bundle that survives into the process is fatal rather
|
|
262
|
+
# than merely wrong - Bundler aborts with GemNotFound as soon as it
|
|
263
|
+
# cannot materialize gems that were never packed. RUBYOPT is already
|
|
264
|
+
# overwritten wholesale, but that alone stopped being enough: RubyGems
|
|
265
|
+
# now requires the file named by BUNDLER_SETUP at interpreter startup,
|
|
266
|
+
# which is how current Bundler versions set a process up, and
|
|
267
|
+
# BUNDLE_GEMFILE would still send the application's own
|
|
268
|
+
# `require "bundler/setup"` at the wrong Gemfile.
|
|
269
|
+
#
|
|
270
|
+
# BUNDLER_SETUP names a file to require, so it cannot simply be blanked
|
|
271
|
+
# - an empty value is still truthy and RubyGems would raise trying to
|
|
272
|
+
# require it. It is pointed at an empty packed file instead. Bundler
|
|
273
|
+
# does treat empty BUNDLE_GEMFILE and BUNDLE_LOCKFILE as unset, which is
|
|
274
|
+
# what lets the application find the Gemfile packed beside it.
|
|
275
|
+
def neutralize_bundler_env(builder)
|
|
276
|
+
builder.touch(BUNDLER_SETUP_NOOP)
|
|
277
|
+
builder.set_env_path("BUNDLER_SETUP", BUNDLER_SETUP_NOOP)
|
|
278
|
+
builder.export("BUNDLE_GEMFILE", "")
|
|
279
|
+
builder.export("BUNDLE_LOCKFILE", "")
|
|
280
|
+
end
|
|
281
|
+
|
|
97
282
|
def normalized_features
|
|
98
283
|
features = @post_env.loaded_features.map { |feature| Pathname(feature) }
|
|
99
284
|
|
|
@@ -107,10 +292,12 @@ module Ocran
|
|
|
107
292
|
# because rubygems.rb uses require_relative to load it.
|
|
108
293
|
kernel_require_rel = "rubygems/core_ext/kernel_require.rb"
|
|
109
294
|
unless features.any? { |f| f.to_posix.end_with?(kernel_require_rel) }
|
|
110
|
-
# Prefer the location alongside the actually-loaded rubygems.rb, fall back to
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
295
|
+
# Prefer the location alongside the actually-loaded rubygems.rb, fall back to
|
|
296
|
+
# rubylibdir. Consider every feature ending in "/rubygems.rb", because a plain
|
|
297
|
+
# suffix match can also hit unrelated files such as bundler's
|
|
298
|
+
# lib/bundler/source/rubygems.rb (loaded before rubygems.rb under bundle exec);
|
|
299
|
+
# the existence check below skips candidates without the core_ext file.
|
|
300
|
+
candidate_dirs = features.select { |f| f.to_posix.end_with?("/rubygems.rb") }.map(&:dirname)
|
|
114
301
|
candidate_dirs << Pathname(RbConfig::CONFIG["rubylibdir"])
|
|
115
302
|
candidate_dirs.each do |base_dir|
|
|
116
303
|
kernel_require_path = base_dir / kernel_require_rel
|
|
@@ -140,17 +327,54 @@ module Ocran
|
|
|
140
327
|
end
|
|
141
328
|
end
|
|
142
329
|
|
|
330
|
+
# True when the packed cosmopolitan Ruby can resolve any of the given
|
|
331
|
+
# features itself, i.e. when a host gem providing them does not have
|
|
332
|
+
# to be packed. Answers are memoized per feature: probing costs one
|
|
333
|
+
# run of the payload interpreter, and only native gems the payload has
|
|
334
|
+
# no gemspec for ever get here.
|
|
335
|
+
def cosmo_payload_provides_feature?(features)
|
|
336
|
+
@cosmo_feature_cache ||= {}
|
|
337
|
+
unknown = features.reject { |feature| @cosmo_feature_cache.key?(feature) }
|
|
338
|
+
unless unknown.empty?
|
|
339
|
+
resolved = CosmoToolchain.resolvable_features(@option.cosmo_ruby, unknown)
|
|
340
|
+
unknown.each { |feature| @cosmo_feature_cache[feature] = resolved.include?(feature) }
|
|
341
|
+
end
|
|
342
|
+
features.any? { |feature| @cosmo_feature_cache[feature] }
|
|
343
|
+
end
|
|
344
|
+
|
|
143
345
|
def construct(builder)
|
|
144
346
|
# Store the currently loaded files
|
|
145
347
|
features = normalized_features
|
|
146
348
|
|
|
349
|
+
# With --cosmo-ruby, run the payload interpreter once on the build
|
|
350
|
+
# host: this validates that it works, provides its embedded gem
|
|
351
|
+
# directory (needed for GEM_PATH below) and its version for the
|
|
352
|
+
# host-vs-payload skew warning. Dependency detection has already
|
|
353
|
+
# run under the *host* Ruby, so stdlib/gem resolution may differ
|
|
354
|
+
# when the versions diverge.
|
|
355
|
+
if @option.cosmo_ruby
|
|
356
|
+
# Kernel#load, matching Option#load_cosmo_toolchain: the file may
|
|
357
|
+
# already have been loaded that way at option-parse time, and
|
|
358
|
+
# require_relative would then run it a second time.
|
|
359
|
+
load File.expand_path("cosmo_toolchain.rb", __dir__) unless defined? CosmoToolchain
|
|
360
|
+
@cosmo_ruby_info = CosmoToolchain.query_ruby(@option.cosmo_ruby)
|
|
361
|
+
say "Packaging cosmopolitan Ruby #{@cosmo_ruby_info[:version]} (#{@option.cosmo_ruby})"
|
|
362
|
+
if RUBY_VERSION.split(".").take(2) != @cosmo_ruby_info[:version].split(".").take(2)
|
|
363
|
+
warning "Dependency detection ran under the host Ruby #{RUBY_VERSION}, but the packed cosmopolitan Ruby is #{@cosmo_ruby_info[:version]}; stdlib and gem behavior may differ between these versions"
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
147
367
|
# If net/http was loaded but openssl wasn't (it is only required lazily
|
|
148
368
|
# at the point of an actual HTTPS connection), require it now inside the
|
|
149
369
|
# OCRAN build process so that every transitive dependency — openssl.rb,
|
|
150
370
|
# digest.so, and any other files pulled in by the extension — appears in
|
|
151
371
|
# $LOADED_FEATURES and gets bundled alongside the application.
|
|
372
|
+
# Skipped with --cosmo-ruby: the payload interpreter carries its own
|
|
373
|
+
# (statically linked) openssl, and the host's files would be excluded
|
|
374
|
+
# from the package anyway.
|
|
152
375
|
openssl_so = Pathname(RbConfig::CONFIG["archdir"]) / "openssl.so"
|
|
153
|
-
if
|
|
376
|
+
if !@option.cosmo_ruby &&
|
|
377
|
+
openssl_so.exist? &&
|
|
154
378
|
features.any? { |f| f.to_posix.end_with?("/net/http.rb") } &&
|
|
155
379
|
features.none? { |f| f == openssl_so }
|
|
156
380
|
say "Auto-loading openssl (net/http loaded but openssl not yet required)"
|
|
@@ -168,40 +392,94 @@ module Ocran
|
|
|
168
392
|
|
|
169
393
|
# Add the ruby executable and DLL
|
|
170
394
|
say "Adding ruby executable #{ruby_executable}"
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
#
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
395
|
+
if @option.cosmo_ruby
|
|
396
|
+
# The cosmopolitan Ruby APE is fully self-contained: a static
|
|
397
|
+
# binary with the standard library embedded in its ZIP store
|
|
398
|
+
# (/zip/lib/ruby/...). No libruby, no shared libraries and no
|
|
399
|
+
# LD_LIBRARY_PATH are needed — pack the single file and be done.
|
|
400
|
+
#
|
|
401
|
+
# Except in ZIP packaging mode, where the output IS that binary and
|
|
402
|
+
# the application is injected into it: packing a copy of the
|
|
403
|
+
# interpreter into itself would double the size of the executable
|
|
404
|
+
# for nothing.
|
|
405
|
+
if @option.cosmo_zip?
|
|
406
|
+
say "Injecting the application into the ZIP store of #{@option.cosmo_ruby}"
|
|
407
|
+
else
|
|
408
|
+
builder.copy_to_bin(Pathname(@option.cosmo_ruby), ruby_executable)
|
|
409
|
+
end
|
|
410
|
+
else
|
|
411
|
+
ruby_source = bindir / ruby_executable
|
|
412
|
+
if !Gem.win_platform? && File.binread(ruby_source, 2) == "#!"
|
|
413
|
+
# On some distros (e.g. Fedora), bindir/ruby is a dispatcher shell
|
|
414
|
+
# script ("rubypick") rather than the interpreter itself, which
|
|
415
|
+
# cannot run on a system without Ruby. Pack the currently running
|
|
416
|
+
# interpreter binary under the expected name instead.
|
|
417
|
+
real_ruby = Pathname("/proc/self/exe")
|
|
418
|
+
raise "#{ruby_source} is a wrapper script and the real interpreter could not be determined" unless real_ruby.exist?
|
|
419
|
+
|
|
420
|
+
say "#{ruby_source} is a wrapper script; packing #{real_ruby.realpath} instead"
|
|
421
|
+
builder.copy_to_bin(real_ruby.realpath, ruby_executable)
|
|
422
|
+
else
|
|
423
|
+
builder.copy_to_bin(ruby_source, ruby_executable)
|
|
424
|
+
end
|
|
425
|
+
if libruby_so
|
|
426
|
+
# On POSIX systems, libruby.so is in libdir; on Windows, it's in bindir
|
|
427
|
+
libruby_src = Gem.win_platform? ? bindir / libruby_so : libdir / libruby_so
|
|
428
|
+
builder.copy_to_bin(libruby_src, libruby_so)
|
|
429
|
+
|
|
430
|
+
# On POSIX systems, create symlinks (aliases) for libruby.so
|
|
431
|
+
unless Gem.win_platform?
|
|
432
|
+
libruby_aliases.each do |libruby_alias|
|
|
433
|
+
builder.symlink_in_bin(libruby_so, libruby_alias)
|
|
434
|
+
end
|
|
181
435
|
end
|
|
182
436
|
end
|
|
183
|
-
end
|
|
184
437
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
438
|
+
# On POSIX systems, set LD_LIBRARY_PATH to find bundled shared libraries
|
|
439
|
+
unless Gem.win_platform?
|
|
440
|
+
extract_bin = File.join(EXTRACT_ROOT, BINDIR.to_s)
|
|
441
|
+
builder.export("LD_LIBRARY_PATH", extract_bin)
|
|
442
|
+
if RUBY_PLATFORM.include?("darwin")
|
|
443
|
+
builder.export("DYLD_LIBRARY_PATH", extract_bin)
|
|
444
|
+
end
|
|
191
445
|
end
|
|
192
446
|
end
|
|
193
447
|
|
|
194
448
|
# Windows-only: Add detected DLLs
|
|
195
449
|
if Gem.win_platform? && @option.auto_detect_dlls?
|
|
450
|
+
# The Windows loader resolves the imports of a native extension from
|
|
451
|
+
# the extension's own directory, the application directory of the
|
|
452
|
+
# packed ruby.exe (bin) plus its SxS assembly (ruby_builtin_dlls),
|
|
453
|
+
# and the system directories. PATH is not consulted on hardened
|
|
454
|
+
# systems, and the AddDllDirectory mechanism gems use through
|
|
455
|
+
# ruby_installer/runtime is not available in a packed app. A detected
|
|
456
|
+
# DLL that lives anywhere else - a gem's bundled library (e.g.
|
|
457
|
+
# FreeTDS under tiny_tds' ports/), a devkit's msys64 tree inside the
|
|
458
|
+
# Ruby prefix, or a directory outside the prefix entirely - is packed
|
|
459
|
+
# only at a location the loader never searches, and the application
|
|
460
|
+
# dies with LoadError on machines where nothing masks the gap. So
|
|
461
|
+
# additionally bundle a copy of every such DLL into bin, next to
|
|
462
|
+
# ruby.exe, mirroring what the Linux branch below achieves with
|
|
463
|
+
# LD_LIBRARY_PATH. DLLs from the Windows directory always come from
|
|
464
|
+
# the target system and are never bundled.
|
|
465
|
+
windows_dir = Pathname(ENV["SystemRoot"] || "C:/Windows")
|
|
466
|
+
dlls_in_bin = Set.new
|
|
467
|
+
add_dll_to_bin = proc do |dll|
|
|
468
|
+
next if dll.subpath?(bindir) || !dlls_in_bin.add?(dll.basename.to_s.downcase)
|
|
469
|
+
|
|
470
|
+
say "Adding detected DLL #{dll} to bin"
|
|
471
|
+
builder.copy_to_bin(dll, dll.basename)
|
|
472
|
+
end
|
|
473
|
+
|
|
196
474
|
detect_dlls.each do |dll|
|
|
197
|
-
next unless dll.
|
|
475
|
+
next unless dll.extname?(".dll") && dll.basename != libruby_so
|
|
476
|
+
next if dll.subpath?(windows_dir)
|
|
198
477
|
|
|
199
|
-
say "Adding detected DLL #{dll}"
|
|
200
478
|
if dll.subpath?(exec_prefix)
|
|
479
|
+
say "Adding detected DLL #{dll}"
|
|
201
480
|
builder.duplicate_to_exec_prefix(dll)
|
|
202
|
-
else
|
|
203
|
-
builder.copy_to_bin(dll, dll.basename)
|
|
204
481
|
end
|
|
482
|
+
add_dll_to_bin.call(dll)
|
|
205
483
|
end
|
|
206
484
|
|
|
207
485
|
# Proactively include companion DLLs for loaded native extensions.
|
|
@@ -209,7 +487,10 @@ module Ocran
|
|
|
209
487
|
# directory (e.g., libssl-3-x64.dll alongside openssl.so) that are
|
|
210
488
|
# loaded lazily on first use. Scanning .so directories ensures those
|
|
211
489
|
# DLLs are bundled even when the extension is required but not
|
|
212
|
-
# exercised during the OCRAN dependency scan.
|
|
490
|
+
# exercised during the OCRAN dependency scan. They also go into bin:
|
|
491
|
+
# a copy in archdir only helps extensions in archdir itself, while
|
|
492
|
+
# the same extension packed at a gem path (openssl and psych are
|
|
493
|
+
# gems since Ruby 3.x) resolves its imports from bin.
|
|
213
494
|
features.select { |f| f.extname?(".so") && f.subpath?(exec_prefix) }
|
|
214
495
|
.map(&:dirname).uniq
|
|
215
496
|
.each do |dir|
|
|
@@ -217,6 +498,45 @@ module Ocran
|
|
|
217
498
|
next unless path.file? && path.extname?(".dll")
|
|
218
499
|
say "Adding companion DLL #{path}"
|
|
219
500
|
builder.duplicate_to_exec_prefix(path)
|
|
501
|
+
add_dll_to_bin.call(path)
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# Linux: bundle detected shared libraries (e.g. libyaml, libssl,
|
|
507
|
+
# libcrypt) next to libruby so the executable also runs on systems
|
|
508
|
+
# where they are missing or have different sonames (the stub already
|
|
509
|
+
# points LD_LIBRARY_PATH at the packed bin directory). Core glibc
|
|
510
|
+
# libraries and the loader are never bundled - they must come from the
|
|
511
|
+
# target system. Ruby native extensions are packed as features, not
|
|
512
|
+
# here. Not needed with --cosmo-ruby: the APE payload is static.
|
|
513
|
+
if RUBY_PLATFORM.include?("linux") && @option.auto_detect_dlls? && !@option.cosmo_ruby
|
|
514
|
+
feature_set = features.to_set
|
|
515
|
+
feature_realpaths = features.filter_map { |f| f.realpath rescue nil }.to_set
|
|
516
|
+
# Ruby native extensions live in these directories and are packed as
|
|
517
|
+
# features; they must not be duplicated into bin.
|
|
518
|
+
ruby_arch_dirs = RbConfig::CONFIG.values_at("archdir", "sitearchdir", "vendorarchdir")
|
|
519
|
+
.compact.map { |dir| Pathname(dir) }
|
|
520
|
+
detect_dlls.each do |dll|
|
|
521
|
+
basename = dll.basename.to_s
|
|
522
|
+
next unless basename.match?(/\.so(\.|\z)/)
|
|
523
|
+
next if basename.match?(LINUX_SYSTEM_LIBRARY_RE)
|
|
524
|
+
# libruby is packed with its aliases already
|
|
525
|
+
next if basename.start_with?("libruby")
|
|
526
|
+
next if feature_set.include?(dll) || feature_realpaths.include?(dll)
|
|
527
|
+
next if ruby_arch_dirs.any? { |dir| dll.subpath?(dir) } || dll.to_posix.match?(%r{/gems/})
|
|
528
|
+
next unless dll.file?
|
|
529
|
+
|
|
530
|
+
say "Adding detected shared library #{dll}"
|
|
531
|
+
builder.copy_to_bin(dll, basename)
|
|
532
|
+
# The loader may request a library by a less specific soname than
|
|
533
|
+
# the fully versioned file name recorded in the memory map (e.g.
|
|
534
|
+
# libssl.so.3 for libssl.so.3.2.4). Provide symlink aliases for
|
|
535
|
+
# each shorter version suffix.
|
|
536
|
+
alias_name = basename
|
|
537
|
+
while (shorter = alias_name.sub(/\.\d+[^.]*\z/, "")) != alias_name && shorter.include?(".so")
|
|
538
|
+
builder.symlink_in_bin(basename, shorter)
|
|
539
|
+
alias_name = shorter
|
|
220
540
|
end
|
|
221
541
|
end
|
|
222
542
|
end
|
|
@@ -261,6 +581,11 @@ module Ocran
|
|
|
261
581
|
end
|
|
262
582
|
end
|
|
263
583
|
|
|
584
|
+
# Gem directories whose packing was skipped because the cosmopolitan
|
|
585
|
+
# Ruby payload provides the gem itself; loaded features from these
|
|
586
|
+
# directories must not be packed either.
|
|
587
|
+
cosmo_skipped_gem_dirs = []
|
|
588
|
+
|
|
264
589
|
# Searches for features that are loaded from gems, then produces a
|
|
265
590
|
# list of files included in those gems' manifests. Also returns a
|
|
266
591
|
# list of original features that caused those gems to be included.
|
|
@@ -275,13 +600,119 @@ module Ocran
|
|
|
275
600
|
next []
|
|
276
601
|
end
|
|
277
602
|
|
|
603
|
+
if @option.cosmo_ruby
|
|
604
|
+
# Default gems of the *host* Ruby are part of its stdlib; the
|
|
605
|
+
# cosmopolitan Ruby ships its own stdlib and default/bundled
|
|
606
|
+
# gems in its embedded ZIP store, so do not pack them (a host
|
|
607
|
+
# 3.x copy would shadow the payload's version).
|
|
608
|
+
if spec.respond_to?(:default_gem?) && spec.default_gem?
|
|
609
|
+
verbose "Skipping default gem #{spec.full_name} (provided by the cosmopolitan Ruby's embedded stdlib)"
|
|
610
|
+
next []
|
|
611
|
+
end
|
|
612
|
+
# Native gems compile (or were precompiled) against a host Ruby
|
|
613
|
+
# ABI and platform; they cannot load under the x86_64-cosmo
|
|
614
|
+
# payload, which is statically linked and cannot dlopen. This
|
|
615
|
+
# covers both source-installed gems (spec.extensions) and
|
|
616
|
+
# precompiled platform gems, which declare no extensions but
|
|
617
|
+
# ship their .so inside the gem directory.
|
|
618
|
+
# When the payload provides the same gem itself (e.g. json,
|
|
619
|
+
# psych are statically linked into the APE), skip the host copy
|
|
620
|
+
# so the payload's own version is used — packing the host .rb
|
|
621
|
+
# files would shadow the payload's and could mismatch the
|
|
622
|
+
# linked-in C extension. Otherwise fail clearly rather than
|
|
623
|
+
# produce a broken executable.
|
|
624
|
+
disposition, native_files = self.class.cosmo_gem_disposition(
|
|
625
|
+
spec, @cosmo_ruby_info[:gem_names], method(:cosmo_payload_provides_feature?)
|
|
626
|
+
)
|
|
627
|
+
if disposition != :pack
|
|
628
|
+
reason = self.class.cosmo_native_reason(spec, native_files)
|
|
629
|
+
if disposition == :payload_provides
|
|
630
|
+
provided =
|
|
631
|
+
if @cosmo_ruby_info[:gem_names].include?(spec.name)
|
|
632
|
+
"its own #{spec.name}"
|
|
633
|
+
else
|
|
634
|
+
"#{spec.name} without a gemspec (linked in, or part of its embedded stdlib)"
|
|
635
|
+
end
|
|
636
|
+
say "Skipping native gem #{spec.full_name} (#{reason}): the cosmopolitan Ruby provides #{provided}"
|
|
637
|
+
cosmo_skipped_gem_dirs << Pathname(spec.gem_dir) if File.directory?(spec.gem_dir)
|
|
638
|
+
ext_dir =
|
|
639
|
+
begin
|
|
640
|
+
spec.extension_dir
|
|
641
|
+
rescue StandardError
|
|
642
|
+
nil
|
|
643
|
+
end
|
|
644
|
+
cosmo_skipped_gem_dirs << Pathname(ext_dir) if ext_dir && File.directory?(ext_dir)
|
|
645
|
+
next []
|
|
646
|
+
end
|
|
647
|
+
raise "Gem #{spec.full_name} is native (#{reason}) and cannot run under the packed cosmopolitan Ruby (x86_64-cosmo, static): exclude the gem or package without --cosmo-ruby"
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
|
|
278
651
|
# Add gemspec files
|
|
652
|
+
local_gem_dir = nil
|
|
279
653
|
if spec_file.subpath?(exec_prefix)
|
|
280
654
|
builder.duplicate_to_exec_prefix(spec_file)
|
|
281
655
|
elsif (gem_path = GemSpecQueryable.find_gem_path(spec_file))
|
|
282
656
|
builder.duplicate_to_gem_home(spec_file, gem_path)
|
|
283
657
|
else
|
|
284
|
-
|
|
658
|
+
# Local development gems (Bundler `gemspec` or `path:` directives)
|
|
659
|
+
# keep their gemspec inside the project tree, outside both the Ruby
|
|
660
|
+
# installation and every gem path, so there is no installed gem
|
|
661
|
+
# layout to mirror. Pack them into GEMDIR as if they were installed
|
|
662
|
+
# there: generate the spec from the in-memory specification (the
|
|
663
|
+
# on-disk gemspec often uses dynamic constructs such as
|
|
664
|
+
# `git ls-files` that would fail in the packed app) and pack the
|
|
665
|
+
# gem's files under gems/<full_name>/ below.
|
|
666
|
+
say "Including local development gem #{spec.full_name} from #{spec_file.dirname}"
|
|
667
|
+
local_gem_dir = Pathname(spec.gem_dir)
|
|
668
|
+
builder.copy_to_gem(generate_gemspec_file(spec), Pathname("specifications") / "#{spec.full_name}.gemspec")
|
|
669
|
+
# RubyGems refuses to activate a gem with extensions unless its
|
|
670
|
+
# gem.build_complete marker exists. The extension files themselves
|
|
671
|
+
# are packed via the loaded features or the extension-dir mirroring
|
|
672
|
+
# below.
|
|
673
|
+
if spec.extensions.any?
|
|
674
|
+
api_version = Gem.respond_to?(:extension_api_version) ? Gem.extension_api_version : Gem.ruby_api_version
|
|
675
|
+
builder.touch(GEMDIR / "extensions" / Gem::Platform.local.to_s / api_version / spec.full_name / "gem.build_complete")
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
spec_dir = spec_file.dirname
|
|
680
|
+
default_spec = spec_dir.basename.to_s == "default"
|
|
681
|
+
# The gem's base directory: specs live in <base_dir>/specifications[/default]
|
|
682
|
+
base_dir = default_spec ? spec_dir.dirname.dirname : spec_dir.dirname
|
|
683
|
+
# Relative packed location of base_dir; nil when the gem lives outside
|
|
684
|
+
# the Ruby prefix, in which case its files are packed under GEMDIR
|
|
685
|
+
# with the same layout.
|
|
686
|
+
packed_base = base_dir.subpath?(exec_prefix) ? base_dir.relative_path_from(exec_prefix) : GEMDIR
|
|
687
|
+
|
|
688
|
+
# Default gemspecs live in a "specifications/default" directory, which
|
|
689
|
+
# RubyGems only scans under Gem.default_dir. On rubies with a
|
|
690
|
+
# compiled-in absolute prefix (e.g. distro Ruby on Fedora), that path
|
|
691
|
+
# points to the build host and does not exist on the target system, so
|
|
692
|
+
# activating such a gem (e.g. via a binstub) fails. Also place a copy
|
|
693
|
+
# in the regular specifications directory of the same packed base
|
|
694
|
+
# directory, where GEM_PATH makes it discoverable and its require
|
|
695
|
+
# paths resolve to the packed gem files.
|
|
696
|
+
if !Gem.win_platform? && default_spec
|
|
697
|
+
builder.cp(spec_file, packed_base / "specifications" / spec_file.basename)
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# Native-extension gems record successful builds in a gem.build_complete
|
|
701
|
+
# marker under <base_dir>/extensions/...; without it RubyGems refuses to
|
|
702
|
+
# activate the gem ("Ignoring x because its extensions are not built").
|
|
703
|
+
# The marker can be absent from the packed tree either because the
|
|
704
|
+
# distro relocates it (e.g. Fedora keeps it under /usr/lib64/gems/ruby
|
|
705
|
+
# via a default_ext_dir_for override) or because the gem is a default
|
|
706
|
+
# gem, which is exempt from the check on the host while its packed
|
|
707
|
+
# regular-spec copy is not. Recreate the marker where RubyGems expects
|
|
708
|
+
# it at runtime.
|
|
709
|
+
if !Gem.win_platform? && spec.extensions.any?
|
|
710
|
+
api_version = Gem.respond_to?(:extension_api_version) ? Gem.extension_api_version : Gem.ruby_api_version
|
|
711
|
+
packed_ext_dir = Pathname("extensions") / Gem::Platform.local.to_s / api_version / spec.full_name
|
|
712
|
+
host_marker = Pathname(spec.gem_build_complete_path)
|
|
713
|
+
if default_spec || (File.exist?(host_marker) && !host_marker.subpath?(base_dir))
|
|
714
|
+
builder.touch(packed_base / packed_ext_dir / "gem.build_complete")
|
|
715
|
+
end
|
|
285
716
|
end
|
|
286
717
|
|
|
287
718
|
# Determine which set of files to include for this particular gem
|
|
@@ -292,6 +723,9 @@ module Ocran
|
|
|
292
723
|
|
|
293
724
|
verbose "\tgem_dir: #{spec.gem_dir}"
|
|
294
725
|
verbose "\tgem_dir exists: #{File.directory?(spec.gem_dir)}"
|
|
726
|
+
unless File.directory?(spec.gem_dir)
|
|
727
|
+
verbose "\tGem directory does not exist (default gem?); packing loaded files via load path instead"
|
|
728
|
+
end
|
|
295
729
|
loaded_matches = include.include?(:loaded) ? features.select { |f| f.subpath?(spec.gem_dir) } : []
|
|
296
730
|
verbose "\t:loaded candidates in features: #{loaded_matches.size}"
|
|
297
731
|
loaded_matches.each { |f| verbose "\t loaded: #{f}" }
|
|
@@ -299,6 +733,20 @@ module Ocran
|
|
|
299
733
|
verbose "\t:files (resource_files) count: #{resource_count}"
|
|
300
734
|
|
|
301
735
|
actual_files = spec.find_gem_files(include, features)
|
|
736
|
+
|
|
737
|
+
# Safety net: gems reaching this point are pure Ruby as far as
|
|
738
|
+
# their gem and extension directories go (see the disposition
|
|
739
|
+
# check above), but a file list can still pull in a native binary
|
|
740
|
+
# from elsewhere. It cannot load under the cosmopolitan payload,
|
|
741
|
+
# so exclude it loudly.
|
|
742
|
+
if @option.cosmo_ruby
|
|
743
|
+
native_files = actual_files.select { |f| NATIVE_BINARY_EXTENSIONS.any? { |ext| f.extname?(ext) } }
|
|
744
|
+
if native_files.any?
|
|
745
|
+
warning "Gem #{spec.full_name} contains native binaries that cannot run under the packed cosmopolitan Ruby; excluding: #{native_files.map(&:basename).join(", ")}"
|
|
746
|
+
actual_files -= native_files
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
|
|
302
750
|
say "\t#{actual_files.size} files, #{actual_files.sum(0, &:size)} bytes"
|
|
303
751
|
|
|
304
752
|
# Decide where to put gem files, either the system gem folder, or
|
|
@@ -308,19 +756,86 @@ module Ocran
|
|
|
308
756
|
builder.duplicate_to_exec_prefix(gemfile)
|
|
309
757
|
elsif (gem_path = GemSpecQueryable.find_gem_path(gemfile))
|
|
310
758
|
builder.duplicate_to_gem_home(gemfile, gem_path)
|
|
759
|
+
elsif local_gem_dir && gemfile.subpath?(local_gem_dir)
|
|
760
|
+
# Mirror local development gem files into the packed GEM_HOME
|
|
761
|
+
# under the gem directory matching the generated specification.
|
|
762
|
+
builder.copy_to_gem(gemfile, Pathname("gems") / spec.full_name / gemfile.relative_path_from(local_gem_dir))
|
|
311
763
|
else
|
|
312
764
|
raise "Don't know where to put gemfile #{gemfile}"
|
|
313
765
|
end
|
|
314
766
|
end
|
|
315
767
|
|
|
768
|
+
# When a distro builds native extensions outside the gem's base_dir
|
|
769
|
+
# and loads them from there (e.g. Fedora's /usr/lib64/gems/ruby), the
|
|
770
|
+
# loaded .so would be packed at a mirror path that is never on the
|
|
771
|
+
# runtime load path. Pack such loaded extension files ONLY into the
|
|
772
|
+
# extension directory RubyGems computes at runtime, which gem
|
|
773
|
+
# activation puts on the load path. Appended to actual_files after
|
|
774
|
+
# the generic copy above so they are claimed as gem files (and thus
|
|
775
|
+
# excluded from the plain feature packing, where a bare .so on
|
|
776
|
+
# RUBYLIB would shadow the gem) without also being mirrored at their
|
|
777
|
+
# host location. The extension dir and the load location may be
|
|
778
|
+
# aliased via symlinks in either direction, so match features against
|
|
779
|
+
# the extension dir by realpath both ways.
|
|
780
|
+
if !Gem.win_platform? && spec.extensions.any?
|
|
781
|
+
host_ext_dir = Pathname(spec.extension_dir)
|
|
782
|
+
if host_ext_dir.directory? && !host_ext_dir.subpath?(base_dir)
|
|
783
|
+
alias_map = {}
|
|
784
|
+
host_ext_dir.find.select(&:file?).each do |entry|
|
|
785
|
+
real = begin
|
|
786
|
+
entry.realpath
|
|
787
|
+
rescue SystemCallError
|
|
788
|
+
next
|
|
789
|
+
end
|
|
790
|
+
alias_map[real] = entry
|
|
791
|
+
end
|
|
792
|
+
features.each do |feature|
|
|
793
|
+
real = begin
|
|
794
|
+
feature.realpath
|
|
795
|
+
rescue SystemCallError
|
|
796
|
+
feature
|
|
797
|
+
end
|
|
798
|
+
entry = if feature.subpath?(host_ext_dir)
|
|
799
|
+
feature
|
|
800
|
+
elsif real.subpath?(host_ext_dir)
|
|
801
|
+
real
|
|
802
|
+
else
|
|
803
|
+
alias_map[feature] || alias_map[real]
|
|
804
|
+
end
|
|
805
|
+
next unless entry
|
|
806
|
+
|
|
807
|
+
builder.cp(feature, packed_base / packed_ext_dir / entry.relative_path_from(host_ext_dir))
|
|
808
|
+
actual_files << feature
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
end
|
|
812
|
+
|
|
316
813
|
actual_files
|
|
317
814
|
end
|
|
318
815
|
gem_files.uniq!
|
|
319
816
|
|
|
320
|
-
|
|
817
|
+
# On some distros parts of a gem are reachable through symlinks at other
|
|
818
|
+
# locations (e.g. Fedora symlinks /usr/share/ruby/psych.rb into the
|
|
819
|
+
# psych gem directory), so a feature and a packed gem file can denote
|
|
820
|
+
# the same file under different paths. Compare realpaths when removing
|
|
821
|
+
# gem files from the feature list; otherwise a stdlib-level duplicate
|
|
822
|
+
# would be packed as well and shadow the packed gem at runtime.
|
|
823
|
+
gem_file_set = (gem_files + gem_files.filter_map { |file| file.realpath rescue nil }).to_set
|
|
824
|
+
features = features.reject do |feature|
|
|
825
|
+
next true if gem_file_set.include?(feature)
|
|
826
|
+
|
|
827
|
+
real = begin
|
|
828
|
+
feature.realpath
|
|
829
|
+
rescue SystemCallError
|
|
830
|
+
nil
|
|
831
|
+
end
|
|
832
|
+
real && gem_file_set.include?(real)
|
|
833
|
+
end
|
|
321
834
|
|
|
322
835
|
# If requested, add all ruby standard libraries
|
|
323
|
-
if @option.add_all_core?
|
|
836
|
+
if @option.add_all_core? && @option.cosmo_ruby
|
|
837
|
+
say "Skipping host core libraries (--add-all-core): the cosmopolitan Ruby embeds its own standard library"
|
|
838
|
+
elsif @option.add_all_core?
|
|
324
839
|
say "Will include all ruby core libraries"
|
|
325
840
|
all_core_dir.each do |path|
|
|
326
841
|
# Match the load path against standard library, site_ruby, and vendor_ruby paths
|
|
@@ -336,7 +851,10 @@ module Ocran
|
|
|
336
851
|
end
|
|
337
852
|
|
|
338
853
|
# Include encoding support files
|
|
339
|
-
if @option.
|
|
854
|
+
if @option.cosmo_ruby
|
|
855
|
+
# Encoding extensions are statically linked into the payload.
|
|
856
|
+
say "Encoding support is embedded in the cosmopolitan Ruby"
|
|
857
|
+
elsif @option.add_all_encoding?
|
|
340
858
|
@post_env.load_path.each do |load_path|
|
|
341
859
|
load_path = Pathname(@post_env.expand_path(load_path))
|
|
342
860
|
next unless load_path.subpath?(exec_prefix)
|
|
@@ -374,6 +892,23 @@ module Ocran
|
|
|
374
892
|
pre_working_directory = Pathname(@pre_env.pwd)
|
|
375
893
|
working_directory = Pathname(@post_env.pwd)
|
|
376
894
|
features.each do |feature|
|
|
895
|
+
# With --cosmo-ruby, files of the host Ruby installation must not
|
|
896
|
+
# be packed: the payload interpreter resolves the standard library
|
|
897
|
+
# from its embedded ZIP store, and a packed host-version copy (or
|
|
898
|
+
# a host-ABI native extension) would be wrong for it.
|
|
899
|
+
if @option.cosmo_ruby
|
|
900
|
+
if feature.subpath?(exec_prefix)
|
|
901
|
+
verbose "\tlibfile: #{feature} -> skipped (host Ruby installation; the cosmopolitan Ruby uses its embedded stdlib)"
|
|
902
|
+
next
|
|
903
|
+
elsif cosmo_skipped_gem_dirs.any? { |dir| feature.subpath?(dir) }
|
|
904
|
+
verbose "\tlibfile: #{feature} -> skipped (gem provided by the cosmopolitan Ruby)"
|
|
905
|
+
next
|
|
906
|
+
elsif feature.extname?(".so") || feature.extname?(".bundle")
|
|
907
|
+
warning "Excluding native extension file #{feature}: native extensions cannot run under the packed cosmopolitan Ruby"
|
|
908
|
+
next
|
|
909
|
+
end
|
|
910
|
+
end
|
|
911
|
+
|
|
377
912
|
load_path = @post_env.find_load_path(feature)
|
|
378
913
|
if load_path.nil?
|
|
379
914
|
verbose "\tlibfile: #{feature} -> src (no load path)"
|
|
@@ -488,8 +1023,24 @@ module Ocran
|
|
|
488
1023
|
end
|
|
489
1024
|
end
|
|
490
1025
|
|
|
1026
|
+
# Translate -I and -r entries in RUBYOPT that refer to build-machine
|
|
1027
|
+
# paths into the packed layout (GitHub issue #20). Ruby cannot parse
|
|
1028
|
+
# quoted arguments inside RUBYOPT and the runtime extraction directory
|
|
1029
|
+
# may contain spaces, so instead of rewriting the paths inside RUBYOPT
|
|
1030
|
+
# the translated entries are applied by a generated launcher script
|
|
1031
|
+
# (see generate_rubyopt_launcher) where they become plain Ruby string
|
|
1032
|
+
# literals.
|
|
1033
|
+
require_relative "rubyopt_processor"
|
|
1034
|
+
rubyopt_result = RubyoptProcessor.new(rubyopt).translate do |path|
|
|
1035
|
+
packed_rubyopt_path(Pathname(path).cleanpath, inst_src_prefix)
|
|
1036
|
+
end
|
|
1037
|
+
rubyopt_result.dropped.each do |entry|
|
|
1038
|
+
say "Removing #{entry} from RUBYOPT (path is not part of the package)"
|
|
1039
|
+
end
|
|
1040
|
+
|
|
491
1041
|
# Set environment variable
|
|
492
|
-
builder.export("RUBYOPT", rubyopt)
|
|
1042
|
+
builder.export("RUBYOPT", rubyopt_result.rubyopt)
|
|
1043
|
+
neutralize_bundler_env(builder)
|
|
493
1044
|
# Add the load path that are required with the correct path after
|
|
494
1045
|
# src_prefix was adjusted.
|
|
495
1046
|
load_path = src_load_path.map { |path| SRCDIR / path.relative_path_from(inst_src_prefix) }.uniq
|
|
@@ -499,14 +1050,19 @@ module Ocran
|
|
|
499
1050
|
# host, which doesn't exist on other systems (e.g., Docker with no Ruby).
|
|
500
1051
|
# By adding the extract-dir equivalents of rubylibdir, sitelibdir, etc. to
|
|
501
1052
|
# RUBYLIB, Ruby can find rubygems and the standard library in the packed tree.
|
|
502
|
-
|
|
503
|
-
|
|
1053
|
+
# Not with --cosmo-ruby: the host stdlib is not packed at all, and the
|
|
1054
|
+
# payload finds its own stdlib in its embedded ZIP store.
|
|
1055
|
+
unless Gem.win_platform? || @option.cosmo_ruby
|
|
1056
|
+
# Use the build Ruby's actual default load path in addition to the
|
|
1057
|
+
# RbConfig directories: some distros compile in extra entries that
|
|
1058
|
+
# RbConfig does not expose (e.g. Fedora's /usr/share/rubygems, where
|
|
1059
|
+
# rubygems.rb lives outside rubylibdir).
|
|
1060
|
+
default_load_paths = @pre_env.load_path.map { |dir| Pathname(@pre_env.expand_path(dir)) }
|
|
1061
|
+
archdir = Pathname(RbConfig::CONFIG["archdir"])
|
|
1062
|
+
core_lib_paths = (default_load_paths + all_core_dir + [archdir])
|
|
504
1063
|
.select { |dir| dir.subpath?(exec_prefix) }
|
|
505
1064
|
.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
|
|
1065
|
+
.uniq
|
|
510
1066
|
load_path = core_lib_paths + load_path
|
|
511
1067
|
end
|
|
512
1068
|
|
|
@@ -515,23 +1071,117 @@ module Ocran
|
|
|
515
1071
|
|
|
516
1072
|
gem_paths = [GEMDIR]
|
|
517
1073
|
# Gems installed under the Ruby prefix (exec_prefix) have their specs and
|
|
518
|
-
# extension dirs placed there via duplicate_to_exec_prefix. Include
|
|
519
|
-
# Gem.
|
|
520
|
-
# find and activate them at runtime. This is required on both
|
|
521
|
-
# (e.g. fxruby/fox16 whose fox16_c.so lives in extension_dir under
|
|
522
|
-
# Ruby prefix) and POSIX (e.g. error_highlight default gems).
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
1074
|
+
# extension dirs placed there via duplicate_to_exec_prefix. Include every
|
|
1075
|
+
# Gem.path entry located under exec_prefix (relative to it) in GEM_PATH so
|
|
1076
|
+
# RubyGems can find and activate them at runtime. This is required on both
|
|
1077
|
+
# Windows (e.g. fxruby/fox16 whose fox16_c.so lives in extension_dir under
|
|
1078
|
+
# the Ruby prefix) and POSIX (e.g. error_highlight default gems). Distros
|
|
1079
|
+
# can split gems over several such directories - e.g. Fedora uses
|
|
1080
|
+
# /usr/share/gems for RPM-packaged (default) gems and /usr/local/share/gems
|
|
1081
|
+
# for user-installed ones - so Gem.default_dir alone is not enough.
|
|
1082
|
+
prefix_gem_dirs = (Gem.path.map { |dir| Pathname(dir) } + [Pathname(Gem.default_dir)])
|
|
1083
|
+
.select { |dir| dir.subpath?(exec_prefix) }
|
|
1084
|
+
.map { |dir| dir.relative_path_from(exec_prefix) }
|
|
1085
|
+
.uniq
|
|
1086
|
+
gem_paths += prefix_gem_dirs
|
|
1087
|
+
# RubyGems probes the default gem dir for writability at startup and
|
|
1088
|
+
# prints "Can't determine writability of default gem path" on stderr
|
|
1089
|
+
# when the directory does not exist in the packed layout - so always
|
|
1090
|
+
# create the packed prefix gem dirs, even when no specs landed there.
|
|
1091
|
+
prefix_gem_dirs.each { |dir| builder.mkdir(dir) }
|
|
1092
|
+
if @option.cosmo_ruby
|
|
1093
|
+
# When GEM_PATH is set, RubyGems no longer scans its compiled-in
|
|
1094
|
+
# default directory — which for the cosmopolitan Ruby is the /zip
|
|
1095
|
+
# store inside the binary, where its bundled gems live. Keep it
|
|
1096
|
+
# reachable by appending it explicitly.
|
|
1097
|
+
gem_paths << @cosmo_ruby_info[:default_gem_dir]
|
|
526
1098
|
end
|
|
527
1099
|
builder.set_env_path("GEM_PATH", *gem_paths)
|
|
528
1100
|
|
|
529
1101
|
# Add the opcode to launch the script
|
|
530
1102
|
installed_ruby_exe = BINDIR / ruby_executable
|
|
531
1103
|
target_script = builder.resolve_source_path(@option.script, inst_src_prefix)
|
|
1104
|
+
if rubyopt_result.translated?
|
|
1105
|
+
target_script = generate_rubyopt_launcher(builder, target_script, rubyopt_result)
|
|
1106
|
+
end
|
|
532
1107
|
builder.exec(installed_ruby_exe, target_script, *@option.argv)
|
|
533
1108
|
end
|
|
534
1109
|
|
|
1110
|
+
# Maps an absolute build-machine path to its location (relative to the
|
|
1111
|
+
# extraction root) inside the packed application, mirroring where
|
|
1112
|
+
# #construct places files. Returns nil when the path is not packed.
|
|
1113
|
+
def packed_rubyopt_path(path, src_prefix)
|
|
1114
|
+
require_relative "gem_spec_queryable"
|
|
1115
|
+
|
|
1116
|
+
if path.subpath?(exec_prefix)
|
|
1117
|
+
path.relative_path_from(exec_prefix)
|
|
1118
|
+
elsif (gem_path = GemSpecQueryable.find_gem_path(path))
|
|
1119
|
+
GEMDIR / path.relative_path_from(gem_path)
|
|
1120
|
+
elsif path.subpath?(src_prefix)
|
|
1121
|
+
SRCDIR / path.relative_path_from(src_prefix)
|
|
1122
|
+
end
|
|
1123
|
+
end
|
|
1124
|
+
private :packed_rubyopt_path
|
|
1125
|
+
|
|
1126
|
+
RUBYOPT_LAUNCHER_NAME = "ocran-rubyopt-launcher.rb"
|
|
1127
|
+
|
|
1128
|
+
# Writes a launcher script next to the packed application script that
|
|
1129
|
+
# applies the translated RUBYOPT -I/-r entries (as plain Ruby string
|
|
1130
|
+
# literals resolved against the extraction directory at runtime) and
|
|
1131
|
+
# then loads the original script. Returns the packed path of the
|
|
1132
|
+
# launcher, which becomes the script the stub executes.
|
|
1133
|
+
def generate_rubyopt_launcher(builder, target_script, result)
|
|
1134
|
+
say "Generating launcher script for RUBYOPT entries with translated paths"
|
|
1135
|
+
launcher_target = target_script.dirname / RUBYOPT_LAUNCHER_NAME
|
|
1136
|
+
|
|
1137
|
+
# Relative path from the launcher's directory up to the extraction root.
|
|
1138
|
+
depth = target_script.dirname.each_filename.count { |name| name != "." }
|
|
1139
|
+
root_rel = depth.zero? ? "." : ([".."] * depth).join("/")
|
|
1140
|
+
|
|
1141
|
+
lines = []
|
|
1142
|
+
lines << "# frozen_string_literal: true"
|
|
1143
|
+
lines << "# Generated by OCRAN. Applies -I/-r options that were given in RUBYOPT"
|
|
1144
|
+
lines << "# with build-machine paths, translated to the extraction directory."
|
|
1145
|
+
lines << "# They cannot remain in RUBYOPT because Ruby does not support quoting"
|
|
1146
|
+
lines << "# there and the extraction path may contain spaces."
|
|
1147
|
+
lines << "ocran_root = File.expand_path(#{root_rel.dump}, __dir__)"
|
|
1148
|
+
lines << "$0 = File.expand_path(#{target_script.basename.to_s.dump}, __dir__)"
|
|
1149
|
+
result.load_paths.reverse_each do |dir|
|
|
1150
|
+
lines << "$LOAD_PATH.unshift(File.expand_path(#{dir.to_posix.dump}, ocran_root))"
|
|
1151
|
+
end
|
|
1152
|
+
result.requires.each do |feature|
|
|
1153
|
+
lines << "require File.expand_path(#{feature.to_posix.dump}, ocran_root)"
|
|
1154
|
+
end
|
|
1155
|
+
lines << "load $0"
|
|
1156
|
+
|
|
1157
|
+
require "tempfile"
|
|
1158
|
+
launcher_file = Tempfile.new(["ocran-rubyopt-launcher", ".rb"])
|
|
1159
|
+
launcher_file.write(lines.join("\n") + "\n")
|
|
1160
|
+
launcher_file.close
|
|
1161
|
+
verbose File.read(launcher_file.path)
|
|
1162
|
+
# Keep a reference so the temporary file survives until the build
|
|
1163
|
+
# finishes; the Inno Setup builder reads source files only when the
|
|
1164
|
+
# installer is compiled, after #construct has returned.
|
|
1165
|
+
@rubyopt_launcher_file = launcher_file
|
|
1166
|
+
|
|
1167
|
+
builder.cp(launcher_file.path, launcher_target)
|
|
1168
|
+
launcher_target
|
|
1169
|
+
end
|
|
1170
|
+
private :generate_rubyopt_launcher
|
|
1171
|
+
# Writes the in-memory gem specification to a temporary file and returns
|
|
1172
|
+
# the file's path, for packing gemspecs that cannot be copied verbatim
|
|
1173
|
+
# from disk (e.g. local development gems). The Tempfile object is
|
|
1174
|
+
# retained because some builders (e.g. InnoSetupScriptBuilder) read
|
|
1175
|
+
# their source files only after construction has completed.
|
|
1176
|
+
def generate_gemspec_file(spec)
|
|
1177
|
+
require "tempfile"
|
|
1178
|
+
file = Tempfile.new(["#{spec.full_name}-", ".gemspec"])
|
|
1179
|
+
file.write(spec.to_ruby)
|
|
1180
|
+
file.close
|
|
1181
|
+
(@generated_gemspec_files ||= []) << file
|
|
1182
|
+
file.path
|
|
1183
|
+
end
|
|
1184
|
+
|
|
535
1185
|
def to_proc
|
|
536
1186
|
method(:construct).to_proc
|
|
537
1187
|
end
|
|
@@ -546,8 +1196,13 @@ module Ocran
|
|
|
546
1196
|
title: @option.output_executable.basename.sub_ext("")
|
|
547
1197
|
)
|
|
548
1198
|
|
|
1199
|
+
# Record the launch events (exports + exec) so they can be replayed
|
|
1200
|
+
# into the wrapper stub executable below.
|
|
1201
|
+
require_relative "launcher_event_recorder"
|
|
1202
|
+
recorder = LauncherEventRecorder.new(launcher_builder)
|
|
1203
|
+
|
|
549
1204
|
require_relative "build_facade"
|
|
550
|
-
builder = BuildFacade.new(iss_builder,
|
|
1205
|
+
builder = BuildFacade.new(iss_builder, recorder)
|
|
551
1206
|
|
|
552
1207
|
if @option.icon_filename
|
|
553
1208
|
builder.cp(@option.icon_filename, File.basename(@option.icon_filename))
|
|
@@ -560,22 +1215,84 @@ module Ocran
|
|
|
560
1215
|
verbose File.read(launcher_path)
|
|
561
1216
|
builder.cp(launcher_path, "launcher.bat")
|
|
562
1217
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1218
|
+
require "tmpdir"
|
|
1219
|
+
Dir.mktmpdir("ocran") do |tmpdir|
|
|
1220
|
+
if @option.wrapper_exe?
|
|
1221
|
+
# Wrapper executable (pre-1.4/OCRA behavior): a small stub without
|
|
1222
|
+
# embedded files that runs the installed application directly from
|
|
1223
|
+
# {app}. It is installed next to the application files so that user
|
|
1224
|
+
# Inno Setup scripts can reference it by the --output name, e.g. in
|
|
1225
|
+
# [Run]/[UninstallRun] entries or for Windows service registration.
|
|
1226
|
+
wrapper_path = Pathname(tmpdir) / @option.output_executable.basename
|
|
1227
|
+
build_wrapper_exe(wrapper_path) { |stub| recorder.replay(stub) }
|
|
1228
|
+
builder.cp(wrapper_path, wrapper_path.basename)
|
|
1229
|
+
end
|
|
1230
|
+
|
|
1231
|
+
say "Build inno setup script file"
|
|
1232
|
+
iss_path = iss_builder.build
|
|
1233
|
+
verbose File.read(iss_path)
|
|
566
1234
|
|
|
567
|
-
|
|
568
|
-
|
|
1235
|
+
say "Running Inno Setup Command-Line compiler (ISCC)"
|
|
1236
|
+
iss_builder.compile(verbose: @option.verbose?)
|
|
1237
|
+
end
|
|
569
1238
|
|
|
570
1239
|
say "Finished building installer file"
|
|
571
1240
|
end
|
|
572
1241
|
|
|
1242
|
+
# Returns the path to the stub built from source with cosmocc when
|
|
1243
|
+
# --cosmo was given, or nil to use the pre-built stub shipped with
|
|
1244
|
+
# the gem. The build result is memoized (and CosmoToolchain caches
|
|
1245
|
+
# compiled stubs across runs), so multiple stubs per build (e.g.
|
|
1246
|
+
# wrapper executables) compile at most once.
|
|
1247
|
+
def cosmo_stub_path
|
|
1248
|
+
return nil unless @option.cosmo_cc
|
|
1249
|
+
|
|
1250
|
+
@cosmo_stub_path ||= begin
|
|
1251
|
+
load File.expand_path("cosmo_toolchain.rb", __dir__) unless defined? CosmoToolchain
|
|
1252
|
+
say "Building launcher stub from source with cosmocc (#{@option.cosmo_cc})"
|
|
1253
|
+
path = CosmoToolchain.build_stub(@option.cosmo_cc)
|
|
1254
|
+
say "Using APE stub #{path}"
|
|
1255
|
+
path
|
|
1256
|
+
end
|
|
1257
|
+
end
|
|
1258
|
+
|
|
1259
|
+
# Builds the small RUN_IN_EXE_DIR wrapper stub that starts the deployed
|
|
1260
|
+
# application directly from the directory the wrapper resides in.
|
|
1261
|
+
def build_wrapper_exe(wrapper_path)
|
|
1262
|
+
require_relative "stub_builder"
|
|
1263
|
+
say "Build wrapper executable #{wrapper_path.basename}"
|
|
1264
|
+
StubBuilder.new(wrapper_path,
|
|
1265
|
+
chdir_before: @option.chdir_before?,
|
|
1266
|
+
chdir_to_exe_dir: @option.chdir_exe_dir?,
|
|
1267
|
+
debug_mode: @option.enable_debug_mode?,
|
|
1268
|
+
gui_mode: @option.windowed?,
|
|
1269
|
+
icon_path: @option.icon_filename,
|
|
1270
|
+
run_in_exe_dir: true,
|
|
1271
|
+
stub_path: cosmo_stub_path) do |stub|
|
|
1272
|
+
yield(stub)
|
|
1273
|
+
end
|
|
1274
|
+
end
|
|
1275
|
+
|
|
573
1276
|
def build_output_dir(path)
|
|
574
1277
|
require_relative "dir_builder"
|
|
575
1278
|
|
|
576
1279
|
path = Pathname(path)
|
|
577
1280
|
say "Building directory #{path}"
|
|
578
|
-
DirBuilder.new(path, &to_proc)
|
|
1281
|
+
builder = DirBuilder.new(path, &to_proc)
|
|
1282
|
+
|
|
1283
|
+
if @option.wrapper_exe?
|
|
1284
|
+
# Same wrapper as in Inno Setup builds: a doubleclickable executable
|
|
1285
|
+
# next to the launch script, usable e.g. for Windows service
|
|
1286
|
+
# registration from an unpacked zip. Disable with --no-wrapper-exe.
|
|
1287
|
+
build_wrapper_exe(path / @option.output_executable.basename) do |stub|
|
|
1288
|
+
builder.env.each { |name, value| stub.export(name, value) }
|
|
1289
|
+
if builder.exec_args
|
|
1290
|
+
image, script, argv = builder.exec_args
|
|
1291
|
+
stub.exec(image, script, *argv)
|
|
1292
|
+
end
|
|
1293
|
+
end
|
|
1294
|
+
end
|
|
1295
|
+
|
|
579
1296
|
say "Finished building directory #{path}"
|
|
580
1297
|
end
|
|
581
1298
|
|
|
@@ -609,11 +1326,13 @@ module Ocran
|
|
|
609
1326
|
|
|
610
1327
|
StubBuilder.new(executable_path,
|
|
611
1328
|
chdir_before: @option.chdir_before?,
|
|
1329
|
+
chdir_to_exe_dir: @option.chdir_exe_dir?,
|
|
612
1330
|
debug_extract: @option.enable_debug_extract?,
|
|
613
1331
|
debug_mode: @option.enable_debug_mode?,
|
|
614
1332
|
enable_compression: @option.enable_compression?,
|
|
615
1333
|
gui_mode: false,
|
|
616
1334
|
icon_path: nil,
|
|
1335
|
+
stub_path: cosmo_stub_path,
|
|
617
1336
|
&to_proc) => builder
|
|
618
1337
|
|
|
619
1338
|
if @option.icon_filename
|
|
@@ -649,6 +1368,40 @@ module Ocran
|
|
|
649
1368
|
say "Finished building #{bundle_path} (#{builder.data_size} bytes decompressed)"
|
|
650
1369
|
end
|
|
651
1370
|
|
|
1371
|
+
# Builds the executable by copying the cosmopolitan Ruby and injecting
|
|
1372
|
+
# the application into its ZIP store (--cosmo-ruby with an interpreter
|
|
1373
|
+
# that runs an embedded /zip/main.rb). No compiler runs, no launcher
|
|
1374
|
+
# stub is involved, and the resulting binary unpacks nothing when it
|
|
1375
|
+
# starts.
|
|
1376
|
+
def build_cosmo_zip_exe
|
|
1377
|
+
require_relative "zip_payload_builder"
|
|
1378
|
+
|
|
1379
|
+
output = @option.output_executable
|
|
1380
|
+
ZipPayloadBuilder.new(output,
|
|
1381
|
+
cosmo_ruby: @option.cosmo_ruby,
|
|
1382
|
+
chdir_before: @option.chdir_before?,
|
|
1383
|
+
debug_mode: @option.enable_debug_mode?,
|
|
1384
|
+
&to_proc) => builder
|
|
1385
|
+
|
|
1386
|
+
builder.ignored_symlinks.each do |link_path, target|
|
|
1387
|
+
verbose "Skipping symlink #{link_path} -> #{target} (ZIP members cannot be symlinks)"
|
|
1388
|
+
end
|
|
1389
|
+
|
|
1390
|
+
if @option.icon_filename
|
|
1391
|
+
warning "--icon has no effect in this mode: the executable is a copy of the cosmopolitan Ruby, whose resources OCRAN does not rewrite"
|
|
1392
|
+
end
|
|
1393
|
+
if @option.enable_debug_extract?
|
|
1394
|
+
warning "--debug-extract has no effect in this mode: nothing is extracted, the application is read from the executable's own ZIP store"
|
|
1395
|
+
end
|
|
1396
|
+
|
|
1397
|
+
_, _, unsupported = ZipPayloadBuilder.parse_rubyopt(rubyopt)
|
|
1398
|
+
unless unsupported.empty?
|
|
1399
|
+
warning "RUBYOPT #{unsupported.join(" ")} cannot be applied when the application is packed into the interpreter's ZIP store (the interpreter is already running); only -I and -r are replayed"
|
|
1400
|
+
end
|
|
1401
|
+
|
|
1402
|
+
say "Finished building #{output} (#{output.size} bytes, #{builder.data_size} bytes of application data)"
|
|
1403
|
+
end
|
|
1404
|
+
|
|
652
1405
|
def build_stab_exe
|
|
653
1406
|
require_relative "stub_builder"
|
|
654
1407
|
|
|
@@ -658,11 +1411,13 @@ module Ocran
|
|
|
658
1411
|
|
|
659
1412
|
StubBuilder.new(@option.output_executable,
|
|
660
1413
|
chdir_before: @option.chdir_before?,
|
|
1414
|
+
chdir_to_exe_dir: @option.chdir_exe_dir?,
|
|
661
1415
|
debug_extract: @option.enable_debug_extract?,
|
|
662
1416
|
debug_mode: @option.enable_debug_mode?,
|
|
663
1417
|
enable_compression: @option.enable_compression?,
|
|
664
1418
|
gui_mode: @option.windowed?,
|
|
665
1419
|
icon_path: @option.icon_filename,
|
|
1420
|
+
stub_path: cosmo_stub_path,
|
|
666
1421
|
&to_proc) => builder
|
|
667
1422
|
say "Finished building #{@option.output_executable} (#{@option.output_executable.size} bytes)"
|
|
668
1423
|
say "After decompression, the data will expand to #{builder.data_size} bytes."
|