ocran 1.4.6 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.txt +68 -0
  3. data/README.md +295 -10
  4. data/ext/extconf.rb +5 -1
  5. data/lib/ocran/aot_toolchain.rb +154 -0
  6. data/lib/ocran/command_output.rb +8 -1
  7. data/lib/ocran/compatibility_scan.rb +414 -0
  8. data/lib/ocran/cosmo_toolchain.rb +29 -7
  9. data/lib/ocran/dir_builder.rb +88 -38
  10. data/lib/ocran/direction.rb +190 -30
  11. data/lib/ocran/ed_resource.rb +113 -0
  12. data/lib/ocran/ed_resource_builder.rb +199 -0
  13. data/lib/ocran/file_path_set.rb +9 -5
  14. data/lib/ocran/gem_spec_queryable.rb +7 -3
  15. data/lib/ocran/host_config_helper.rb +11 -1
  16. data/lib/ocran/inno_setup_script_builder.rb +24 -14
  17. data/lib/ocran/launcher_batch_builder.rb +8 -11
  18. data/lib/ocran/npm_package.rb +106 -0
  19. data/lib/ocran/option.rb +425 -35
  20. data/lib/ocran/pe_imports.rb +96 -0
  21. data/lib/ocran/pe_resources.rb +226 -0
  22. data/lib/ocran/refine_pathname.rb +29 -32
  23. data/lib/ocran/roundhouse_builder.rb +316 -0
  24. data/lib/ocran/runner.rb +43 -11
  25. data/lib/ocran/runtime_sdk.rb +252 -0
  26. data/lib/ocran/spinel_builder.rb +183 -0
  27. data/lib/ocran/spinel_compatibility.rb +149 -0
  28. data/lib/ocran/static_runtime/boot.rb +443 -0
  29. data/lib/ocran/static_runtime/build_static_ext.rb +86 -0
  30. data/lib/ocran/static_runtime_builder.rb +814 -0
  31. data/lib/ocran/stub_builder.rb +70 -25
  32. data/lib/ocran/version.rb +1 -1
  33. data/lib/ocran/wasm_builder.rb +570 -0
  34. data/lib/ocran/wasm_compatibility.rb +198 -0
  35. data/lib/ocran/windows_command_escaping.rb +21 -0
  36. data/lib/ocran/zip_payload_builder.rb +18 -9
  37. data/lib/ocran/zip_writer.rb +48 -9
  38. data/src/Makefile +18 -2
  39. data/src/Makefile.mswin +65 -0
  40. data/src/inst_dir.c +34 -6
  41. data/src/path_utils.c +144 -0
  42. data/src/script_info.c +5 -0
  43. data/src/stub.c +77 -19
  44. data/src/stub.rc +1 -1
  45. data/src/system_utils.c +276 -155
  46. data/src/system_utils.h +46 -3
  47. data/src/system_utils_posix.c +243 -148
  48. data/src/unpack.c +9 -0
  49. metadata +22 -4
data/lib/ocran/option.rb CHANGED
@@ -6,15 +6,21 @@ module Ocran
6
6
  load File.expand_path("refine_pathname.rb", __dir__) unless defined? RefinePathname
7
7
  using RefinePathname
8
8
 
9
+ # Valid values for --set-requested-execution-level (manifest UAC levels).
10
+ EXECUTION_LEVELS = %w[asInvoker highestAvailable requireAdministrator].freeze
11
+
9
12
  def initialize
10
13
  @options = {
11
14
  :add_all_core? => false,
12
15
  :add_all_encoding? => true,
16
+ :application_manifest => nil,
13
17
  :argv => [],
14
18
  :auto_detect_dlls? => true,
15
19
  :bundle_identifier => nil,
16
20
  :macosx_bundle => nil,
17
21
  :macosx_bundle? => false,
22
+ :runtime_dlls => [],
23
+ :static_runtime => nil,
18
24
  :chdir_before? => false,
19
25
  :chdir_exe_dir? => false,
20
26
  :cosmo_cc => nil,
@@ -23,26 +29,44 @@ module Ocran
23
29
  :enable_compression? => true,
24
30
  :enable_debug_extract? => false,
25
31
  :enable_debug_mode? => false,
32
+ :execution_level => nil,
26
33
  :extra_dlls => [],
34
+ :file_version => nil,
27
35
  :force_console? => false,
28
36
  :force_windows? => false,
29
37
  :gem_options => [],
30
38
  :gemfile => nil,
31
39
  :icon_filename => nil,
40
+ :inputs => [],
32
41
  :inno_setup_script => nil,
33
42
  :load_autoload? => true,
34
43
  :output_dir => nil,
35
44
  :output_override => nil,
36
45
  :output_zip => nil,
46
+ :product_version => nil,
37
47
  :quiet? => false,
48
+ :resource_strings => {},
49
+ :roundhouse? => false,
50
+ :roundhouse_app => nil,
51
+ :roundhouse_options => [],
52
+ :roundhouse_output => nil,
38
53
  :rubyopt => nil,
39
54
  :run_script? => true,
40
55
  :script => nil,
41
56
  :source_files => [],
57
+ :spinel? => false,
58
+ :spinel_options => [],
42
59
  :verbose? => false,
60
+ :version_strings => {},
43
61
  :warning? => true,
62
+ :wasm_options => [],
63
+ :wasm_output => nil,
64
+ :wasm_ruby => nil,
65
+ :wasm_runtime => nil,
66
+ :wasm_zip? => false,
44
67
  :wrapper_exe? => true,
45
68
  }
69
+ @warnings = []
46
70
  end
47
71
 
48
72
  def usage
@@ -69,7 +93,7 @@ Gem content detection modes:
69
93
  --gem-guess=[gem1,...] Include loaded scripts & best guess (DEFAULT)
70
94
  --gem-all[=gem1,..] Include all scripts & files
71
95
  --gem-full[=gem1,..] Include EVERYTHING
72
- --gem-spec[=gem1,..] Include files in gemspec (Does not work with Rubygems 1.7+)
96
+ --gem-spec[=gem1,..] Include the files listed in the gemspec
73
97
 
74
98
  minimal: loaded scripts
75
99
  guess: loaded scripts and other files
@@ -86,7 +110,8 @@ Gem content detection modes:
86
110
 
87
111
  Auto-detection options:
88
112
 
89
- --no-dep-run Don't run script.rb to check for dependencies.
113
+ --no-dep-run Don't run script.rb to check for dependencies (usually
114
+ needs --add-all-core, and --gem-full with --gemfile).
90
115
  --no-autoload Don't load/include script.rb's autoloads.
91
116
  --no-autodll Disable detection of runtime DLL dependencies.
92
117
 
@@ -116,8 +141,38 @@ Executable options:
116
141
  --debug Executable will be verbose.
117
142
  --debug-extract Executable will unpack to local dir and not delete after.
118
143
 
144
+ PE resource options (Windows, rcedit-compatible):
145
+
146
+ --set-version-string <key> <value> Add a StringFileInfo entry (e.g. CompanyName).
147
+ Alias: --version-string
148
+ --set-file-version <x.y.z.w> Set the file version. Alias: --file-version
149
+ --set-product-version <x.y.z.w> Set the product version. Alias: --product-version
150
+ --set-resource-string <id> <value> Set an RT_STRING table entry by numeric id.
151
+ Alias: --resource-string
152
+ --set-requested-execution-level <level> Set the manifest UAC level (asInvoker,
153
+ highestAvailable, requireAdministrator).
154
+ Alias: --uac-level
155
+ --application-manifest <file> Embed the given file as the application manifest.
156
+ Alias: --manifest
157
+
119
158
  Experimental options:
120
159
 
160
+ --static-runtime <runtime-dir> Build a single executable that runs the
161
+ application without extracting anything: a statically
162
+ linked Ruby (the static runtime in <runtime-dir>, built
163
+ with the scripts in runtime/) with the application as a
164
+ ZIP archive. The runtime decides the target: Windows or
165
+ Linux (archive appended, readable with unzip or 7-Zip)
166
+ or macOS (archive linked in as section __OCRAN,__zip;
167
+ packaging needs a Mac). The runtime's own standard
168
+ library and bundled gems are packed; C extensions must
169
+ be linked into the runtime. The build host's Ruby must
170
+ have the runtime's API version (e.g. 4.0.0).
171
+ --runtime-dll <lib,...> With --static-runtime: link these C libraries of the
172
+ runtime's SDK as DLLs shipped next to the executable
173
+ instead of into it (e.g. iconv,sybdb - the LGPL ones;
174
+ see runtime/LICENSES.md). Their DLL dependencies come
175
+ along.
121
176
  --cosmo-ruby <ruby.com> Package the given cosmopolitan-built Ruby (an APE,
122
177
  e.g. ruby.com) as the interpreter instead of the host
123
178
  Ruby: the result is a single <scriptname>.com that runs
@@ -144,6 +199,50 @@ Experimental options:
144
199
  packages the host Ruby behind an APE stub. Console-only;
145
200
  output defaults to <scriptname>.com.
146
201
  (Alias: --cosmo-toolchain)
202
+
203
+ Native compilation (experimental):
204
+
205
+ --spinel Compile script.rb ahead of time with Spinel
206
+ (https://github.com/matz/spinel) into a native executable
207
+ that needs no Ruby, instead of packaging the interpreter.
208
+ Spinel compiles a subset of Ruby: when the program is
209
+ outside it, OCRAN reports what in your code and in which
210
+ gems stands in the way. The script is not run at build
211
+ time. Requires the spinel command (SPINEL, PATH, or
212
+ ~/.local/bin); OCRAN explains how to install it.
213
+ --spinel-opt <arg> Pass <arg> to the spinel compiler (repeatable), e.g.
214
+ --spinel-opt --int-overflow=promote
215
+ --roundhouse Compile a Rails application with Roundhouse
216
+ (https://github.com/rubys/roundhouse) and Spinel into a
217
+ native server binary. Give the application directory
218
+ instead of a script (default: the current directory).
219
+ The output is a directory (default <app>-spinel, or
220
+ --output/--output-dir) with the binary and the files it
221
+ serves. When the app is not covered yet, OCRAN shows
222
+ Roundhouse's analysis, including which gems it does not
223
+ support. Requires roundhouse, spinel and spin.
224
+ --roundhouse-opt <arg> Pass <arg> to roundhouse (repeatable), e.g.
225
+ --roundhouse-opt --survey
226
+
227
+ WebAssembly (experimental):
228
+
229
+ --wasm[=ruby|picoruby] Export the application as a web page that runs it
230
+ in the browser on WebAssembly: a folder (default
231
+ <scriptname>-wasm, or --output-dir) or a zip archive
232
+ (--output-zip, or an --output name ending in .zip) with
233
+ index.html, the runtime and the application.
234
+ ruby: ruby.wasm, CRuby with its standard library; needs
235
+ rbwasm (gem install ruby_wasm). Gems must be listed in
236
+ the application's Gemfile.
237
+ picoruby: PicoRuby, a 2 MB mruby-based runtime with its
238
+ own small library and no gems; needs nothing installed.
239
+ OCRAN reports what in the program and its gems may not
240
+ work there. The script is not run at build time.
241
+ --picoruby Same as --wasm=picoruby.
242
+ --wasm-ruby <x.y> The CRuby version for --wasm (4.0, 3.4, 3.3 or 3.2;
243
+ default: this Ruby's, when ruby.wasm has it).
244
+ --wasm-opt <arg> Pass <arg> to `rbwasm build` (repeatable), used when the
245
+ application has gems.
147
246
  EOF
148
247
  end
149
248
 
@@ -163,6 +262,32 @@ EOF
163
262
  load File.expand_path("cosmo_toolchain.rb", __dir__) unless defined? CosmoToolchain
164
263
  end
165
264
 
265
+ # The --gem-<group> options GemSpecQueryable.gem_inclusion_set knows,
266
+ # and the file sets among them that --no-gem-<set> can take away.
267
+ GEM_GROUPS = %i[minimal guess all full spec scripts files extras].freeze
268
+ GEM_FILE_SETS = %i[scripts files extras].freeze
269
+
270
+ # Warnings about the command line, for the caller to print once output
271
+ # is set up.
272
+ attr_reader :warnings
273
+
274
+ # The value of an option that takes a path or name: the next argument,
275
+ # which must be there and must not be empty.
276
+ def required_argument(argv, option)
277
+ value = argv.shift
278
+ raise "#{option} requires an argument" if value.nil? || value.empty?
279
+
280
+ value
281
+ end
282
+ private :required_argument
283
+
284
+ # Whether a --gem-* option makes every gem be packed with all of its
285
+ # scripts, which is what a build without the dependency run needs.
286
+ def whole_gems?
287
+ gem_options.any? { |negate, group, list| list.nil? && !negate && %i[all full spec scripts].include?(group) }
288
+ end
289
+ private :whole_gems?
290
+
166
291
  def parse(argv)
167
292
  while (arg = argv.shift)
168
293
  case arg
@@ -173,23 +298,19 @@ EOF
173
298
  when "--add-all-core"
174
299
  @options[:add_all_core?] = true
175
300
  when "--output"
176
- path = argv.shift
177
- @options[:output_override] = Pathname.new(path).expand_path if path
301
+ @options[:output_override] = Pathname.new(required_argument(argv, arg)).expand_path
178
302
  when "--output-dir"
179
- path = argv.shift
180
- @options[:output_dir] = Pathname.new(path).expand_path if path
303
+ @options[:output_dir] = Pathname.new(required_argument(argv, arg)).expand_path
181
304
  when "--output-zip"
182
- path = argv.shift
183
- @options[:output_zip] = Pathname.new(path).expand_path if path
305
+ @options[:output_zip] = Pathname.new(required_argument(argv, arg)).expand_path
184
306
  when "--no-wrapper-exe"
185
307
  @options[:wrapper_exe?] = false
186
308
  when "--macosx-bundle"
187
309
  @options[:macosx_bundle?] = true
188
310
  when "--bundle-id"
189
- @options[:bundle_identifier] = argv.shift
311
+ @options[:bundle_identifier] = required_argument(argv, arg)
190
312
  when "--dll"
191
- path = argv.shift
192
- @options[:extra_dlls] << path if path
313
+ @options[:extra_dlls] << required_argument(argv, arg)
193
314
  when "--quiet"
194
315
  @options[:quiet?] = true
195
316
  when "--verbose"
@@ -205,26 +326,81 @@ EOF
205
326
  when "--chdir-exe-dir"
206
327
  @options[:chdir_exe_dir?] = true
207
328
  when "--icon"
208
- path = argv.shift
209
- raise "Icon file #{path} not found" unless path && File.exist?(path)
329
+ path = required_argument(argv, arg)
330
+ raise "Icon file #{path} not found" unless File.exist?(path)
210
331
  @options[:icon_filename] = Pathname.new(path).expand_path
332
+ when "--set-version-string", "--version-string"
333
+ key = argv.shift
334
+ value = argv.shift
335
+ raise "#{arg} requires a key and a value" unless key && value
336
+ @options[:version_strings][key] = value
337
+ when "--set-file-version", "--file-version"
338
+ value = argv.shift
339
+ raise "#{arg} requires a version string" unless value
340
+ @options[:file_version] = value
341
+ when "--set-product-version", "--product-version"
342
+ value = argv.shift
343
+ raise "#{arg} requires a version string" unless value
344
+ @options[:product_version] = value
345
+ when "--set-resource-string", "--resource-string"
346
+ id = argv.shift
347
+ value = argv.shift
348
+ raise "#{arg} requires an id and a value" unless id && value
349
+ @options[:resource_strings][id.to_i] = value
350
+ when "--set-requested-execution-level", "--uac-level"
351
+ level = argv.shift
352
+ unless EXECUTION_LEVELS.include?(level)
353
+ raise "#{arg} must be one of: #{EXECUTION_LEVELS.join(", ")}"
354
+ end
355
+ @options[:execution_level] = level
356
+ when "--application-manifest", "--manifest"
357
+ path = argv.shift
358
+ raise "Manifest file #{path} not found" unless path && File.exist?(path)
359
+ @options[:application_manifest] = Pathname.new(path).expand_path
211
360
  when "--rubyopt"
212
- @options[:rubyopt] = argv.shift
361
+ # An empty value is meaningful: run with no RUBYOPT at all.
362
+ @options[:rubyopt] = argv.shift or raise "#{arg} requires an argument"
213
363
  when "--cosmo", "--cosmo-toolchain"
214
364
  # Kept unresolved until validation: resolving here would report
215
365
  # "cosmocc ... is not executable" on a Windows build host, ahead of
216
366
  # the clearer "not supported when building on Windows" check.
217
- @options[:cosmo_cc] = argv.shift
367
+ @options[:cosmo_cc] = required_argument(argv, arg)
368
+ when "--spinel"
369
+ @options[:spinel?] = true
370
+ when "--spinel-opt"
371
+ @options[:spinel_options] << required_argument(argv, arg)
372
+ when "--roundhouse"
373
+ @options[:roundhouse?] = true
374
+ when /\A--wasm(?:=(.*))?\z/
375
+ runtime = $1.nil? || $1.empty? ? "ruby" : $1
376
+ @options[:wasm_runtime] =
377
+ case runtime
378
+ when "ruby", "ruby.wasm", "cruby" then :ruby
379
+ when "picoruby" then :picoruby
380
+ else raise "Unknown --wasm runtime #{runtime.inspect} (use --wasm=ruby or --wasm=picoruby)"
381
+ end
382
+ when "--picoruby"
383
+ @options[:wasm_runtime] = :picoruby
384
+ when "--wasm-ruby"
385
+ @options[:wasm_ruby] = required_argument(argv, arg)
386
+ when "--wasm-opt"
387
+ @options[:wasm_options] << required_argument(argv, arg)
388
+ when "--roundhouse-opt"
389
+ @options[:roundhouse_options] << required_argument(argv, arg)
390
+ when "--static-runtime"
391
+ @options[:static_runtime] = Pathname.new(required_argument(argv, arg)).expand_path
392
+ when "--runtime-dll"
393
+ @options[:runtime_dlls] |= required_argument(argv, arg).split(",").map(&:strip).reject(&:empty?)
218
394
  when "--cosmo-ruby"
219
395
  load_cosmo_toolchain
220
396
  @options[:cosmo_ruby] = CosmoToolchain.resolve_ruby(argv.shift)
221
397
  when "--gemfile"
222
- path = argv.shift
223
- raise "Gemfile #{path} not found" unless path && File.exist?(path)
398
+ path = required_argument(argv, arg)
399
+ raise "Gemfile #{path} not found" unless File.exist?(path)
224
400
  @options[:gemfile] = Pathname.new(path).expand_path
225
401
  when "--innosetup"
226
- path = argv.shift
227
- raise "Inno Script #{path} not found" unless path && File.exist?(path)
402
+ path = required_argument(argv, arg)
403
+ raise "Inno Script #{path} not found" unless File.exist?(path)
228
404
  @options[:inno_setup_script] = Pathname.new(path).expand_path
229
405
  when "--no-autodll"
230
406
  @options[:auto_detect_dlls?] = false
@@ -246,25 +422,44 @@ EOF
246
422
  @options[:add_all_encoding?] = !$1
247
423
  when /\A--(no-)?gem-(\w+)(?:=(.*))?$/
248
424
  negate, group, list = $1, $2, $3
249
- @options[:gem_options] << [negate, group.to_sym, list&.split(",")] if group
250
- when "--help", "-h", /\A--./
425
+ group = group.to_sym
426
+ if negate ? !GEM_FILE_SETS.include?(group) : !GEM_GROUPS.include?(group)
427
+ raise "Invalid gem content detection option #{arg}: use " \
428
+ "#{GEM_GROUPS.map { |g| "--gem-#{g}" }.join(", ")} or " \
429
+ "#{GEM_FILE_SETS.map { |g| "--no-gem-#{g}" }.join(", ")}"
430
+ end
431
+ @options[:gem_options] << [negate, group, list&.split(",")]
432
+ when "--help", "-h"
251
433
  puts usage
252
434
  raise SystemExit
435
+ when /\A--./
436
+ raise "Unknown option #{arg} (see ocran --help)"
253
437
  else
254
- expanded = Dir.glob(arg)
255
- if expanded.empty?
256
- raise "#{arg} not found!" unless File.exist?(arg)
257
- expanded = [arg]
258
- end
438
+ @options[:inputs] << arg
439
+ end
440
+ end
259
441
 
260
- expanded.each do |f|
261
- if File.directory?(f)
262
- raise "#{f} is empty!" if Dir.empty?(f)
263
- # If a directory is passed, we want all files under that directory
264
- @options[:source_files] += Pathname.new(f).find.reject(&:directory?).map(&:expand_path)
265
- else
266
- @options[:source_files] << Pathname.new(f).expand_path
267
- end
442
+ if roundhouse?
443
+ # A Rails application directory rather than a script: none of the
444
+ # packaging below applies, and the application is not loaded.
445
+ parse_roundhouse
446
+ return
447
+ end
448
+
449
+ @options[:inputs].each do |arg|
450
+ expanded = Dir.glob(arg)
451
+ if expanded.empty?
452
+ raise "#{arg} not found!" unless File.exist?(arg)
453
+ expanded = [arg]
454
+ end
455
+
456
+ expanded.each do |f|
457
+ if File.directory?(f)
458
+ raise "#{f} is empty!" if Dir.empty?(f)
459
+ # If a directory is passed, we want all files under that directory
460
+ @options[:source_files] += Pathname.new(f).find.reject(&:directory?).map(&:expand_path)
461
+ else
462
+ @options[:source_files] << Pathname.new(f).expand_path
268
463
  end
269
464
  end
270
465
  end
@@ -273,6 +468,17 @@ EOF
273
468
 
274
469
  @options[:script] = source_files.first
275
470
 
471
+ if wasm_runtime
472
+ parse_wasm
473
+ end
474
+
475
+ if spinel?
476
+ reject_with_native_compilation("--spinel", output_dir: "--output-dir")
477
+ # Spinel compiles the source; it never needs the program to run, and
478
+ # running it would only pull OCRAN's dependency detection in.
479
+ @options[:run_script?] = false
480
+ end
481
+
276
482
  @options[:force_autoload?] = run_script? && load_autoload?
277
483
 
278
484
  @options[:output_executable] =
@@ -281,7 +487,8 @@ EOF
281
487
  # without .exe - `--output myapp`, as a build script shared with
282
488
  # Linux and macOS writes it - gets one there. .exe and .com names
283
489
  # are used as given.
284
- if Gem.win_platform? && !output_override.extname?(".exe") && !output_override.extname?(".com")
490
+ windows_target = static_runtime ? static_runtime_os == "windows" : Gem.win_platform?
491
+ if windows_target && !output_override.extname?(".exe") && !output_override.extname?(".com")
285
492
  Pathname("#{output_override}.exe")
286
493
  else
287
494
  output_override
@@ -294,6 +501,8 @@ EOF
294
501
  # APE binaries built with cosmocc conventionally use .com.
295
502
  ext = if cosmo?
296
503
  ".com"
504
+ elsif static_runtime
505
+ static_runtime_os == "windows" ? ".exe" : ""
297
506
  elsif Gem.win_platform?
298
507
  ".exe"
299
508
  else
@@ -311,6 +520,28 @@ EOF
311
520
  raise "--chdir-first and --chdir-exe-dir cannot be used together"
312
521
  end
313
522
 
523
+ if force_windows? && force_console?
524
+ raise "--windows and --console cannot be used together"
525
+ end
526
+
527
+ if inno_setup_script && (output_dir || output_zip)
528
+ raise "--innosetup cannot be combined with --output-dir or --output-zip"
529
+ end
530
+
531
+ unless run_script?
532
+ # Without the dependency run nothing the script loads is detected,
533
+ # so the libraries it needs must be named some other way.
534
+ unless add_all_core?
535
+ @warnings << "--no-dep-run without --add-all-core: the script is not run, so none of the " \
536
+ "standard library it requires is detected or packed"
537
+ end
538
+ if gemfile && !whole_gems?
539
+ @warnings << "--no-dep-run with --gemfile but without --gem-all or --gem-full: the script " \
540
+ "is not run, so no gem file it loads is detected, and the gems are packed " \
541
+ "without their scripts"
542
+ end
543
+ end
544
+
314
545
  @options[:use_inno_setup?] = !!inno_setup_script
315
546
 
316
547
  @options[:verbose?] &&= !quiet?
@@ -373,12 +604,112 @@ EOF
373
604
  if macosx_bundle && (output_dir || output_zip || inno_setup_script)
374
605
  raise "--macosx-bundle cannot be combined with --output-dir, --output-zip, or --innosetup"
375
606
  end
607
+
608
+ if static_runtime
609
+ reject_with_static_runtime
610
+ raise "#{static_runtime} is not a directory" unless static_runtime.directory?
611
+ end
612
+ raise "--runtime-dll needs --static-runtime" if runtime_dlls.any? && !static_runtime
376
613
  end
377
614
 
615
+ # --static-runtime produces one executable from a prebuilt runtime, so neither
616
+ # the other output formats nor what only the C stub implements apply.
617
+ def reject_with_static_runtime
618
+ conflicts = {
619
+ "--output-dir" => output_dir,
620
+ "--output-zip" => output_zip,
621
+ "--innosetup" => inno_setup_script,
622
+ "--macosx-bundle" => @options[:macosx_bundle?],
623
+ "--cosmo/--cosmo-ruby" => cosmo?,
624
+ "--spinel/--roundhouse" => spinel? || roundhouse?,
625
+ "--wasm" => wasm_runtime,
626
+ "--debug-extract" => enable_debug_extract?,
627
+ }
628
+ conflicts.each do |name, given|
629
+ raise "#{name} cannot be used with --static-runtime" if given
630
+ end
631
+ end
632
+ private :reject_with_static_runtime
633
+
634
+ # Validates --wasm and works out where the site goes: a zip archive for
635
+ # --output-zip or an --output name ending in .zip, a folder otherwise.
636
+ def parse_wasm
637
+ mode = wasm_runtime == :picoruby ? "--wasm=picoruby" : "--wasm"
638
+ reject_with_native_compilation(mode, output_zip: false)
639
+ raise "#{mode} cannot be used with --spinel or --roundhouse" if spinel? || roundhouse?
640
+ raise "--output-dir and --output-zip cannot be used together" if output_dir && output_zip
641
+
642
+ suffix = wasm_runtime == :picoruby ? "-picoruby" : "-wasm"
643
+ if output_zip || output_override&.extname?(".zip")
644
+ @options[:wasm_zip?] = true
645
+ @options[:wasm_output] = output_zip || output_override
646
+ else
647
+ @options[:wasm_output] = output_dir || output_override ||
648
+ Pathname("#{script.basename(".*")}#{suffix}").expand_path
649
+ end
650
+ # Nothing runs at build time: the browser is where the program runs.
651
+ @options[:run_script?] = false
652
+ end
653
+ private :parse_wasm
654
+
655
+ # Validates --roundhouse and works out the application and the output
656
+ # directory.
657
+ def parse_roundhouse
658
+ raise "--static-runtime cannot be used with --roundhouse" if static_runtime
659
+ raise "--roundhouse takes one Rails application directory" if @options[:inputs].size > 1
660
+ reject_with_native_compilation("--roundhouse")
661
+ raise "--spinel and --roundhouse cannot be used together (--roundhouse compiles with Spinel already)" if spinel?
662
+
663
+ input = Pathname(@options[:inputs].first || Dir.pwd).expand_path
664
+ raise "#{input} not found!" unless input.exist?
665
+
666
+ app = rails_root(input.directory? ? input : input.dirname)
667
+ unless app
668
+ raise "#{input} is not a Rails application (no config/application.rb in it or above it); " \
669
+ "--roundhouse compiles a Rails app - use --spinel for a plain script"
670
+ end
671
+
672
+ output = output_dir || output_override || Pathname("#{app.basename}-spinel").expand_path
673
+ raise "The output directory #{output} would overwrite the application itself" if output == app
674
+
675
+ @options[:roundhouse_app] = app
676
+ @options[:roundhouse_output] = output
677
+ @options[:run_script?] = false
678
+ @options[:force_autoload?] = false
679
+ @options[:verbose?] &&= !quiet?
680
+ end
681
+ private :parse_roundhouse
682
+
683
+ # The Rails application root at or above the given directory.
684
+ def rails_root(dir)
685
+ dir.ascend.find { |d| (d + "config" + "application.rb").file? }
686
+ end
687
+ private :rails_root
688
+
689
+ # Native compilation produces a program, not a package of one, so the
690
+ # options that shape a package do not apply to it.
691
+ def reject_with_native_compilation(mode, output_dir: nil, output_zip: true)
692
+ conflicts = {
693
+ "--innosetup" => inno_setup_script,
694
+ "--macosx-bundle" => @options[:macosx_bundle?],
695
+ "--cosmo/--cosmo-ruby" => cosmo?,
696
+ "--windows" => force_windows?,
697
+ "--icon" => icon_filename,
698
+ }
699
+ conflicts[output_dir] = self.output_dir if output_dir
700
+ conflicts["--output-zip"] = self.output_zip if output_zip
701
+ conflicts.each do |name, given|
702
+ raise "#{name} cannot be used with #{mode}" if given
703
+ end
704
+ end
705
+ private :reject_with_native_compilation
706
+
378
707
  def add_all_core? = @options[__method__]
379
708
 
380
709
  def add_all_encoding? = @options[__method__]
381
710
 
711
+ def application_manifest = @options[__method__]
712
+
382
713
  def argv = @options[__method__]
383
714
 
384
715
  def bundle_identifier = @options[__method__]
@@ -394,6 +725,23 @@ EOF
394
725
 
395
726
  def cosmo_ruby = @options[__method__]
396
727
 
728
+ # Directory of the static runtime given with --static-runtime, or nil.
729
+ def static_runtime = @options[__method__]
730
+
731
+ def static_runtime? = !!static_runtime
732
+
733
+ # The operating system the --static-runtime runtime is for, from its
734
+ # runtime.json ("windows" when that cannot be read yet).
735
+ def static_runtime_os
736
+ require "json"
737
+ JSON.parse(File.read(File.join(static_runtime, "runtime.json"))).fetch("os", "windows")
738
+ rescue SystemCallError, JSON::ParserError
739
+ "windows"
740
+ end
741
+
742
+ # C libraries --runtime-dll ships as DLLs next to the executable.
743
+ def runtime_dlls = @options[__method__]
744
+
397
745
  # True when the application is packaged by injecting it into the ZIP
398
746
  # store of the cosmopolitan Ruby itself: no cosmocc, no launcher stub,
399
747
  # and no extraction to a temporary directory at run time.
@@ -418,8 +766,12 @@ EOF
418
766
 
419
767
  def enable_debug_mode? = @options[__method__]
420
768
 
769
+ def execution_level = @options[__method__]
770
+
421
771
  def extra_dlls = @options[__method__]
422
772
 
773
+ def file_version = @options[__method__]
774
+
423
775
  def force_autoload? = @options[__method__]
424
776
 
425
777
  def force_console? = @options[__method__]
@@ -444,12 +796,42 @@ EOF
444
796
 
445
797
  def output_zip = @options[__method__]
446
798
 
799
+ def product_version = @options[__method__]
800
+
447
801
  def quiet? = @options[__method__]
448
802
 
803
+ def resource_strings = @options[__method__]
804
+
449
805
  def wrapper_exe? = @options[__method__]
450
806
 
451
807
  def rubyopt = @options[__method__]
452
808
 
809
+ def roundhouse? = @options[__method__]
810
+
811
+ # The root directory of the Rails application --roundhouse compiles.
812
+ def roundhouse_app = @options[__method__]
813
+
814
+ def roundhouse_options = @options[__method__]
815
+
816
+ # The directory --roundhouse writes the binary and its files to.
817
+ def roundhouse_output = @options[__method__]
818
+
819
+ def spinel? = @options[__method__]
820
+
821
+ def spinel_options = @options[__method__]
822
+
823
+ # :ruby (ruby.wasm) or :picoruby with --wasm, else nil.
824
+ def wasm_runtime = @options[__method__]
825
+
826
+ def wasm_options = @options[__method__]
827
+
828
+ def wasm_ruby = @options[__method__]
829
+
830
+ # The folder or zip archive --wasm writes.
831
+ def wasm_output = @options[__method__]
832
+
833
+ def wasm_zip? = @options[__method__]
834
+
453
835
  def run_script? = @options[__method__]
454
836
 
455
837
  def script = @options[__method__]
@@ -490,9 +872,17 @@ EOF
490
872
 
491
873
  def verbose? = @options[__method__]
492
874
 
875
+ def version_strings = @options[__method__]
876
+
493
877
  def warning? = @options[__method__]
494
878
 
495
879
  def windowed?
880
+ if static_runtime?
881
+ # The runtime decides, not the build host. For a runtime that is
882
+ # not for Windows only an explicit --windows counts, so that the
883
+ # builder can reject it; a .rbw script is just a script there.
884
+ return static_runtime_os == "windows" ? @options[:windowed?] : force_windows?
885
+ end
496
886
  return false unless Gem.win_platform?
497
887
  @options[:windowed?]
498
888
  end