ocran 1.4.4 → 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 +18 -0
- data/README.md +435 -16
- data/lib/ocran/cosmo_toolchain.rb +383 -0
- data/lib/ocran/direction.rb +592 -58
- data/lib/ocran/option.rb +165 -3
- data/lib/ocran/rubyopt_processor.rb +95 -0
- data/lib/ocran/runner.rb +80 -0
- data/lib/ocran/runtime_environment.rb +19 -1
- data/lib/ocran/stub_builder.rb +23 -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/script_info.c +15 -2
- data/src/script_info.h +15 -1
- data/src/stub.c +51 -1
- data/src/system_utils_posix.c +40 -0
- data/src/unpack.c +4 -0
- data/src/unpack.h +10 -0
- metadata +5 -1
data/lib/ocran/option.rb
CHANGED
|
@@ -16,6 +16,10 @@ module Ocran
|
|
|
16
16
|
:macosx_bundle => nil,
|
|
17
17
|
:macosx_bundle? => false,
|
|
18
18
|
:chdir_before? => false,
|
|
19
|
+
:chdir_exe_dir? => false,
|
|
20
|
+
:cosmo_cc => nil,
|
|
21
|
+
:cosmo_ruby => nil,
|
|
22
|
+
:cosmo_zip? => false,
|
|
19
23
|
:enable_compression? => true,
|
|
20
24
|
:enable_debug_extract? => false,
|
|
21
25
|
:enable_debug_mode? => false,
|
|
@@ -103,13 +107,62 @@ Executable options:
|
|
|
103
107
|
--windows Force Windows application (rubyw.exe)
|
|
104
108
|
--console Force console application (ruby.exe)
|
|
105
109
|
--chdir-first When exe starts, change working directory to app dir.
|
|
110
|
+
--chdir-exe-dir When exe starts, change working directory to the directory
|
|
111
|
+
containing the executable itself.
|
|
106
112
|
--icon <ico> Replace icon with a custom one.
|
|
107
|
-
--rubyopt <str> Set the RUBYOPT environment variable when running the executable
|
|
113
|
+
--rubyopt <str> Set the RUBYOPT environment variable when running the executable.
|
|
114
|
+
-I/-r entries with absolute build-machine paths are
|
|
115
|
+
translated to their packed locations at build time.
|
|
108
116
|
--debug Executable will be verbose.
|
|
109
117
|
--debug-extract Executable will unpack to local dir and not delete after.
|
|
118
|
+
|
|
119
|
+
Experimental options:
|
|
120
|
+
|
|
121
|
+
--cosmo-ruby <ruby.com> Package the given cosmopolitan-built Ruby (an APE,
|
|
122
|
+
e.g. ruby.com) as the interpreter instead of the host
|
|
123
|
+
Ruby: the result is a single <scriptname>.com that runs
|
|
124
|
+
on Linux, Windows and macOS. The APE must be fully
|
|
125
|
+
self-contained (stdlib embedded in its ZIP store).
|
|
126
|
+
Dependency detection still runs under the host Ruby;
|
|
127
|
+
native-extension gems are rejected (pure-Ruby gems are
|
|
128
|
+
packed as usual).
|
|
129
|
+
When the given interpreter runs an embedded /zip/main.rb
|
|
130
|
+
(CosmoRuby builds do), the application is injected into
|
|
131
|
+
its ZIP store: no compiler is needed and the executable
|
|
132
|
+
starts without unpacking anything. Otherwise OCRAN falls
|
|
133
|
+
back to an APE launcher stub that extracts the
|
|
134
|
+
application at every start, and locates the cosmocc
|
|
135
|
+
toolchain to build that stub automatically (COSMOCC
|
|
136
|
+
environment variable, cosmocc in PATH, then conventional
|
|
137
|
+
install locations such as ~/.cosmocc/*/bin/cosmocc).
|
|
138
|
+
--cosmo <path> Use the Cosmopolitan toolchain (cosmocc) at <path> to
|
|
139
|
+
build the launcher stub, overriding the automatic
|
|
140
|
+
toolchain lookup. <path> is the cosmocc executable or
|
|
141
|
+
its install dir. Given together with --cosmo-ruby it
|
|
142
|
+
forces the launcher-stub build even when the interpreter
|
|
143
|
+
supports ZIP packaging. Given without --cosmo-ruby, it
|
|
144
|
+
packages the host Ruby behind an APE stub. Console-only;
|
|
145
|
+
output defaults to <scriptname>.com.
|
|
146
|
+
(Alias: --cosmo-toolchain)
|
|
110
147
|
EOF
|
|
111
148
|
end
|
|
112
149
|
|
|
150
|
+
# Pulls in Ocran::CosmoToolchain for the --cosmo/--cosmo-ruby options.
|
|
151
|
+
#
|
|
152
|
+
# Deliberately Kernel#load and not require_relative: OCRAN detects the
|
|
153
|
+
# application's dependencies by diffing $LOADED_FEATURES around the
|
|
154
|
+
# dependency run, and the "before" snapshot is taken *before* the command
|
|
155
|
+
# line is parsed (Runner#initialize). Anything require'd from here would
|
|
156
|
+
# therefore show up in that diff and be packed into the user's
|
|
157
|
+
# application - packing OCRAN's own source file with it, and (because the
|
|
158
|
+
# src prefix is the common parent of all source files) moving the whole
|
|
159
|
+
# application down into a deeper src/ subdirectory. Kernel#load leaves
|
|
160
|
+
# $LOADED_FEATURES untouched; the same idiom is used for
|
|
161
|
+
# refine_pathname.rb above.
|
|
162
|
+
def load_cosmo_toolchain
|
|
163
|
+
load File.expand_path("cosmo_toolchain.rb", __dir__) unless defined? CosmoToolchain
|
|
164
|
+
end
|
|
165
|
+
|
|
113
166
|
def parse(argv)
|
|
114
167
|
while (arg = argv.shift)
|
|
115
168
|
case arg
|
|
@@ -149,12 +202,22 @@ EOF
|
|
|
149
202
|
@options[:load_autoload?] = false
|
|
150
203
|
when "--chdir-first"
|
|
151
204
|
@options[:chdir_before?] = true
|
|
205
|
+
when "--chdir-exe-dir"
|
|
206
|
+
@options[:chdir_exe_dir?] = true
|
|
152
207
|
when "--icon"
|
|
153
208
|
path = argv.shift
|
|
154
209
|
raise "Icon file #{path} not found" unless path && File.exist?(path)
|
|
155
210
|
@options[:icon_filename] = Pathname.new(path).expand_path
|
|
156
211
|
when "--rubyopt"
|
|
157
212
|
@options[:rubyopt] = argv.shift
|
|
213
|
+
when "--cosmo", "--cosmo-toolchain"
|
|
214
|
+
# Kept unresolved until validation: resolving here would report
|
|
215
|
+
# "cosmocc ... is not executable" on a Windows build host, ahead of
|
|
216
|
+
# the clearer "not supported when building on Windows" check.
|
|
217
|
+
@options[:cosmo_cc] = argv.shift
|
|
218
|
+
when "--cosmo-ruby"
|
|
219
|
+
load_cosmo_toolchain
|
|
220
|
+
@options[:cosmo_ruby] = CosmoToolchain.resolve_ruby(argv.shift)
|
|
158
221
|
when "--gemfile"
|
|
159
222
|
path = argv.shift
|
|
160
223
|
raise "Gemfile #{path} not found" unless path && File.exist?(path)
|
|
@@ -219,8 +282,15 @@ EOF
|
|
|
219
282
|
executable = script
|
|
220
283
|
# If debug mode is enabled, append "-debug" to the filename
|
|
221
284
|
executable = executable.append_to_filename("-debug") if enable_debug_mode?
|
|
222
|
-
# Build output files are created in the current directory
|
|
223
|
-
|
|
285
|
+
# Build output files are created in the current directory.
|
|
286
|
+
# APE binaries built with cosmocc conventionally use .com.
|
|
287
|
+
ext = if cosmo?
|
|
288
|
+
".com"
|
|
289
|
+
elsif Gem.win_platform?
|
|
290
|
+
".exe"
|
|
291
|
+
else
|
|
292
|
+
""
|
|
293
|
+
end
|
|
224
294
|
executable.basename.sub_ext(ext).expand_path
|
|
225
295
|
end
|
|
226
296
|
|
|
@@ -229,6 +299,10 @@ EOF
|
|
|
229
299
|
@options[:macosx_bundle] = Pathname(bundle_base).sub_ext(".app").expand_path
|
|
230
300
|
end
|
|
231
301
|
|
|
302
|
+
if chdir_before? && chdir_exe_dir?
|
|
303
|
+
raise "--chdir-first and --chdir-exe-dir cannot be used together"
|
|
304
|
+
end
|
|
305
|
+
|
|
232
306
|
@options[:use_inno_setup?] = !!inno_setup_script
|
|
233
307
|
|
|
234
308
|
@options[:verbose?] &&= !quiet?
|
|
@@ -253,6 +327,41 @@ EOF
|
|
|
253
327
|
raise "--output-dir and --output-zip cannot be used together"
|
|
254
328
|
end
|
|
255
329
|
|
|
330
|
+
if cosmo?
|
|
331
|
+
opt = cosmo_cc ? "--cosmo" : "--cosmo-ruby"
|
|
332
|
+
|
|
333
|
+
if Gem.win_platform?
|
|
334
|
+
raise "#{opt} is not supported when building on Windows (build the APE package on a Linux/macOS host)"
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
if force_windows?
|
|
338
|
+
raise "--windows cannot be used with #{opt}: cosmocc has no GUI (stubw) equivalent; APE stubs are console-only"
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
load_cosmo_toolchain
|
|
342
|
+
|
|
343
|
+
# A cosmopolitan Ruby that runs an embedded /zip/main.rb can carry
|
|
344
|
+
# the application inside its own ZIP store, which needs no compiler
|
|
345
|
+
# and no launcher stub at all - so that is what --cosmo-ruby does
|
|
346
|
+
# on its own. Naming a toolchain with --cosmo is the way to ask for
|
|
347
|
+
# the launcher stub explicitly; the other output formats
|
|
348
|
+
# (--output-dir, --output-zip, --innosetup, --macosx-bundle) are
|
|
349
|
+
# directory layouts rather than a single binary, so they keep using
|
|
350
|
+
# it too.
|
|
351
|
+
@options[:cosmo_zip?] =
|
|
352
|
+
!!cosmo_ruby && !cosmo_cc && !single_binary_output_conflict &&
|
|
353
|
+
CosmoToolchain.zip_main_support?(cosmo_ruby)
|
|
354
|
+
|
|
355
|
+
unless cosmo_zip?
|
|
356
|
+
# The APE stub is compiled with cosmocc. --cosmo names that
|
|
357
|
+
# toolchain explicitly; otherwise it is discovered on the build
|
|
358
|
+
# host (COSMOCC, PATH, conventional install locations), so one
|
|
359
|
+
# option is still enough. Resolved here, before the dependency
|
|
360
|
+
# run, so a missing toolchain fails fast.
|
|
361
|
+
@options[:cosmo_cc] = CosmoToolchain.require_cc(cosmo_cc)
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
256
365
|
if macosx_bundle && (output_dir || output_zip || inno_setup_script)
|
|
257
366
|
raise "--macosx-bundle cannot be combined with --output-dir, --output-zip, or --innosetup"
|
|
258
367
|
end
|
|
@@ -272,6 +381,29 @@ EOF
|
|
|
272
381
|
|
|
273
382
|
def chdir_before? = @options[__method__]
|
|
274
383
|
|
|
384
|
+
def chdir_exe_dir? = @options[__method__]
|
|
385
|
+
def cosmo_cc = @options[__method__]
|
|
386
|
+
|
|
387
|
+
def cosmo_ruby = @options[__method__]
|
|
388
|
+
|
|
389
|
+
# True when the application is packaged by injecting it into the ZIP
|
|
390
|
+
# store of the cosmopolitan Ruby itself: no cosmocc, no launcher stub,
|
|
391
|
+
# and no extraction to a temporary directory at run time.
|
|
392
|
+
def cosmo_zip? = @options[__method__]
|
|
393
|
+
|
|
394
|
+
# Output formats that are not a single self-contained binary, and
|
|
395
|
+
# therefore cannot be produced by injecting into the interpreter.
|
|
396
|
+
def single_binary_output_conflict
|
|
397
|
+
output_dir || output_zip || inno_setup_script || @options[:macosx_bundle?]
|
|
398
|
+
end
|
|
399
|
+
private :single_binary_output_conflict
|
|
400
|
+
|
|
401
|
+
# True when the build produces an APE (Actually Portable Executable),
|
|
402
|
+
# i.e. an APE launcher stub is used: an explicit toolchain (--cosmo), a
|
|
403
|
+
# cosmopolitan Ruby payload (--cosmo-ruby, whose toolchain is inferred),
|
|
404
|
+
# or both.
|
|
405
|
+
def cosmo? = !!(cosmo_cc || cosmo_ruby)
|
|
406
|
+
|
|
275
407
|
def enable_compression? = @options[__method__]
|
|
276
408
|
|
|
277
409
|
def enable_debug_extract? = @options[__method__]
|
|
@@ -314,6 +446,36 @@ EOF
|
|
|
314
446
|
|
|
315
447
|
def script = @options[__method__]
|
|
316
448
|
|
|
449
|
+
# The names Bundler accepts for a Gemfile, most common first.
|
|
450
|
+
GEMFILE_NAMES = %w[Gemfile gems.rb].freeze
|
|
451
|
+
private_constant :GEMFILE_NAMES
|
|
452
|
+
|
|
453
|
+
# The Gemfile the application runs under: the one named by --gemfile,
|
|
454
|
+
# or else the nearest Gemfile at or above the script's directory, which
|
|
455
|
+
# is where Bundler itself would look. Returns nil when the application
|
|
456
|
+
# has no Gemfile at all.
|
|
457
|
+
#
|
|
458
|
+
# This is how the application's own bundle is told apart from whatever
|
|
459
|
+
# bundle OCRAN happens to have been started under. Running
|
|
460
|
+
# `bundle exec ocran app.rb` from the application's own directory is the
|
|
461
|
+
# ordinary case, and there the two are the same file; packaging some
|
|
462
|
+
# other project from inside this one's bundle is not.
|
|
463
|
+
def application_gemfile
|
|
464
|
+
return gemfile if gemfile
|
|
465
|
+
|
|
466
|
+
dir = script.expand_path.dirname
|
|
467
|
+
loop do
|
|
468
|
+
GEMFILE_NAMES.each do |name|
|
|
469
|
+
candidate = dir + name
|
|
470
|
+
return candidate if candidate.file?
|
|
471
|
+
end
|
|
472
|
+
parent = dir.parent
|
|
473
|
+
return nil if parent == dir
|
|
474
|
+
|
|
475
|
+
dir = parent
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
317
479
|
def source_files = @options[__method__]
|
|
318
480
|
|
|
319
481
|
def use_inno_setup? = @options[__method__]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ocran
|
|
4
|
+
# Parses the RUBYOPT environment variable the way Ruby itself does
|
|
5
|
+
# (whitespace-separated words; -I and -r may take their argument either
|
|
6
|
+
# attached or as the following word) and translates -I/-r entries that
|
|
7
|
+
# refer to absolute build-machine paths into their locations inside the
|
|
8
|
+
# packed application.
|
|
9
|
+
#
|
|
10
|
+
# Translated entries cannot simply be rewritten inside RUBYOPT: the
|
|
11
|
+
# extraction directory is only known at runtime and may contain spaces,
|
|
12
|
+
# and Ruby does not support any form of quoting in RUBYOPT. The
|
|
13
|
+
# translated entries are therefore removed from RUBYOPT and returned
|
|
14
|
+
# separately so that the build can apply them through a generated
|
|
15
|
+
# launcher script instead. See GitHub issue #20.
|
|
16
|
+
class RubyoptProcessor
|
|
17
|
+
# -r entries whose feature path matches this pattern are always removed.
|
|
18
|
+
# When building under `bundle exec` (Ruby 3.2+), Bundler injects
|
|
19
|
+
# `-r<absolute path>/bundler/setup` into RUBYOPT; requiring it inside
|
|
20
|
+
# the packed application would make Bundler look for the build
|
|
21
|
+
# machine's Gemfile, so it must not survive into the executable.
|
|
22
|
+
BUNDLER_SETUP_PATTERN = %r{/bundler/setup\z}
|
|
23
|
+
|
|
24
|
+
# rubyopt: the RUBYOPT string with all translated/removed entries
|
|
25
|
+
# stripped; safe to bake into the executable as-is.
|
|
26
|
+
# load_paths: packed locations (relative to the extraction root) for
|
|
27
|
+
# translated -I entries, in the order given.
|
|
28
|
+
# requires: packed locations (relative to the extraction root) for
|
|
29
|
+
# translated -r entries, in the order given.
|
|
30
|
+
# dropped: -I/-r entries with absolute paths that are not part of
|
|
31
|
+
# the package; they cannot work at runtime and were removed.
|
|
32
|
+
Result = Struct.new(:rubyopt, :load_paths, :requires, :dropped) do
|
|
33
|
+
def translated?
|
|
34
|
+
!load_paths.empty? || !requires.empty?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize(rubyopt)
|
|
39
|
+
@rubyopt = rubyopt.to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Translates absolute -I/-r paths. For each absolute path the block is
|
|
43
|
+
# called with the path (String) and must return the corresponding
|
|
44
|
+
# location inside the packed application (relative to the extraction
|
|
45
|
+
# root), or nil when the path is not part of the package.
|
|
46
|
+
def translate
|
|
47
|
+
remaining, load_paths, requires, dropped = [], [], [], []
|
|
48
|
+
|
|
49
|
+
words = @rubyopt.split
|
|
50
|
+
until words.empty?
|
|
51
|
+
word = words.shift
|
|
52
|
+
case word
|
|
53
|
+
when /\A-I(.*)\z/m
|
|
54
|
+
arg = $1.empty? ? words.shift : $1
|
|
55
|
+
next if arg.nil?
|
|
56
|
+
|
|
57
|
+
kept = []
|
|
58
|
+
arg.split(File::PATH_SEPARATOR).each do |dir|
|
|
59
|
+
next if dir.empty?
|
|
60
|
+
|
|
61
|
+
if File.absolute_path?(dir)
|
|
62
|
+
if (packed = yield(dir))
|
|
63
|
+
load_paths << packed
|
|
64
|
+
else
|
|
65
|
+
dropped << "-I#{dir}"
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
kept << dir
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
remaining << "-I#{kept.join(File::PATH_SEPARATOR)}" unless kept.empty?
|
|
72
|
+
when /\A-r(.*)\z/m
|
|
73
|
+
feature = $1.empty? ? words.shift : $1
|
|
74
|
+
next if feature.nil?
|
|
75
|
+
|
|
76
|
+
if feature.match?(BUNDLER_SETUP_PATTERN)
|
|
77
|
+
# Silently stripped, see BUNDLER_SETUP_PATTERN above.
|
|
78
|
+
elsif File.absolute_path?(feature)
|
|
79
|
+
if (packed = yield(feature))
|
|
80
|
+
requires << packed
|
|
81
|
+
else
|
|
82
|
+
dropped << "-r#{feature}"
|
|
83
|
+
end
|
|
84
|
+
else
|
|
85
|
+
remaining << "-r#{feature}"
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
remaining << word
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Result.new(remaining.join(" "), load_paths, requires, dropped)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/ocran/runner.rb
CHANGED
|
@@ -44,10 +44,88 @@ module Ocran
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
exit unless @option.run_script?
|
|
47
|
+
if @option.gemfile
|
|
48
|
+
apply_gemfile_to_bundler_env
|
|
49
|
+
else
|
|
50
|
+
warn_about_foreign_bundle
|
|
51
|
+
end
|
|
47
52
|
say "Loading script to check dependencies"
|
|
48
53
|
$PROGRAM_NAME = @option.script.to_s
|
|
49
54
|
end
|
|
50
55
|
|
|
56
|
+
# --gemfile names the Gemfile the application runs under, so it has to
|
|
57
|
+
# govern the dependency run as well, not just the later Gemfile scan.
|
|
58
|
+
# The script is loaded in this very process, and a BUNDLE_GEMFILE
|
|
59
|
+
# inherited from the environment would otherwise win: invoke OCRAN from
|
|
60
|
+
# inside another project's `bundle exec` and the script's
|
|
61
|
+
# `require "bundler/setup"` sets up that project's bundle instead of the
|
|
62
|
+
# application's. Gems only the application's Gemfile provides are then
|
|
63
|
+
# missing, and local development gems declared with `path:` or `gemspec`
|
|
64
|
+
# always are, since they exist nowhere else. The dependency run dies with
|
|
65
|
+
# a LoadError before anything can be packed (github issue #34).
|
|
66
|
+
def apply_gemfile_to_bundler_env
|
|
67
|
+
gemfile = @option.gemfile.to_s
|
|
68
|
+
active = active_bundler_gemfile
|
|
69
|
+
|
|
70
|
+
ENV["BUNDLE_GEMFILE"] = gemfile
|
|
71
|
+
return if active.nil? || same_file?(active, gemfile)
|
|
72
|
+
|
|
73
|
+
# Past this point the ambient bundle is a different one, so anything
|
|
74
|
+
# else describing it has to go: BUNDLE_LOCKFILE names the lockfile of
|
|
75
|
+
# the bundle being displaced, and Bundler would sooner believe it than
|
|
76
|
+
# the Gemfile we just pointed it at.
|
|
77
|
+
ENV.delete("BUNDLE_LOCKFILE")
|
|
78
|
+
return unless defined?(Bundler) && Bundler.respond_to?(:reset!)
|
|
79
|
+
|
|
80
|
+
# `bundle exec` puts -rbundler/setup in RUBYOPT as well, so the wrong
|
|
81
|
+
# bundle may already be set up by the time this runs. The variable
|
|
82
|
+
# alone is then too late: bundler/setup sits in $LOADED_FEATURES, and
|
|
83
|
+
# the script's own `require "bundler/setup"` would be a no-op. Drop
|
|
84
|
+
# Bundler's memoized state and let it be set up afresh, against the
|
|
85
|
+
# Gemfile we were given.
|
|
86
|
+
verbose "Rebinding Bundler from #{active} to #{gemfile}"
|
|
87
|
+
Bundler.reset!
|
|
88
|
+
$LOADED_FEATURES.delete_if { |feature| feature.match?(RuntimeEnvironment::BUNDLER_SETUP_FEATURE) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Without --gemfile the dependency run inherits whatever bundle the
|
|
92
|
+
# environment carries, and when that is some other project's bundle the
|
|
93
|
+
# script's own gems are simply absent. The failure that follows is a
|
|
94
|
+
# LoadError from deep inside the script, which says nothing about the
|
|
95
|
+
# bundle being the wrong one, so say it here while the reason is still
|
|
96
|
+
# visible.
|
|
97
|
+
def warn_about_foreign_bundle
|
|
98
|
+
# Only when a bundle really is set up. BUNDLE_GEMFILE alone is
|
|
99
|
+
# inherited by any child process and governs nothing on its own.
|
|
100
|
+
return unless @pre_env.bundler_setup_loaded?
|
|
101
|
+
|
|
102
|
+
active = active_bundler_gemfile
|
|
103
|
+
return if active.nil? || active.empty?
|
|
104
|
+
|
|
105
|
+
app_gemfile = @option.application_gemfile
|
|
106
|
+
return if app_gemfile && same_file?(active, app_gemfile)
|
|
107
|
+
|
|
108
|
+
warning "Running under Bundler with #{active}, which is not the Gemfile " \
|
|
109
|
+
"#{@option.script} runs under" +
|
|
110
|
+
(app_gemfile ? "; pass --gemfile #{app_gemfile} to package against that bundle" : "")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The Gemfile this process was started against, if any.
|
|
114
|
+
def active_bundler_gemfile
|
|
115
|
+
return ENV["BUNDLE_GEMFILE"] unless ENV["BUNDLE_GEMFILE"].to_s.empty?
|
|
116
|
+
return nil unless defined?(Bundler) && Bundler.respond_to?(:default_gemfile)
|
|
117
|
+
|
|
118
|
+
Bundler.default_gemfile.to_s
|
|
119
|
+
rescue StandardError
|
|
120
|
+
# Bundler raises when there is no Gemfile anywhere above the working
|
|
121
|
+
# directory, which just means there is no ambient bundle to displace.
|
|
122
|
+
nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def same_file?(a, b)
|
|
126
|
+
File.identical?(a, b) || File.expand_path(a) == File.expand_path(b)
|
|
127
|
+
end
|
|
128
|
+
|
|
51
129
|
# Force loading autoloaded constants. Searches through all modules
|
|
52
130
|
# (and hence classes), and checks their constants for autoloaded
|
|
53
131
|
# ones, then attempts to load them.
|
|
@@ -107,6 +185,8 @@ module Ocran
|
|
|
107
185
|
direction.build_output_dir(@option.output_dir)
|
|
108
186
|
elsif @option.output_zip
|
|
109
187
|
direction.build_zip(@option.output_zip)
|
|
188
|
+
elsif @option.cosmo_zip?
|
|
189
|
+
direction.build_cosmo_zip_exe
|
|
110
190
|
else
|
|
111
191
|
direction.build_stab_exe
|
|
112
192
|
end
|
|
@@ -10,13 +10,31 @@ module Ocran
|
|
|
10
10
|
alias save new
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# Matches the bundler/setup feature by which `bundle exec` sets a
|
|
14
|
+
# process up, whatever prefix the bundler gem happens to be installed
|
|
15
|
+
# under.
|
|
16
|
+
BUNDLER_SETUP_FEATURE = %r{[\\/]bundler[\\/]setup\.rb\z}
|
|
17
|
+
|
|
18
|
+
attr_reader :env, :load_path, :loaded_features, :pwd, :activated_gems
|
|
14
19
|
|
|
15
20
|
def initialize
|
|
16
21
|
@env = ENV.to_hash.freeze
|
|
17
22
|
@load_path = $LOAD_PATH.dup.freeze
|
|
18
23
|
@loaded_features = $LOADED_FEATURES.dup.freeze
|
|
19
24
|
@pwd = Dir.pwd.freeze
|
|
25
|
+
# The gems RubyGems had activated at this point. Under `bundle exec`
|
|
26
|
+
# that is the entire bundle, activated before OCRAN's first line runs,
|
|
27
|
+
# so a snapshot taken before the dependency run is what tells the build
|
|
28
|
+
# environment's gems apart from the application's.
|
|
29
|
+
@activated_gems = (defined?(Gem) ? Gem.loaded_specs.keys : []).freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether Bundler had already set this process up when the snapshot was
|
|
33
|
+
# taken. `bundle exec` arranges that through RUBYOPT (and, with newer
|
|
34
|
+
# RubyGems, through the BUNDLER_SETUP variable), so bundler/setup is in
|
|
35
|
+
# $LOADED_FEATURES before the command it runs gets to say anything.
|
|
36
|
+
def bundler_setup_loaded?
|
|
37
|
+
@loaded_features.any? { |feature| feature.to_s.match?(BUNDLER_SETUP_FEATURE) }
|
|
20
38
|
end
|
|
21
39
|
|
|
22
40
|
# Expands the given path using the working directory stored in this
|
data/lib/ocran/stub_builder.rb
CHANGED
|
@@ -21,6 +21,7 @@ module Ocran
|
|
|
21
21
|
CHDIR_BEFORE_SCRIPT = 0x08
|
|
22
22
|
DATA_COMPRESSED = 0x10
|
|
23
23
|
RUN_IN_EXE_DIR = 0x20
|
|
24
|
+
CHDIR_TO_EXE_DIR = 0x40
|
|
24
25
|
|
|
25
26
|
WINDOWS = Gem.win_platform?
|
|
26
27
|
|
|
@@ -77,6 +78,12 @@ module Ocran
|
|
|
77
78
|
# When set to true, the working directory is changed to the application's
|
|
78
79
|
# deployment location at runtime.
|
|
79
80
|
#
|
|
81
|
+
# chdir_to_exe_dir:
|
|
82
|
+
# When set to true, the working directory is changed to the directory
|
|
83
|
+
# containing the (stub) executable before the script starts. This makes
|
|
84
|
+
# relative file access resolve next to the executable regardless of how
|
|
85
|
+
# it was invoked. Mutually exclusive with chdir_before.
|
|
86
|
+
#
|
|
80
87
|
# debug_mode:
|
|
81
88
|
# When the debug_mode option is set to true, the stub will output debug information
|
|
82
89
|
# when the exe file is executed. Debug mode can also be enabled within the directive
|
|
@@ -103,9 +110,16 @@ module Ocran
|
|
|
103
110
|
# are installed next to the stub (pre-1.4/OCRA behavior). The directory
|
|
104
111
|
# is never deleted on exit.
|
|
105
112
|
#
|
|
106
|
-
|
|
113
|
+
# stub_path:
|
|
114
|
+
# Path to a stub binary to package with instead of the pre-built
|
|
115
|
+
# stub shipped with the gem (e.g. an APE stub freshly compiled with
|
|
116
|
+
# cosmocc, see --cosmo). When set, it takes precedence over both
|
|
117
|
+
# STUB_PATH and STUBW_PATH.
|
|
118
|
+
#
|
|
119
|
+
def initialize(path, chdir_before: nil, chdir_to_exe_dir: nil,
|
|
120
|
+
debug_extract: nil, debug_mode: nil,
|
|
107
121
|
enable_compression: nil, gui_mode: nil, icon_path: nil,
|
|
108
|
-
run_in_exe_dir: nil)
|
|
122
|
+
run_in_exe_dir: nil, stub_path: nil)
|
|
109
123
|
@dirs = FilePathSet.new
|
|
110
124
|
@files = FilePathSet.new
|
|
111
125
|
@data_size = 0
|
|
@@ -117,7 +131,9 @@ module Ocran
|
|
|
117
131
|
output_dir = File.dirname(path)
|
|
118
132
|
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
|
119
133
|
stub_tmp = File.join(output_dir, ".ocran_stub_#{$$}_#{Time.now.to_i}")
|
|
120
|
-
stub_src = if
|
|
134
|
+
stub_src = if stub_path
|
|
135
|
+
stub_path
|
|
136
|
+
elsif gui_mode && WINDOWS
|
|
121
137
|
STUBW_PATH
|
|
122
138
|
else
|
|
123
139
|
STUB_PATH
|
|
@@ -138,7 +154,7 @@ module Ocran
|
|
|
138
154
|
@of = of
|
|
139
155
|
@opcode_offset = @of.size
|
|
140
156
|
|
|
141
|
-
write_header(debug_mode, debug_extract, chdir_before, enable_compression, run_in_exe_dir)
|
|
157
|
+
write_header(debug_mode, debug_extract, chdir_before, enable_compression, run_in_exe_dir, chdir_to_exe_dir)
|
|
142
158
|
|
|
143
159
|
b = proc {
|
|
144
160
|
yield(self)
|
|
@@ -222,7 +238,7 @@ module Ocran
|
|
|
222
238
|
end
|
|
223
239
|
private :compress
|
|
224
240
|
|
|
225
|
-
def write_header(debug_mode, debug_extract, chdir_before, compressed, run_in_exe_dir = nil)
|
|
241
|
+
def write_header(debug_mode, debug_extract, chdir_before, compressed, run_in_exe_dir = nil, chdir_to_exe_dir = nil)
|
|
226
242
|
next_to_exe, delete_after = debug_extract, !debug_extract
|
|
227
243
|
if run_in_exe_dir
|
|
228
244
|
# Wrapper mode: run in place next to the executable — never extract,
|
|
@@ -235,7 +251,8 @@ module Ocran
|
|
|
235
251
|
(delete_after ? AUTO_CLEAN_INST_DIR : 0) |
|
|
236
252
|
(chdir_before ? CHDIR_BEFORE_SCRIPT : 0) |
|
|
237
253
|
(compressed ? DATA_COMPRESSED : 0) |
|
|
238
|
-
(run_in_exe_dir ? RUN_IN_EXE_DIR : 0)
|
|
254
|
+
(run_in_exe_dir ? RUN_IN_EXE_DIR : 0) |
|
|
255
|
+
(chdir_to_exe_dir ? CHDIR_TO_EXE_DIR : 0)
|
|
239
256
|
].pack("C")
|
|
240
257
|
end
|
|
241
258
|
private :write_header
|
data/lib/ocran/version.rb
CHANGED