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.
- checksums.yaml +4 -4
- data/CHANGELOG.txt +68 -0
- data/README.md +295 -10
- data/ext/extconf.rb +5 -1
- data/lib/ocran/aot_toolchain.rb +154 -0
- data/lib/ocran/command_output.rb +8 -1
- data/lib/ocran/compatibility_scan.rb +414 -0
- data/lib/ocran/cosmo_toolchain.rb +29 -7
- data/lib/ocran/dir_builder.rb +88 -38
- data/lib/ocran/direction.rb +190 -30
- data/lib/ocran/ed_resource.rb +113 -0
- data/lib/ocran/ed_resource_builder.rb +199 -0
- data/lib/ocran/file_path_set.rb +9 -5
- data/lib/ocran/gem_spec_queryable.rb +7 -3
- data/lib/ocran/host_config_helper.rb +11 -1
- data/lib/ocran/inno_setup_script_builder.rb +24 -14
- data/lib/ocran/launcher_batch_builder.rb +8 -11
- data/lib/ocran/npm_package.rb +106 -0
- data/lib/ocran/option.rb +425 -35
- data/lib/ocran/pe_imports.rb +96 -0
- data/lib/ocran/pe_resources.rb +226 -0
- data/lib/ocran/refine_pathname.rb +29 -32
- data/lib/ocran/roundhouse_builder.rb +316 -0
- data/lib/ocran/runner.rb +43 -11
- data/lib/ocran/runtime_sdk.rb +252 -0
- data/lib/ocran/spinel_builder.rb +183 -0
- data/lib/ocran/spinel_compatibility.rb +149 -0
- data/lib/ocran/static_runtime/boot.rb +443 -0
- data/lib/ocran/static_runtime/build_static_ext.rb +86 -0
- data/lib/ocran/static_runtime_builder.rb +814 -0
- data/lib/ocran/stub_builder.rb +70 -25
- data/lib/ocran/version.rb +1 -1
- data/lib/ocran/wasm_builder.rb +570 -0
- data/lib/ocran/wasm_compatibility.rb +198 -0
- data/lib/ocran/windows_command_escaping.rb +21 -0
- data/lib/ocran/zip_payload_builder.rb +18 -9
- data/lib/ocran/zip_writer.rb +48 -9
- data/src/Makefile +18 -2
- data/src/Makefile.mswin +65 -0
- data/src/inst_dir.c +34 -6
- data/src/path_utils.c +144 -0
- data/src/script_info.c +5 -0
- data/src/stub.c +77 -19
- data/src/stub.rc +1 -1
- data/src/system_utils.c +276 -155
- data/src/system_utils.h +46 -3
- data/src/system_utils_posix.c +243 -148
- data/src/unpack.c +9 -0
- metadata +22 -4
data/lib/ocran/dir_builder.rb
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
require "pathname"
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require_relative "build_constants"
|
|
5
|
+
require_relative "windows_command_escaping"
|
|
5
6
|
|
|
6
7
|
module Ocran
|
|
7
8
|
# Builder that outputs all files to a plain directory instead of a self-extracting
|
|
8
9
|
# executable. A launch script (`.sh` on POSIX, `.bat` on Windows) is written at
|
|
9
10
|
# the root of the directory so the packaged app can be started directly.
|
|
10
11
|
class DirBuilder
|
|
11
|
-
include BuildConstants
|
|
12
|
+
include BuildConstants, WindowsCommandEscaping
|
|
12
13
|
|
|
13
14
|
WINDOWS = Gem.win_platform?
|
|
14
15
|
|
|
@@ -17,8 +18,20 @@ module Ocran
|
|
|
17
18
|
# by Direction to build the optional wrapper executable.
|
|
18
19
|
attr_reader :env, :exec_args
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
# +script_name+ names the launch script (without extension); it defaults
|
|
22
|
+
# to the name of the script that is executed. Direction passes the
|
|
23
|
+
# user's script, because what is executed can be a generated launcher
|
|
24
|
+
# (see Direction#generate_rubyopt_launcher).
|
|
25
|
+
#
|
|
26
|
+
# +chdir_before+ and +chdir_exe_dir+ mirror the stub flags of the same
|
|
27
|
+
# names (--chdir-first, --chdir-exe-dir): the launch script changes into
|
|
28
|
+
# the directory of the packed script, or into its own directory, which
|
|
29
|
+
# is where the output directory keeps the application.
|
|
30
|
+
def initialize(path, script_name: nil, chdir_before: false, chdir_exe_dir: false)
|
|
21
31
|
@path = Pathname(path)
|
|
32
|
+
@script_name = script_name
|
|
33
|
+
@chdir_before = chdir_before
|
|
34
|
+
@chdir_exe_dir = chdir_exe_dir
|
|
22
35
|
@path.mkpath
|
|
23
36
|
@env = {}
|
|
24
37
|
@exec_args = nil
|
|
@@ -55,17 +68,33 @@ module Ocran
|
|
|
55
68
|
@exec_args = [image.to_s, script.to_s, argv.map(&:to_s)]
|
|
56
69
|
end
|
|
57
70
|
|
|
58
|
-
#
|
|
71
|
+
# PowerShell command for create_zip on Windows. The paths come in through
|
|
72
|
+
# the environment rather than being spliced into the command, so no
|
|
73
|
+
# character in them (a quote in a user name, brackets) can change its
|
|
74
|
+
# meaning; -LiteralPath keeps wildcards in the source path literal.
|
|
75
|
+
POWERSHELL_ZIP_COMMAND =
|
|
76
|
+
"$ErrorActionPreference = 'Stop'; " \
|
|
77
|
+
"Get-ChildItem -LiteralPath $env:OCRAN_ZIP_SOURCE -Force | " \
|
|
78
|
+
"Compress-Archive -DestinationPath $env:OCRAN_ZIP_DESTINATION"
|
|
79
|
+
|
|
80
|
+
# Create a zip archive from a source directory, replacing any archive
|
|
81
|
+
# already at +zip_path+.
|
|
59
82
|
# Uses the `zip` command on POSIX and PowerShell on Windows.
|
|
60
83
|
def self.create_zip(zip_path, source_dir)
|
|
61
84
|
zip_path = File.expand_path(zip_path.to_s)
|
|
85
|
+
# Both tools add to an existing archive rather than replace it (zip
|
|
86
|
+
# keeps entries the new tree no longer has; Compress-Archive refuses
|
|
87
|
+
# without -Force), so start from nothing.
|
|
88
|
+
FileUtils.rm_f(zip_path)
|
|
62
89
|
if Gem.win_platform?
|
|
63
|
-
|
|
64
|
-
|
|
90
|
+
env = { "OCRAN_ZIP_SOURCE" => source_dir.to_s, "OCRAN_ZIP_DESTINATION" => zip_path }
|
|
91
|
+
system(env, "powershell", "-NoProfile", "-NonInteractive", "-Command", POWERSHELL_ZIP_COMMAND,
|
|
65
92
|
exception: true)
|
|
66
93
|
else
|
|
94
|
+
# -y stores symlinks (the libruby.so aliases) as links; without it
|
|
95
|
+
# zip follows them and stores the library once per alias.
|
|
67
96
|
Dir.chdir(source_dir) do
|
|
68
|
-
system("zip", "-r", zip_path, ".", exception: true)
|
|
97
|
+
system("zip", "-q", "-r", "-y", zip_path, ".", exception: true)
|
|
69
98
|
end
|
|
70
99
|
end
|
|
71
100
|
end
|
|
@@ -84,13 +113,41 @@ module Ocran
|
|
|
84
113
|
write_launch_script
|
|
85
114
|
end
|
|
86
115
|
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
def
|
|
90
|
-
|
|
116
|
+
# Anchors a relative packed path at the extraction root placeholder;
|
|
117
|
+
# absolute paths are kept as they are.
|
|
118
|
+
def root_path(path)
|
|
119
|
+
return path if path.start_with?("#{EXTRACT_ROOT}/") || File.absolute_path?(path)
|
|
120
|
+
"#{EXTRACT_ROOT}/#{path}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Quotes +value+ as one POSIX shell word. The extraction root placeholder
|
|
124
|
+
# becomes "$SCRIPT_DIR"; everything else is single-quoted, so the shell
|
|
125
|
+
# expands nothing in it.
|
|
126
|
+
def shell_word(value)
|
|
127
|
+
word = value.split("#{EXTRACT_ROOT}/", -1).map { |part|
|
|
128
|
+
part.empty? ? "" : "'#{part.gsub("'", %q('"'"'))}'"
|
|
129
|
+
}.join('"$SCRIPT_DIR"/')
|
|
130
|
+
word.empty? ? "''" : word
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The batch script's reference to its own directory, which ends in a
|
|
134
|
+
# backslash; see WindowsCommandEscaping#batch_value.
|
|
135
|
+
BATCH_SCRIPT_DIR = "%SCRIPT_DIR%"
|
|
136
|
+
|
|
137
|
+
# The directory the launch script changes into before it starts the
|
|
138
|
+
# application, as a packed path (see root_path), or nil to stay put.
|
|
139
|
+
def chdir_target
|
|
140
|
+
if @chdir_exe_dir
|
|
141
|
+
EXTRACT_ROOT.to_s
|
|
142
|
+
elsif @chdir_before && @exec_args
|
|
143
|
+
dir = File.dirname(@exec_args[1])
|
|
144
|
+
dir == "." ? EXTRACT_ROOT.to_s : root_path(dir)
|
|
145
|
+
end
|
|
91
146
|
end
|
|
92
147
|
|
|
93
148
|
def script_basename
|
|
149
|
+
return @script_name.to_s if @script_name
|
|
150
|
+
|
|
94
151
|
@exec_args ? Pathname(@exec_args[1]).basename.sub_ext("").to_s : "run"
|
|
95
152
|
end
|
|
96
153
|
|
|
@@ -107,24 +164,18 @@ module Ocran
|
|
|
107
164
|
]
|
|
108
165
|
|
|
109
166
|
@env.each do |name, value|
|
|
110
|
-
|
|
111
|
-
|
|
167
|
+
lines << "export #{name}=#{shell_word(value)}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
if (dir = chdir_target)
|
|
171
|
+
target = dir == EXTRACT_ROOT.to_s ? '"$SCRIPT_DIR"' : shell_word(dir)
|
|
172
|
+
lines << "cd #{target} || exit 1"
|
|
112
173
|
end
|
|
113
174
|
|
|
114
175
|
if @exec_args
|
|
115
176
|
image, script, argv = @exec_args
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
# Prepend SCRIPT_DIR to relative paths
|
|
120
|
-
image_r = "$SCRIPT_DIR/#{image_r}" unless image_r.start_with?("/", "$SCRIPT_DIR")
|
|
121
|
-
script_r = "$SCRIPT_DIR/#{script_r}" unless script_r.start_with?("/", "$SCRIPT_DIR")
|
|
122
|
-
|
|
123
|
-
args = argv.map { |a| "\"#{replace_root(a, "$SCRIPT_DIR")}\"" }.join(" ")
|
|
124
|
-
exec_line = "exec \"#{image_r}\" \"#{script_r}\""
|
|
125
|
-
exec_line += " #{args}" unless args.empty?
|
|
126
|
-
exec_line += ' "$@"'
|
|
127
|
-
lines << exec_line
|
|
177
|
+
words = [root_path(image), root_path(script), *argv].map { |a| shell_word(a) }
|
|
178
|
+
lines << "exec #{words.join(" ")} \"$@\""
|
|
128
179
|
end
|
|
129
180
|
|
|
130
181
|
File.write(script_path, lines.join("\n") + "\n")
|
|
@@ -134,29 +185,28 @@ module Ocran
|
|
|
134
185
|
def write_batch_script
|
|
135
186
|
script_path = @path / "#{script_basename}.bat"
|
|
136
187
|
|
|
188
|
+
# setlocal keeps the variables and the working directory set here from
|
|
189
|
+
# outliving the script in the cmd.exe session that ran it.
|
|
137
190
|
lines = [
|
|
138
191
|
"@echo off",
|
|
139
|
-
"
|
|
192
|
+
"setlocal",
|
|
193
|
+
'set "SCRIPT_DIR=%~dp0"',
|
|
140
194
|
]
|
|
141
195
|
|
|
142
196
|
@env.each do |name, value|
|
|
143
|
-
|
|
144
|
-
|
|
197
|
+
lines << "set \"#{name}=#{batch_value(value, BATCH_SCRIPT_DIR).tr("/", "\\")}\""
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
if (dir = chdir_target)
|
|
201
|
+
target = dir == EXTRACT_ROOT.to_s ? BATCH_SCRIPT_DIR : batch_value(dir, BATCH_SCRIPT_DIR).tr("/", "\\")
|
|
202
|
+
lines << "cd /d #{quote_and_escape(target)} || exit /b 1"
|
|
145
203
|
end
|
|
146
204
|
|
|
147
205
|
if @exec_args
|
|
148
206
|
image, script, argv = @exec_args
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
image_r = "%SCRIPT_DIR%#{image_r}" unless image_r.include?("%SCRIPT_DIR%") || File.absolute_path?(image_r)
|
|
153
|
-
script_r = "%SCRIPT_DIR%#{script_r}" unless script_r.include?("%SCRIPT_DIR%") || File.absolute_path?(script_r)
|
|
154
|
-
|
|
155
|
-
args = argv.map { |a| replace_root(a, "%SCRIPT_DIR%") }.join(" ")
|
|
156
|
-
exec_line = "\"#{image_r}\" \"#{script_r}\""
|
|
157
|
-
exec_line += " #{args}" unless args.empty?
|
|
158
|
-
exec_line += " %*"
|
|
159
|
-
lines << exec_line
|
|
207
|
+
words = [root_path(image), root_path(script)].map { |p| batch_value(p, BATCH_SCRIPT_DIR).tr("/", "\\") }
|
|
208
|
+
words += argv.map { |a| batch_value(a, BATCH_SCRIPT_DIR) }
|
|
209
|
+
lines << "#{words.map { |w| quote_and_escape(w) }.join(" ")} %*"
|
|
160
210
|
end
|
|
161
211
|
|
|
162
212
|
File.write(script_path, lines.join("\r\n") + "\r\n")
|
data/lib/ocran/direction.rb
CHANGED
|
@@ -16,7 +16,9 @@ module Ocran
|
|
|
16
16
|
# - /ruby/3.0.0/
|
|
17
17
|
# - /ruby/site_ruby/3.0.0/
|
|
18
18
|
# - /ruby/vendor_ruby/3.0.0/
|
|
19
|
-
|
|
19
|
+
# The version directory is RbConfig's ruby_version, which development
|
|
20
|
+
# builds suffix with their ABI version (e.g. "4.1.0+5").
|
|
21
|
+
RUBY_LIBRARY_PATH_REGEX = %r{/(ruby/(?:site_ruby/|vendor_ruby/)?\d+\.\d+\.\d+(?:\+\d+)?)/?$}i
|
|
20
22
|
|
|
21
23
|
# Core libraries provided by glibc and the dynamic loader. These must
|
|
22
24
|
# never be bundled: they are tightly coupled to the target system's
|
|
@@ -35,6 +37,9 @@ module Ocran
|
|
|
35
37
|
# payload, which is statically linked and has no dlopen.
|
|
36
38
|
NATIVE_BINARY_EXTENSIONS = %w[.so .bundle .dll].freeze
|
|
37
39
|
|
|
40
|
+
# Visual C++ 2015-2022 runtime DLLs (Microsoft.VC14x.CRT redistributables)
|
|
41
|
+
VC_RUNTIME_DLL_RE = /\A(?:vcruntime140(?:_\d+)?|msvcp140(?:_\w+)?|concrt140|vccorlib140|vcomp140)\.dll\z/i
|
|
42
|
+
|
|
38
43
|
# Native binaries a gem ships or has had built for it, i.e. the files
|
|
39
44
|
# that make it unusable under the cosmopolitan Ruby payload.
|
|
40
45
|
#
|
|
@@ -176,7 +181,48 @@ module Ocran
|
|
|
176
181
|
else
|
|
177
182
|
require_relative "library_detector_posix"
|
|
178
183
|
end
|
|
179
|
-
LibraryDetector.loaded_dlls.map { |path| Pathname.new(path).cleanpath }
|
|
184
|
+
dlls = LibraryDetector.loaded_dlls.map { |path| Pathname.new(path).cleanpath }
|
|
185
|
+
# A Ruby reached through a junction (the "current" link of rb or
|
|
186
|
+
# scoop) loads its DLLs by the junction path, while RbConfig reports
|
|
187
|
+
# the real prefix. Resolve them, or no DLL is recognised as part of
|
|
188
|
+
# the Ruby installation.
|
|
189
|
+
dlls.map! { |dll| dll.realpath rescue dll } if Gem.win_platform?
|
|
190
|
+
dlls
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# The DLLs that the packed ruby.exe (or rubyw.exe), the Ruby DLL and
|
|
194
|
+
# the native extensions among +features+ import, directly or through
|
|
195
|
+
# each other, that are found in the Ruby bin directory, and the Visual
|
|
196
|
+
# C++ runtime DLLs among them (from the system directory). System DLLs,
|
|
197
|
+
# and DLLs elsewhere that only the dependency run can find, are not
|
|
198
|
+
# included.
|
|
199
|
+
def windows_imported_dlls(features)
|
|
200
|
+
require_relative "pe_imports"
|
|
201
|
+
|
|
202
|
+
bin_dlls = bindir.children.select { |path| path.extname?(".dll") && path.file? }
|
|
203
|
+
.to_h { |path| [path.basename.to_s.downcase, path] }
|
|
204
|
+
system_dir = Pathname((ENV["SystemRoot"] || "C:/Windows").tr("\\", "/")) / "System32"
|
|
205
|
+
seen = Set.new
|
|
206
|
+
seen << libruby_so.to_s.downcase if libruby_so
|
|
207
|
+
found = []
|
|
208
|
+
queue = [bindir / ruby_executable]
|
|
209
|
+
queue << bindir / libruby_so if libruby_so
|
|
210
|
+
queue += features.select { |f| f.extname?(".so") }
|
|
211
|
+
while (image = queue.shift)
|
|
212
|
+
next unless image.file?
|
|
213
|
+
|
|
214
|
+
PEImports.dll_names(image).each do |name|
|
|
215
|
+
next unless seen.add?(name.downcase)
|
|
216
|
+
|
|
217
|
+
dll = bin_dlls[name.downcase]
|
|
218
|
+
dll ||= system_dir / name if name.match?(VC_RUNTIME_DLL_RE) && (system_dir / name).file?
|
|
219
|
+
next unless dll
|
|
220
|
+
|
|
221
|
+
found << dll
|
|
222
|
+
queue << dll
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
found
|
|
180
226
|
end
|
|
181
227
|
|
|
182
228
|
def find_gemspecs(features)
|
|
@@ -484,8 +530,17 @@ module Ocran
|
|
|
484
530
|
end
|
|
485
531
|
end
|
|
486
532
|
|
|
533
|
+
# Windows-only: DLLs bundled into bin, next to ruby.exe, by basename
|
|
534
|
+
dlls_in_bin = Set.new
|
|
535
|
+
add_dll_to_bin = proc do |dll|
|
|
536
|
+
next if dll.subpath?(bindir) || !dlls_in_bin.add?(dll.basename.to_s.downcase)
|
|
537
|
+
|
|
538
|
+
say "Adding detected DLL #{dll} to bin"
|
|
539
|
+
builder.copy_to_bin(dll, dll.basename)
|
|
540
|
+
end
|
|
541
|
+
|
|
487
542
|
# Windows-only: Add detected DLLs
|
|
488
|
-
if Gem.win_platform? && @option.auto_detect_dlls?
|
|
543
|
+
if Gem.win_platform? && @option.auto_detect_dlls? && !@option.static_runtime?
|
|
489
544
|
# The Windows loader resolves the imports of a native extension from
|
|
490
545
|
# the extension's own directory, the application directory of the
|
|
491
546
|
# packed ruby.exe (bin) plus its SxS assembly (ruby_builtin_dlls),
|
|
@@ -502,16 +557,17 @@ module Ocran
|
|
|
502
557
|
# LD_LIBRARY_PATH. DLLs from the Windows directory always come from
|
|
503
558
|
# the target system and are never bundled.
|
|
504
559
|
windows_dir = Pathname(ENV["SystemRoot"] || "C:/Windows")
|
|
505
|
-
dlls_in_bin = Set.new
|
|
506
|
-
add_dll_to_bin = proc do |dll|
|
|
507
|
-
next if dll.subpath?(bindir) || !dlls_in_bin.add?(dll.basename.to_s.downcase)
|
|
508
|
-
|
|
509
|
-
say "Adding detected DLL #{dll} to bin"
|
|
510
|
-
builder.copy_to_bin(dll, dll.basename)
|
|
511
|
-
end
|
|
512
560
|
|
|
513
561
|
detect_dlls.each do |dll|
|
|
514
562
|
next unless dll.extname?(".dll") && dll.basename != libruby_so
|
|
563
|
+
# The Visual C++ runtime is not part of Windows: an mswin Ruby
|
|
564
|
+
# (and any MSVC-built DLL a gem loads) fails to start with
|
|
565
|
+
# 0xC0000135 where the VC++ Redistributable is not installed.
|
|
566
|
+
# Microsoft permits deploying it app-locally, next to ruby.exe.
|
|
567
|
+
if dll.basename.to_s.match?(VC_RUNTIME_DLL_RE)
|
|
568
|
+
add_dll_to_bin.call(dll)
|
|
569
|
+
next
|
|
570
|
+
end
|
|
515
571
|
next if dll.subpath?(windows_dir)
|
|
516
572
|
|
|
517
573
|
if dll.subpath?(exec_prefix)
|
|
@@ -531,7 +587,7 @@ module Ocran
|
|
|
531
587
|
# the same extension packed at a gem path (openssl and psych are
|
|
532
588
|
# gems since Ruby 3.x) resolves its imports from bin.
|
|
533
589
|
features.select { |f| f.extname?(".so") && f.subpath?(exec_prefix) }
|
|
534
|
-
.map(&:dirname).uniq
|
|
590
|
+
.map(&:dirname).uniq { |dir| dir.path_key }
|
|
535
591
|
.each do |dir|
|
|
536
592
|
dir.each_child do |path|
|
|
537
593
|
next unless path.file? && path.extname?(".dll")
|
|
@@ -542,6 +598,25 @@ module Ocran
|
|
|
542
598
|
end
|
|
543
599
|
end
|
|
544
600
|
|
|
601
|
+
# Windows-only: DLLs that ruby.exe, the Ruby DLL and the packed native
|
|
602
|
+
# extensions import from the Ruby bin directory, plus the Visual C++
|
|
603
|
+
# runtime. RubyInstaller keeps such DLLs in ruby_builtin_dlls, which is
|
|
604
|
+
# packed whole below; an mswin Ruby keeps them (gmp, libffi, OpenSSL,
|
|
605
|
+
# libyaml, zlib from vcpkg) next to ruby.exe, and its interpreter does
|
|
606
|
+
# not even start without gmp and vcruntime140. They are read from the
|
|
607
|
+
# PE import tables, so they are packed with --no-autodll too, and for
|
|
608
|
+
# extensions the dependency run did not exercise.
|
|
609
|
+
if Gem.win_platform?
|
|
610
|
+
windows_imported_dlls(features).each do |dll|
|
|
611
|
+
if dll.subpath?(bindir)
|
|
612
|
+
verbose "Adding imported DLL #{dll}"
|
|
613
|
+
builder.duplicate_to_exec_prefix(dll)
|
|
614
|
+
else
|
|
615
|
+
add_dll_to_bin.call(dll)
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
545
620
|
# Linux: bundle detected shared libraries (e.g. libyaml, libssl,
|
|
546
621
|
# libcrypt) next to libruby so the executable also runs on systems
|
|
547
622
|
# where they are missing or have different sonames (the stub already
|
|
@@ -549,7 +624,7 @@ module Ocran
|
|
|
549
624
|
# libraries and the loader are never bundled - they must come from the
|
|
550
625
|
# target system. Ruby native extensions are packed as features, not
|
|
551
626
|
# here. Not needed with --cosmo-ruby: the APE payload is static.
|
|
552
|
-
if RUBY_PLATFORM.include?("linux") && @option.auto_detect_dlls? && !@option.cosmo_ruby
|
|
627
|
+
if RUBY_PLATFORM.include?("linux") && @option.auto_detect_dlls? && !@option.cosmo_ruby && !@option.static_runtime?
|
|
553
628
|
feature_set = features.to_set
|
|
554
629
|
feature_realpaths = features.filter_map { |f| f.realpath rescue nil }.to_set
|
|
555
630
|
# Ruby native extensions live in these directories and are packed as
|
|
@@ -851,7 +926,9 @@ module Ocran
|
|
|
851
926
|
|
|
852
927
|
actual_files
|
|
853
928
|
end
|
|
854
|
-
|
|
929
|
+
# Keyed on Pathname#path_key here and below: Array#uniq and Set do not
|
|
930
|
+
# see the refined, case-insensitive Pathname#eql? on Windows.
|
|
931
|
+
gem_files.uniq! { |file| file.path_key }
|
|
855
932
|
|
|
856
933
|
# On some distros parts of a gem are reachable through symlinks at other
|
|
857
934
|
# locations (e.g. Fedora symlinks /usr/share/ruby/psych.rb into the
|
|
@@ -859,16 +936,17 @@ module Ocran
|
|
|
859
936
|
# the same file under different paths. Compare realpaths when removing
|
|
860
937
|
# gem files from the feature list; otherwise a stdlib-level duplicate
|
|
861
938
|
# would be packed as well and shadow the packed gem at runtime.
|
|
862
|
-
gem_file_set = (gem_files + gem_files.filter_map { |file| file.realpath rescue nil })
|
|
939
|
+
gem_file_set = (gem_files + gem_files.filter_map { |file| file.realpath rescue nil })
|
|
940
|
+
.to_set { |file| file.path_key }
|
|
863
941
|
features = features.reject do |feature|
|
|
864
|
-
next true if gem_file_set.include?(feature)
|
|
942
|
+
next true if gem_file_set.include?(feature.path_key)
|
|
865
943
|
|
|
866
944
|
real = begin
|
|
867
945
|
feature.realpath
|
|
868
946
|
rescue SystemCallError
|
|
869
947
|
nil
|
|
870
948
|
end
|
|
871
|
-
real && gem_file_set.include?(real)
|
|
949
|
+
real && gem_file_set.include?(real.path_key)
|
|
872
950
|
end
|
|
873
951
|
|
|
874
952
|
# If requested, add all ruby standard libraries
|
|
@@ -883,7 +961,6 @@ module Ocran
|
|
|
883
961
|
end
|
|
884
962
|
path.find.each do |src|
|
|
885
963
|
next if src.directory?
|
|
886
|
-
a = Pathname(subdir) / src.relative_path_from(path)
|
|
887
964
|
builder.copy_to_lib(src, Pathname(subdir) / src.relative_path_from(path))
|
|
888
965
|
end
|
|
889
966
|
end
|
|
@@ -912,7 +989,8 @@ module Ocran
|
|
|
912
989
|
end
|
|
913
990
|
|
|
914
991
|
# Windows-only: Workaround for RubyInstaller MSYS folder detection
|
|
915
|
-
|
|
992
|
+
# (mswin Ruby, built with Visual C++, has no MSYS2 devkit)
|
|
993
|
+
if Gem.win_platform? && !RUBY_PLATFORM.include?("mswin")
|
|
916
994
|
# RubyInstaller cannot find the msys folder if ../msys64/usr/bin/msys-2.0.dll is not present
|
|
917
995
|
# (since RubyInstaller-2.4.1 rubyinstaller 2 issue 23)
|
|
918
996
|
builder.touch('msys64/usr/bin/msys-2.0.dll')
|
|
@@ -1082,7 +1160,7 @@ module Ocran
|
|
|
1082
1160
|
neutralize_bundler_env(builder)
|
|
1083
1161
|
# Add the load path that are required with the correct path after
|
|
1084
1162
|
# src_prefix was adjusted.
|
|
1085
|
-
load_path = src_load_path.map { |path| SRCDIR / path.relative_path_from(inst_src_prefix) }.uniq
|
|
1163
|
+
load_path = src_load_path.map { |path| SRCDIR / path.relative_path_from(inst_src_prefix) }.uniq { |path| path.path_key }
|
|
1086
1164
|
|
|
1087
1165
|
# On POSIX systems, also add the packed Ruby standard library directories
|
|
1088
1166
|
# to RUBYLIB. The Ruby binary has a compiled-in prefix pointing to the build
|
|
@@ -1121,7 +1199,7 @@ module Ocran
|
|
|
1121
1199
|
prefix_gem_dirs = (Gem.path.map { |dir| Pathname(dir) } + [Pathname(Gem.default_dir)])
|
|
1122
1200
|
.select { |dir| dir.subpath?(exec_prefix) }
|
|
1123
1201
|
.map { |dir| dir.relative_path_from(exec_prefix) }
|
|
1124
|
-
.uniq
|
|
1202
|
+
.uniq { |dir| dir.path_key }
|
|
1125
1203
|
gem_paths += prefix_gem_dirs
|
|
1126
1204
|
# RubyGems probes the default gem dir for writability at startup and
|
|
1127
1205
|
# prints "Can't determine writability of default gem path" on stderr
|
|
@@ -1295,6 +1373,20 @@ module Ocran
|
|
|
1295
1373
|
end
|
|
1296
1374
|
end
|
|
1297
1375
|
|
|
1376
|
+
# The PE resource options (version info, string table, manifest), for
|
|
1377
|
+
# every Windows executable the user launches: the self-extracting exe and
|
|
1378
|
+
# the wrapper that installer, directory and zip output start from.
|
|
1379
|
+
def pe_resource_options
|
|
1380
|
+
{
|
|
1381
|
+
version_strings: @option.version_strings,
|
|
1382
|
+
file_version: @option.file_version,
|
|
1383
|
+
product_version: @option.product_version,
|
|
1384
|
+
resource_strings: @option.resource_strings,
|
|
1385
|
+
execution_level: @option.execution_level,
|
|
1386
|
+
application_manifest: @option.application_manifest&.to_s,
|
|
1387
|
+
}
|
|
1388
|
+
end
|
|
1389
|
+
|
|
1298
1390
|
# Builds the small RUN_IN_EXE_DIR wrapper stub that starts the deployed
|
|
1299
1391
|
# application directly from the directory the wrapper resides in.
|
|
1300
1392
|
def build_wrapper_exe(wrapper_path)
|
|
@@ -1307,7 +1399,8 @@ module Ocran
|
|
|
1307
1399
|
gui_mode: @option.windowed?,
|
|
1308
1400
|
icon_path: @option.icon_filename,
|
|
1309
1401
|
run_in_exe_dir: true,
|
|
1310
|
-
stub_path: cosmo_stub_path
|
|
1402
|
+
stub_path: cosmo_stub_path,
|
|
1403
|
+
**pe_resource_options) do |stub|
|
|
1311
1404
|
yield(stub)
|
|
1312
1405
|
end
|
|
1313
1406
|
end
|
|
@@ -1317,7 +1410,11 @@ module Ocran
|
|
|
1317
1410
|
|
|
1318
1411
|
path = Pathname(path)
|
|
1319
1412
|
say "Building directory #{path}"
|
|
1320
|
-
builder = DirBuilder.new(path,
|
|
1413
|
+
builder = DirBuilder.new(path,
|
|
1414
|
+
script_name: @option.script.basename.sub_ext("").to_s,
|
|
1415
|
+
chdir_before: @option.chdir_before?,
|
|
1416
|
+
chdir_exe_dir: @option.chdir_exe_dir?,
|
|
1417
|
+
&to_proc)
|
|
1321
1418
|
|
|
1322
1419
|
if @option.wrapper_exe?
|
|
1323
1420
|
# Same wrapper as in Inno Setup builds: a doubleclickable executable
|
|
@@ -1381,30 +1478,41 @@ module Ocran
|
|
|
1381
1478
|
end
|
|
1382
1479
|
|
|
1383
1480
|
bundle_id = @option.bundle_identifier || "com.example.#{app_name}"
|
|
1384
|
-
|
|
1481
|
+
File.write(contents_dir / "Info.plist",
|
|
1482
|
+
self.class.info_plist(app_name, bundle_id, icon: !!@option.icon_filename))
|
|
1483
|
+
|
|
1484
|
+
say "Finished building #{bundle_path} (#{builder.data_size} bytes decompressed)"
|
|
1485
|
+
end
|
|
1385
1486
|
|
|
1386
|
-
|
|
1487
|
+
XML_ESCAPES = { "&" => "&", "<" => "<", ">" => ">", '"' => """, "'" => "'" }.freeze
|
|
1488
|
+
|
|
1489
|
+
# The Info.plist of a macOS app bundle. The values are XML-escaped: an
|
|
1490
|
+
# app named "R&D" must not produce a property list macOS cannot parse.
|
|
1491
|
+
def self.info_plist(app_name, bundle_id, icon: false)
|
|
1492
|
+
name = app_name.to_s.gsub(/[&<>"']/, XML_ESCAPES)
|
|
1493
|
+
id = bundle_id.to_s.gsub(/[&<>"']/, XML_ESCAPES)
|
|
1494
|
+
icon_entry = icon ? " <key>CFBundleIconFile</key>\n <string>AppIcon</string>\n" : ""
|
|
1495
|
+
|
|
1496
|
+
<<~PLIST
|
|
1387
1497
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
1388
1498
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1389
1499
|
<plist version="1.0">
|
|
1390
1500
|
<dict>
|
|
1391
1501
|
<key>CFBundleName</key>
|
|
1392
|
-
<string>#{
|
|
1502
|
+
<string>#{name}</string>
|
|
1393
1503
|
<key>CFBundleDisplayName</key>
|
|
1394
|
-
<string>#{
|
|
1504
|
+
<string>#{name}</string>
|
|
1395
1505
|
<key>CFBundleIdentifier</key>
|
|
1396
|
-
<string>#{
|
|
1506
|
+
<string>#{id}</string>
|
|
1397
1507
|
<key>CFBundleVersion</key>
|
|
1398
1508
|
<string>1.0</string>
|
|
1399
1509
|
<key>CFBundlePackageType</key>
|
|
1400
1510
|
<string>APPL</string>
|
|
1401
1511
|
<key>CFBundleExecutable</key>
|
|
1402
|
-
<string>#{
|
|
1512
|
+
<string>#{name}</string>
|
|
1403
1513
|
#{icon_entry}</dict>
|
|
1404
1514
|
</plist>
|
|
1405
1515
|
PLIST
|
|
1406
|
-
|
|
1407
|
-
say "Finished building #{bundle_path} (#{builder.data_size} bytes decompressed)"
|
|
1408
1516
|
end
|
|
1409
1517
|
|
|
1410
1518
|
# Builds the executable by copying the cosmopolitan Ruby and injecting
|
|
@@ -1419,6 +1527,7 @@ module Ocran
|
|
|
1419
1527
|
ZipPayloadBuilder.new(output,
|
|
1420
1528
|
cosmo_ruby: @option.cosmo_ruby,
|
|
1421
1529
|
chdir_before: @option.chdir_before?,
|
|
1530
|
+
chdir_exe_dir: @option.chdir_exe_dir?,
|
|
1422
1531
|
debug_mode: @option.enable_debug_mode?,
|
|
1423
1532
|
&to_proc) => builder
|
|
1424
1533
|
|
|
@@ -1441,6 +1550,56 @@ module Ocran
|
|
|
1441
1550
|
say "Finished building #{output} (#{output.size} bytes, #{builder.data_size} bytes of application data)"
|
|
1442
1551
|
end
|
|
1443
1552
|
|
|
1553
|
+
# --static-runtime: the application appended as a ZIP archive to a statically
|
|
1554
|
+
# linked Windows Ruby (see StaticRuntimeBuilder).
|
|
1555
|
+
def build_static_runtime_exe
|
|
1556
|
+
require_relative "static_runtime_builder"
|
|
1557
|
+
|
|
1558
|
+
runtime = StaticRuntime.load(@option.static_runtime)
|
|
1559
|
+
runtime.verify!
|
|
1560
|
+
host_api = RbConfig::CONFIG["ruby_version"]
|
|
1561
|
+
# Gems, default gem specs and the host files the runtime does not
|
|
1562
|
+
# replace were resolved for the build host's Ruby; they only fit a
|
|
1563
|
+
# runtime with the same API version.
|
|
1564
|
+
unless host_api == runtime.ruby_api_version
|
|
1565
|
+
raise "The static runtime in #{runtime.dir} is Ruby #{runtime.ruby_version} (API #{runtime.ruby_api_version}), " \
|
|
1566
|
+
"but this is Ruby #{RUBY_VERSION} (API #{host_api}): package with a Ruby #{runtime.ruby_api_version.sub(/\.0\z/, "")}"
|
|
1567
|
+
end
|
|
1568
|
+
if RUBY_VERSION != runtime.ruby_version
|
|
1569
|
+
warning "Dependency detection ran under Ruby #{RUBY_VERSION}; the static runtime is Ruby #{runtime.ruby_version}"
|
|
1570
|
+
end
|
|
1571
|
+
say "Packaging for the static runtime #{runtime.executable} (Ruby #{runtime.ruby_version} #{runtime.arch})"
|
|
1572
|
+
if @option.extra_dlls.any?
|
|
1573
|
+
warning "--dll has no effect with --static-runtime: a DLL cannot be loaded from inside the executable"
|
|
1574
|
+
end
|
|
1575
|
+
|
|
1576
|
+
output = @option.output_executable
|
|
1577
|
+
StaticRuntimeBuilder.new(output,
|
|
1578
|
+
runtime: runtime,
|
|
1579
|
+
host_ruby_api_version: host_api,
|
|
1580
|
+
host_arch: RbConfig::CONFIG["arch"],
|
|
1581
|
+
chdir_before: @option.chdir_before?,
|
|
1582
|
+
chdir_exe_dir: @option.chdir_exe_dir?,
|
|
1583
|
+
windowed: @option.windowed?,
|
|
1584
|
+
debug_mode: @option.enable_debug_mode?,
|
|
1585
|
+
dlls: @option.runtime_dlls,
|
|
1586
|
+
resources: { icon_path: @option.icon_filename&.to_s, **pe_resource_options },
|
|
1587
|
+
&to_proc) => builder
|
|
1588
|
+
|
|
1589
|
+
builder.skipped.each do |reason, names|
|
|
1590
|
+
verbose "Not packed (#{reason.to_s.tr("_", " ")}): #{names.size} file(s)"
|
|
1591
|
+
names.each { |name| verbose "\t#{name}" }
|
|
1592
|
+
end
|
|
1593
|
+
unless builder.skipped[:not_in_runtime_stdlib].empty?
|
|
1594
|
+
say "#{builder.skipped[:not_in_runtime_stdlib].size} file(s) of this Ruby's standard library are not " \
|
|
1595
|
+
"in the runtime's and were left out (--verbose lists them)"
|
|
1596
|
+
end
|
|
1597
|
+
say "Finished building #{output} (#{output.size} bytes, #{builder.data_size} bytes of application data)"
|
|
1598
|
+
unless builder.shipped_dlls.empty?
|
|
1599
|
+
say "Ship these DLLs next to #{output.basename}: #{builder.shipped_dlls.map { |d| d.basename.to_s }.join(", ")}"
|
|
1600
|
+
end
|
|
1601
|
+
end
|
|
1602
|
+
|
|
1444
1603
|
def build_stab_exe
|
|
1445
1604
|
require_relative "stub_builder"
|
|
1446
1605
|
|
|
@@ -1457,6 +1616,7 @@ module Ocran
|
|
|
1457
1616
|
gui_mode: @option.windowed?,
|
|
1458
1617
|
icon_path: @option.icon_filename,
|
|
1459
1618
|
stub_path: cosmo_stub_path,
|
|
1619
|
+
**pe_resource_options,
|
|
1460
1620
|
&to_proc) => builder
|
|
1461
1621
|
say "Finished building #{@option.output_executable} (#{@option.output_executable.size} bytes)"
|
|
1462
1622
|
say "After decompression, the data will expand to #{builder.data_size} bytes."
|