ocran 1.4.5 → 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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.txt +74 -0
  3. data/README.md +296 -9
  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 +232 -33
  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 +433 -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 +96 -11
  25. data/lib/ocran/runtime_environment.rb +14 -6
  26. data/lib/ocran/runtime_sdk.rb +252 -0
  27. data/lib/ocran/spinel_builder.rb +183 -0
  28. data/lib/ocran/spinel_compatibility.rb +149 -0
  29. data/lib/ocran/static_runtime/boot.rb +443 -0
  30. data/lib/ocran/static_runtime/build_static_ext.rb +86 -0
  31. data/lib/ocran/static_runtime_builder.rb +814 -0
  32. data/lib/ocran/stub_builder.rb +70 -25
  33. data/lib/ocran/version.rb +1 -1
  34. data/lib/ocran/wasm_builder.rb +570 -0
  35. data/lib/ocran/wasm_compatibility.rb +198 -0
  36. data/lib/ocran/windows_command_escaping.rb +21 -0
  37. data/lib/ocran/zip_payload_builder.rb +18 -9
  38. data/lib/ocran/zip_writer.rb +48 -9
  39. data/src/Makefile +18 -2
  40. data/src/Makefile.mswin +65 -0
  41. data/src/inst_dir.c +34 -6
  42. data/src/path_utils.c +144 -0
  43. data/src/script_info.c +5 -0
  44. data/src/stub.c +77 -19
  45. data/src/stub.rc +1 -1
  46. data/src/system_utils.c +276 -155
  47. data/src/system_utils.h +46 -3
  48. data/src/system_utils_posix.c +243 -148
  49. data/src/unpack.c +9 -0
  50. metadata +23 -5
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,11 +468,31 @@ 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] =
279
485
  if output_override
280
- output_override
486
+ # Windows runs a program only by its extension, so a name given
487
+ # without .exe - `--output myapp`, as a build script shared with
488
+ # Linux and macOS writes it - gets one there. .exe and .com names
489
+ # are used as given.
490
+ windows_target = static_runtime ? static_runtime_os == "windows" : Gem.win_platform?
491
+ if windows_target && !output_override.extname?(".exe") && !output_override.extname?(".com")
492
+ Pathname("#{output_override}.exe")
493
+ else
494
+ output_override
495
+ end
281
496
  else
282
497
  executable = script
283
498
  # If debug mode is enabled, append "-debug" to the filename
@@ -286,6 +501,8 @@ EOF
286
501
  # APE binaries built with cosmocc conventionally use .com.
287
502
  ext = if cosmo?
288
503
  ".com"
504
+ elsif static_runtime
505
+ static_runtime_os == "windows" ? ".exe" : ""
289
506
  elsif Gem.win_platform?
290
507
  ".exe"
291
508
  else
@@ -303,6 +520,28 @@ EOF
303
520
  raise "--chdir-first and --chdir-exe-dir cannot be used together"
304
521
  end
305
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
+
306
545
  @options[:use_inno_setup?] = !!inno_setup_script
307
546
 
308
547
  @options[:verbose?] &&= !quiet?
@@ -365,12 +604,112 @@ EOF
365
604
  if macosx_bundle && (output_dir || output_zip || inno_setup_script)
366
605
  raise "--macosx-bundle cannot be combined with --output-dir, --output-zip, or --innosetup"
367
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
368
613
  end
369
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
+
370
707
  def add_all_core? = @options[__method__]
371
708
 
372
709
  def add_all_encoding? = @options[__method__]
373
710
 
711
+ def application_manifest = @options[__method__]
712
+
374
713
  def argv = @options[__method__]
375
714
 
376
715
  def bundle_identifier = @options[__method__]
@@ -386,6 +725,23 @@ EOF
386
725
 
387
726
  def cosmo_ruby = @options[__method__]
388
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
+
389
745
  # True when the application is packaged by injecting it into the ZIP
390
746
  # store of the cosmopolitan Ruby itself: no cosmocc, no launcher stub,
391
747
  # and no extraction to a temporary directory at run time.
@@ -410,8 +766,12 @@ EOF
410
766
 
411
767
  def enable_debug_mode? = @options[__method__]
412
768
 
769
+ def execution_level = @options[__method__]
770
+
413
771
  def extra_dlls = @options[__method__]
414
772
 
773
+ def file_version = @options[__method__]
774
+
415
775
  def force_autoload? = @options[__method__]
416
776
 
417
777
  def force_console? = @options[__method__]
@@ -436,12 +796,42 @@ EOF
436
796
 
437
797
  def output_zip = @options[__method__]
438
798
 
799
+ def product_version = @options[__method__]
800
+
439
801
  def quiet? = @options[__method__]
440
802
 
803
+ def resource_strings = @options[__method__]
804
+
441
805
  def wrapper_exe? = @options[__method__]
442
806
 
443
807
  def rubyopt = @options[__method__]
444
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
+
445
835
  def run_script? = @options[__method__]
446
836
 
447
837
  def script = @options[__method__]
@@ -482,9 +872,17 @@ EOF
482
872
 
483
873
  def verbose? = @options[__method__]
484
874
 
875
+ def version_strings = @options[__method__]
876
+
485
877
  def warning? = @options[__method__]
486
878
 
487
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
488
886
  return false unless Gem.win_platform?
489
887
  @options[:windowed?]
490
888
  end