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.
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
- ext = Gem.win_platform? ? ".exe" : ""
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
- attr_reader :env, :load_path, :loaded_features, :pwd
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
@@ -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
- def initialize(path, chdir_before: nil, debug_extract: nil, debug_mode: nil,
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 gui_mode && WINDOWS
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ocran
4
- VERSION = "1.4.4"
4
+ VERSION = "1.4.5"
5
5
  end