ocran 1.4.0-x86_64-linux
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 +7 -0
- data/CHANGELOG.txt +306 -0
- data/LICENSE.txt +23 -0
- data/README.md +549 -0
- data/exe/ocran +5 -0
- data/lib/ocran/build_constants.rb +16 -0
- data/lib/ocran/build_facade.rb +17 -0
- data/lib/ocran/build_helper.rb +110 -0
- data/lib/ocran/command_output.rb +22 -0
- data/lib/ocran/dir_builder.rb +162 -0
- data/lib/ocran/direction.rb +623 -0
- data/lib/ocran/empty_source +0 -0
- data/lib/ocran/file_path_set.rb +69 -0
- data/lib/ocran/gem_spec_queryable.rb +172 -0
- data/lib/ocran/host_config_helper.rb +57 -0
- data/lib/ocran/inno_setup_script_builder.rb +111 -0
- data/lib/ocran/launcher_batch_builder.rb +85 -0
- data/lib/ocran/library_detector.rb +61 -0
- data/lib/ocran/library_detector_posix.rb +55 -0
- data/lib/ocran/option.rb +323 -0
- data/lib/ocran/refine_pathname.rb +104 -0
- data/lib/ocran/runner.rb +115 -0
- data/lib/ocran/runtime_environment.rb +46 -0
- data/lib/ocran/stub_builder.rb +298 -0
- data/lib/ocran/version.rb +5 -0
- data/lib/ocran/windows_command_escaping.rb +15 -0
- data/lib/ocran.rb +7 -0
- data/share/ocran/lzma.exe +0 -0
- data/share/ocran/stub +0 -0
- metadata +109 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "rbconfig"
|
|
3
|
+
require "pathname"
|
|
4
|
+
require_relative "refine_pathname"
|
|
5
|
+
require_relative "host_config_helper"
|
|
6
|
+
require_relative "command_output"
|
|
7
|
+
require_relative "build_constants"
|
|
8
|
+
|
|
9
|
+
module Ocran
|
|
10
|
+
class Direction
|
|
11
|
+
using RefinePathname
|
|
12
|
+
|
|
13
|
+
# Match the load path against standard library, site_ruby, and vendor_ruby paths
|
|
14
|
+
# This regular expression matches:
|
|
15
|
+
# - /ruby/3.0.0/
|
|
16
|
+
# - /ruby/site_ruby/3.0.0/
|
|
17
|
+
# - /ruby/vendor_ruby/3.0.0/
|
|
18
|
+
RUBY_LIBRARY_PATH_REGEX = %r{/(ruby/(?:site_ruby/|vendor_ruby/)?\d+\.\d+\.\d+)/?$}i
|
|
19
|
+
|
|
20
|
+
include BuildConstants, CommandOutput, HostConfigHelper
|
|
21
|
+
|
|
22
|
+
attr_reader :ruby_executable, :rubyopt
|
|
23
|
+
|
|
24
|
+
def initialize(post_env, pre_env, option)
|
|
25
|
+
@post_env, @pre_env, @option = post_env, pre_env, option
|
|
26
|
+
@ruby_executable = @option.windowed? ? rubyw_exe : ruby_exe
|
|
27
|
+
|
|
28
|
+
# Initializes @rubyopt with the user-intended RUBYOPT environment variable.
|
|
29
|
+
# This ensures that RUBYOPT matches the user's initial settings before any
|
|
30
|
+
# modifications that may occur during script execution.
|
|
31
|
+
@rubyopt = @option.rubyopt || pre_env.env["RUBYOPT"] || ""
|
|
32
|
+
|
|
33
|
+
# Remove any absolute path to bundler/setup from RUBYOPT.
|
|
34
|
+
# When building under `bundle exec`, RUBYOPT contains `-r/absolute/path/bundler/setup`.
|
|
35
|
+
# That path doesn't exist inside the packed executable's environment, causing Ruby to
|
|
36
|
+
# print "RubyGems were not loaded" / "did_you_mean was not loaded" warnings on startup.
|
|
37
|
+
# We strip the flag regardless of install prefix because the gem may live in a user gem
|
|
38
|
+
# directory that doesn't share a prefix with RbConfig::TOPDIR (e.g. on CI runners).
|
|
39
|
+
@rubyopt = @rubyopt.gsub(/-r\S*\/bundler\/setup/, "").strip
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Resolves the common root directory prefix from an array of absolute paths.
|
|
43
|
+
# This method iterates over each file path, checking if they have a subpath
|
|
44
|
+
# that matches a given execution prefix.
|
|
45
|
+
def resolve_root_prefix(files)
|
|
46
|
+
files.inject(files.first.dirname) do |current_root, file|
|
|
47
|
+
next current_root if file.subpath?(exec_prefix)
|
|
48
|
+
|
|
49
|
+
current_root.ascend.find do |candidate_root|
|
|
50
|
+
path_from_root = file.relative_path_from(candidate_root)
|
|
51
|
+
rescue ArgumentError
|
|
52
|
+
raise "No common directory contains all specified files"
|
|
53
|
+
else
|
|
54
|
+
path_from_root.each_filename.first != ".."
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# For RubyInstaller environments supporting Ruby 2.4 and above,
|
|
60
|
+
# this method checks for the existence of a required manifest file
|
|
61
|
+
def ruby_builtin_manifest
|
|
62
|
+
manifest_path = exec_prefix / "bin/ruby_builtin_dlls/ruby_builtin_dlls.manifest"
|
|
63
|
+
manifest_path.exist? ? manifest_path : nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def detect_dlls
|
|
67
|
+
if Gem.win_platform?
|
|
68
|
+
require_relative "library_detector"
|
|
69
|
+
else
|
|
70
|
+
require_relative "library_detector_posix"
|
|
71
|
+
end
|
|
72
|
+
LibraryDetector.loaded_dlls.map { |path| Pathname.new(path).cleanpath }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def find_gemspecs(features)
|
|
76
|
+
require_relative "gem_spec_queryable"
|
|
77
|
+
|
|
78
|
+
specs = []
|
|
79
|
+
# If a Bundler Gemfile was provided, add all gems it specifies
|
|
80
|
+
if @option.gemfile
|
|
81
|
+
say "Scanning Gemfile"
|
|
82
|
+
specs += GemSpecQueryable.scanning_gemfile(@option.gemfile).each do |spec|
|
|
83
|
+
verbose "From Gemfile, adding gem #{spec.full_name}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
if defined?(Gem)
|
|
87
|
+
specs += Gem.loaded_specs.values
|
|
88
|
+
# Now, we also detect gems that are not included in Gem.loaded_specs.
|
|
89
|
+
# Therefore, we look for any loaded file from a gem path.
|
|
90
|
+
specs += GemSpecQueryable.detect_gems_from(features, verbose: @option.verbose?)
|
|
91
|
+
end
|
|
92
|
+
# Prioritize the spec detected from Gemfile.
|
|
93
|
+
specs.uniq!(&:name)
|
|
94
|
+
specs
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def normalized_features
|
|
98
|
+
features = @post_env.loaded_features.map { |feature| Pathname(feature) }
|
|
99
|
+
|
|
100
|
+
# Since https://github.com/rubygems/rubygems/commit/cad4cf16cf8fcc637d9da643ef97cf0be2ed63cb
|
|
101
|
+
# rubygems/core_ext/kernel_require.rb is loaded via IO.read+eval rather than require,
|
|
102
|
+
# so it never appears in $LOADED_FEATURES and must be added manually.
|
|
103
|
+
# Use RbConfig::CONFIG["rubylibdir"] directly so the path is always correct,
|
|
104
|
+
# regardless of Ruby version or platform path separator conventions.
|
|
105
|
+
kernel_require_rel = "rubygems/core_ext/kernel_require.rb"
|
|
106
|
+
unless features.any? { |f| f.to_posix.end_with?(kernel_require_rel) }
|
|
107
|
+
kernel_require_path = Pathname(RbConfig::CONFIG["rubylibdir"]) / kernel_require_rel
|
|
108
|
+
features.push(kernel_require_path) if kernel_require_path.exist?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Convert all relative paths to absolute paths before building.
|
|
112
|
+
# NOTE: In the future, different strategies may be needed before and after script execution.
|
|
113
|
+
features.filter_map do |feature|
|
|
114
|
+
if feature.absolute?
|
|
115
|
+
feature
|
|
116
|
+
elsif (load_path = @post_env.find_load_path(feature))
|
|
117
|
+
feature.expand_path(@post_env.expand_path(load_path))
|
|
118
|
+
else
|
|
119
|
+
# This message occurs when paths for core library files (e.g., enumerator.so,
|
|
120
|
+
# rational.so, complex.so, fiber.so, thread.rb, ruby2_keywords.rb) are not
|
|
121
|
+
# found. These are integral to Ruby's standard libraries or extensions and
|
|
122
|
+
# may not be located via normal load path searches, especially in RubyInstaller
|
|
123
|
+
# environments.
|
|
124
|
+
verbose "Load path not found for #{feature}, skip this feature"
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def construct(builder)
|
|
131
|
+
# Store the currently loaded files
|
|
132
|
+
features = normalized_features
|
|
133
|
+
|
|
134
|
+
say "Building #{@option.output_executable}"
|
|
135
|
+
require_relative "build_helper"
|
|
136
|
+
builder.extend(BuildHelper)
|
|
137
|
+
|
|
138
|
+
# Add the ruby executable and DLL
|
|
139
|
+
say "Adding ruby executable #{ruby_executable}"
|
|
140
|
+
builder.copy_to_bin(bindir / ruby_executable, ruby_executable)
|
|
141
|
+
if libruby_so
|
|
142
|
+
# On POSIX systems, libruby.so is in libdir; on Windows, it's in bindir
|
|
143
|
+
libruby_src = Gem.win_platform? ? bindir / libruby_so : libdir / libruby_so
|
|
144
|
+
builder.copy_to_bin(libruby_src, libruby_so)
|
|
145
|
+
|
|
146
|
+
# On POSIX systems, create symlinks (aliases) for libruby.so
|
|
147
|
+
unless Gem.win_platform?
|
|
148
|
+
libruby_aliases.each do |libruby_alias|
|
|
149
|
+
builder.symlink_in_bin(libruby_so, libruby_alias)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# On POSIX systems, set LD_LIBRARY_PATH to find bundled shared libraries
|
|
155
|
+
unless Gem.win_platform?
|
|
156
|
+
extract_bin = File.join(EXTRACT_ROOT, BINDIR.to_s)
|
|
157
|
+
builder.export("LD_LIBRARY_PATH", extract_bin)
|
|
158
|
+
if RUBY_PLATFORM.include?("darwin")
|
|
159
|
+
builder.export("DYLD_LIBRARY_PATH", extract_bin)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Windows-only: Add detected DLLs
|
|
164
|
+
if Gem.win_platform? && @option.auto_detect_dlls?
|
|
165
|
+
detect_dlls.each do |dll|
|
|
166
|
+
next unless dll.subpath?(exec_prefix) && dll.extname?(".dll") && dll.basename != libruby_so
|
|
167
|
+
|
|
168
|
+
say "Adding detected DLL #{dll}"
|
|
169
|
+
if dll.subpath?(exec_prefix)
|
|
170
|
+
builder.duplicate_to_exec_prefix(dll)
|
|
171
|
+
else
|
|
172
|
+
builder.copy_to_bin(dll, dll.basename)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Windows-only: Add external manifest and builtin DLLs
|
|
178
|
+
if Gem.win_platform?
|
|
179
|
+
if (manifest = ruby_builtin_manifest)
|
|
180
|
+
manifest.dirname.each_child do |path|
|
|
181
|
+
next if path.directory?
|
|
182
|
+
say "Adding builtin DLL/manifest #{path}"
|
|
183
|
+
builder.duplicate_to_exec_prefix(path)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Include SxS assembly manifests for native extensions.
|
|
188
|
+
# Each .so file may have an embedded manifest referencing a companion
|
|
189
|
+
# *.so-assembly.manifest file in the same directory. Without these
|
|
190
|
+
# manifests the SxS activation context fails (error 14001) at runtime.
|
|
191
|
+
# Scan archdir and the extension dirs of all loaded gems.
|
|
192
|
+
sxs_manifest_dirs = []
|
|
193
|
+
archdir = Pathname(RbConfig::CONFIG["archdir"])
|
|
194
|
+
sxs_manifest_dirs << archdir if archdir.exist? && archdir.subpath?(exec_prefix)
|
|
195
|
+
if defined?(Gem)
|
|
196
|
+
Gem.loaded_specs.each_value do |spec|
|
|
197
|
+
next if spec.extensions.empty?
|
|
198
|
+
ext_dir = Pathname(spec.extension_dir)
|
|
199
|
+
sxs_manifest_dirs << ext_dir if ext_dir.exist? && ext_dir.subpath?(exec_prefix)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
sxs_manifest_dirs.each do |dir|
|
|
203
|
+
dir.each_child do |path|
|
|
204
|
+
next unless path.extname == ".manifest"
|
|
205
|
+
say "Adding native extension assembly manifest #{path}"
|
|
206
|
+
builder.duplicate_to_exec_prefix(path)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Add extra DLLs specified on the command line
|
|
211
|
+
@option.extra_dlls.each do |dll|
|
|
212
|
+
say "Adding supplied DLL #{dll}"
|
|
213
|
+
builder.copy_to_bin(bindir / dll, dll)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Searches for features that are loaded from gems, then produces a
|
|
218
|
+
# list of files included in those gems' manifests. Also returns a
|
|
219
|
+
# list of original features that caused those gems to be included.
|
|
220
|
+
gem_files = find_gemspecs(features).flat_map do |spec|
|
|
221
|
+
spec_file = Pathname(spec.loaded_from)
|
|
222
|
+
# FIXME: From Ruby 3.2 onwards, launching Ruby with bundle exec causes
|
|
223
|
+
# Bundler's loaded_from to point to the root directory of the
|
|
224
|
+
# bundler gem, not returning the path to gemspec files. Here, we
|
|
225
|
+
# are only collecting gemspec files.
|
|
226
|
+
unless spec_file.file?
|
|
227
|
+
verbose "Gem #{spec.full_name} root folder was not found, skipping"
|
|
228
|
+
next []
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Add gemspec files
|
|
232
|
+
if spec_file.subpath?(exec_prefix)
|
|
233
|
+
builder.duplicate_to_exec_prefix(spec_file)
|
|
234
|
+
elsif (gem_path = GemSpecQueryable.find_gem_path(spec_file))
|
|
235
|
+
builder.duplicate_to_gem_home(spec_file, gem_path)
|
|
236
|
+
else
|
|
237
|
+
raise "Gem spec #{spec_file} does not exist in the Ruby installation. Don't know where to put it."
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Determine which set of files to include for this particular gem
|
|
241
|
+
include = GemSpecQueryable.gem_inclusion_set(spec.name, @option.gem_options)
|
|
242
|
+
say "Detected gem #{spec.full_name} (#{include.join(", ")})"
|
|
243
|
+
|
|
244
|
+
spec.extend(GemSpecQueryable)
|
|
245
|
+
|
|
246
|
+
verbose "\tgem_dir: #{spec.gem_dir}"
|
|
247
|
+
verbose "\tgem_dir exists: #{File.directory?(spec.gem_dir)}"
|
|
248
|
+
loaded_matches = include.include?(:loaded) ? features.select { |f| f.subpath?(spec.gem_dir) } : []
|
|
249
|
+
verbose "\t:loaded candidates in features: #{loaded_matches.size}"
|
|
250
|
+
loaded_matches.each { |f| verbose "\t loaded: #{f}" }
|
|
251
|
+
resource_count = include.include?(:files) && File.directory?(spec.gem_dir) ? spec.resource_files.size : 0
|
|
252
|
+
verbose "\t:files (resource_files) count: #{resource_count}"
|
|
253
|
+
|
|
254
|
+
actual_files = spec.find_gem_files(include, features)
|
|
255
|
+
say "\t#{actual_files.size} files, #{actual_files.sum(0, &:size)} bytes"
|
|
256
|
+
|
|
257
|
+
# Decide where to put gem files, either the system gem folder, or
|
|
258
|
+
# GEMDIR.
|
|
259
|
+
actual_files.each do |gemfile|
|
|
260
|
+
if gemfile.subpath?(exec_prefix)
|
|
261
|
+
builder.duplicate_to_exec_prefix(gemfile)
|
|
262
|
+
elsif (gem_path = GemSpecQueryable.find_gem_path(gemfile))
|
|
263
|
+
builder.duplicate_to_gem_home(gemfile, gem_path)
|
|
264
|
+
else
|
|
265
|
+
raise "Don't know where to put gemfile #{gemfile}"
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
actual_files
|
|
270
|
+
end
|
|
271
|
+
gem_files.uniq!
|
|
272
|
+
|
|
273
|
+
features -= gem_files
|
|
274
|
+
|
|
275
|
+
# If requested, add all ruby standard libraries
|
|
276
|
+
if @option.add_all_core?
|
|
277
|
+
say "Will include all ruby core libraries"
|
|
278
|
+
all_core_dir.each do |path|
|
|
279
|
+
# Match the load path against standard library, site_ruby, and vendor_ruby paths
|
|
280
|
+
unless (subdir = path.to_posix.match(RUBY_LIBRARY_PATH_REGEX)&.[](1))
|
|
281
|
+
raise "Unexpected library path format (does not match core dirs): #{path}"
|
|
282
|
+
end
|
|
283
|
+
path.find.each do |src|
|
|
284
|
+
next if src.directory?
|
|
285
|
+
a = Pathname(subdir) / src.relative_path_from(path)
|
|
286
|
+
builder.copy_to_lib(src, Pathname(subdir) / src.relative_path_from(path))
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Include encoding support files
|
|
292
|
+
if @option.add_all_encoding?
|
|
293
|
+
@post_env.load_path.each do |load_path|
|
|
294
|
+
load_path = Pathname(@post_env.expand_path(load_path))
|
|
295
|
+
next unless load_path.subpath?(exec_prefix)
|
|
296
|
+
|
|
297
|
+
enc_dir = load_path / "enc"
|
|
298
|
+
next unless enc_dir.directory?
|
|
299
|
+
|
|
300
|
+
enc_files = enc_dir.find.select { |path| path.file? && path.extname?(".so") }
|
|
301
|
+
say "Including #{enc_files.size} encoding support files (#{enc_files.sum(0, &:size)} bytes, use --no-enc to exclude)"
|
|
302
|
+
enc_files.each do |path|
|
|
303
|
+
builder.duplicate_to_exec_prefix(path)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
else
|
|
307
|
+
say "Not including encoding support files"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Windows-only: Workaround for RubyInstaller MSYS folder detection
|
|
311
|
+
if Gem.win_platform?
|
|
312
|
+
# RubyInstaller cannot find the msys folder if ../msys64/usr/bin/msys-2.0.dll is not present
|
|
313
|
+
# (since RubyInstaller-2.4.1 rubyinstaller 2 issue 23)
|
|
314
|
+
builder.touch('msys64/usr/bin/msys-2.0.dll')
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Find the source root and adjust paths
|
|
318
|
+
source_files = @option.source_files.dup
|
|
319
|
+
src_prefix = resolve_root_prefix(source_files)
|
|
320
|
+
|
|
321
|
+
# Find features and decide where to put them in the temporary
|
|
322
|
+
# directory layout.
|
|
323
|
+
src_load_path = []
|
|
324
|
+
# Add loaded libraries (features, gems)
|
|
325
|
+
say "Adding library files"
|
|
326
|
+
added_load_paths = (@post_env.load_path - @pre_env.load_path).map { |load_path| Pathname(@post_env.expand_path(load_path)) }
|
|
327
|
+
pre_working_directory = Pathname(@pre_env.pwd)
|
|
328
|
+
working_directory = Pathname(@post_env.pwd)
|
|
329
|
+
features.each do |feature|
|
|
330
|
+
load_path = @post_env.find_load_path(feature)
|
|
331
|
+
if load_path.nil?
|
|
332
|
+
verbose "\tlibfile: #{feature} -> src (no load path)"
|
|
333
|
+
source_files << feature
|
|
334
|
+
next
|
|
335
|
+
end
|
|
336
|
+
abs_load_path = Pathname(@post_env.expand_path(load_path))
|
|
337
|
+
if abs_load_path == pre_working_directory
|
|
338
|
+
verbose "\tlibfile: #{feature} -> src (pre-working-dir load path)"
|
|
339
|
+
source_files << feature
|
|
340
|
+
elsif feature.subpath?(exec_prefix)
|
|
341
|
+
# Features found in the Ruby installation are put in the
|
|
342
|
+
# temporary Ruby installation.
|
|
343
|
+
verbose "\tlibfile: #{feature} -> exec_prefix"
|
|
344
|
+
builder.duplicate_to_exec_prefix(feature)
|
|
345
|
+
elsif (gem_path = GemSpecQueryable.find_gem_path(feature))
|
|
346
|
+
# Features found in any other Gem path (e.g. ~/.gems) is put
|
|
347
|
+
# in a special 'gems' folder.
|
|
348
|
+
verbose "\tlibfile: #{feature} -> gem_home"
|
|
349
|
+
builder.duplicate_to_gem_home(feature, gem_path)
|
|
350
|
+
elsif feature.subpath?(src_prefix) || abs_load_path == working_directory
|
|
351
|
+
# Any feature found inside the src_prefix automatically gets
|
|
352
|
+
# added as a source file (to go in 'src').
|
|
353
|
+
verbose "\tlibfile: #{feature} -> src (src_prefix/working_dir)"
|
|
354
|
+
source_files << feature
|
|
355
|
+
# Add the load path unless it was added by the script while
|
|
356
|
+
# running (or we assume that the script can also set it up
|
|
357
|
+
# correctly when running from the resulting executable).
|
|
358
|
+
src_load_path << abs_load_path unless added_load_paths.include?(abs_load_path)
|
|
359
|
+
elsif added_load_paths.include?(abs_load_path)
|
|
360
|
+
# Any feature that exist in a load path added by the script
|
|
361
|
+
# itself is added as a file to go into the 'src' (src_prefix
|
|
362
|
+
# will be adjusted below to point to the common parent).
|
|
363
|
+
verbose "\tlibfile: #{feature} -> src (script-added load path)"
|
|
364
|
+
source_files << feature
|
|
365
|
+
else
|
|
366
|
+
# All other feature that can not be resolved go in the the
|
|
367
|
+
# Ruby sitelibdir. This is automatically in the load path
|
|
368
|
+
# when Ruby starts on Windows.
|
|
369
|
+
# On POSIX systems the ruby binary has a compile-time prefix so the
|
|
370
|
+
# extraction dir's sitelibdir is not on the load path; put
|
|
371
|
+
# the file in src instead and add the load path to RUBYLIB.
|
|
372
|
+
if Gem.win_platform?
|
|
373
|
+
inst_sitelibdir = sitelibdir.relative_path_from(exec_prefix)
|
|
374
|
+
builder.cp(feature, inst_sitelibdir / feature.relative_path_from(abs_load_path))
|
|
375
|
+
else
|
|
376
|
+
source_files << feature
|
|
377
|
+
src_load_path << abs_load_path unless src_load_path.include?(abs_load_path)
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# Recompute the src_prefix. Files may have been added implicitly
|
|
383
|
+
# while scanning through features.
|
|
384
|
+
inst_src_prefix = resolve_root_prefix(source_files)
|
|
385
|
+
|
|
386
|
+
# Add explicitly mentioned files
|
|
387
|
+
say "Adding user-supplied source files"
|
|
388
|
+
source_files.each do |source|
|
|
389
|
+
target = builder.resolve_source_path(source, inst_src_prefix)
|
|
390
|
+
|
|
391
|
+
if source.directory?
|
|
392
|
+
builder.mkdir(target)
|
|
393
|
+
else
|
|
394
|
+
builder.cp(source, target)
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Bundle SSL certificates if OpenSSL was loaded (e.g. via net/http HTTPS)
|
|
399
|
+
if defined?(OpenSSL)
|
|
400
|
+
cert_file = Pathname(OpenSSL::X509::DEFAULT_CERT_FILE)
|
|
401
|
+
if cert_file.file? && cert_file.subpath?(exec_prefix)
|
|
402
|
+
say "Adding SSL certificate file #{cert_file}"
|
|
403
|
+
builder.duplicate_to_exec_prefix(cert_file)
|
|
404
|
+
builder.export("SSL_CERT_FILE", File.join(EXTRACT_ROOT, cert_file.relative_path_from(exec_prefix).to_posix))
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
cert_dir = Pathname(OpenSSL::X509::DEFAULT_CERT_DIR)
|
|
408
|
+
if cert_dir.directory? && cert_dir.subpath?(exec_prefix)
|
|
409
|
+
say "Adding SSL certificate directory #{cert_dir}"
|
|
410
|
+
cert_dir.find.each do |path|
|
|
411
|
+
next if path.directory?
|
|
412
|
+
builder.duplicate_to_exec_prefix(path)
|
|
413
|
+
end
|
|
414
|
+
builder.export("SSL_CERT_DIR", File.join(EXTRACT_ROOT, cert_dir.relative_path_from(exec_prefix).to_posix))
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Bundle Tcl/Tk library scripts if the Tk extension is loaded.
|
|
419
|
+
# tcl86.dll and tk86.dll are auto-detected by DLL scanning, but the
|
|
420
|
+
# Tcl/Tk script libraries (init.tcl etc.) must also be bundled so
|
|
421
|
+
# that Tcl can find them relative to the DLL at runtime.
|
|
422
|
+
if defined?(TclTkLib)
|
|
423
|
+
exec_prefix.glob("**/lib/tcl[0-9]*/init.tcl").each do |init_tcl|
|
|
424
|
+
tcl_lib_dir = init_tcl.dirname
|
|
425
|
+
next unless tcl_lib_dir.subpath?(exec_prefix)
|
|
426
|
+
say "Adding Tcl library files #{tcl_lib_dir}"
|
|
427
|
+
tcl_lib_dir.find.each do |path|
|
|
428
|
+
next if path.directory?
|
|
429
|
+
builder.duplicate_to_exec_prefix(path)
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
exec_prefix.glob("**/lib/tk[0-9]*/pkgIndex.tcl").each do |pkg_index|
|
|
434
|
+
tk_lib_dir = pkg_index.dirname
|
|
435
|
+
next unless tk_lib_dir.subpath?(exec_prefix)
|
|
436
|
+
say "Adding Tk library files #{tk_lib_dir}"
|
|
437
|
+
tk_lib_dir.find.each do |path|
|
|
438
|
+
next if path.directory?
|
|
439
|
+
builder.duplicate_to_exec_prefix(path)
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# Set environment variable
|
|
445
|
+
builder.export("RUBYOPT", rubyopt)
|
|
446
|
+
# Add the load path that are required with the correct path after
|
|
447
|
+
# src_prefix was adjusted.
|
|
448
|
+
load_path = src_load_path.map { |path| SRCDIR / path.relative_path_from(inst_src_prefix) }.uniq
|
|
449
|
+
|
|
450
|
+
# On POSIX systems, also add the packed Ruby standard library directories
|
|
451
|
+
# to RUBYLIB. The Ruby binary has a compiled-in prefix pointing to the build
|
|
452
|
+
# host, which doesn't exist on other systems (e.g., Docker with no Ruby).
|
|
453
|
+
# By adding the extract-dir equivalents of rubylibdir, sitelibdir, etc. to
|
|
454
|
+
# RUBYLIB, Ruby can find rubygems and the standard library in the packed tree.
|
|
455
|
+
unless Gem.win_platform?
|
|
456
|
+
core_lib_paths = all_core_dir
|
|
457
|
+
.select { |dir| dir.subpath?(exec_prefix) }
|
|
458
|
+
.map { |dir| dir.relative_path_from(exec_prefix) }
|
|
459
|
+
archdir = Pathname(RbConfig::CONFIG["archdir"])
|
|
460
|
+
if archdir.subpath?(exec_prefix)
|
|
461
|
+
core_lib_paths << archdir.relative_path_from(exec_prefix)
|
|
462
|
+
end
|
|
463
|
+
load_path = core_lib_paths + load_path
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
builder.set_env_path("RUBYLIB", *load_path)
|
|
467
|
+
builder.set_env_path("GEM_HOME", GEMDIR)
|
|
468
|
+
|
|
469
|
+
gem_paths = [GEMDIR]
|
|
470
|
+
# On POSIX, default gems (e.g. error_highlight) are stored under the Ruby
|
|
471
|
+
# installation's gem dir (Gem.default_dir), not in GEMDIR. Include it in
|
|
472
|
+
# GEM_PATH so RubyGems can find and activate them in the extracted tree.
|
|
473
|
+
unless Gem.win_platform?
|
|
474
|
+
default_gem_dir = Pathname(Gem.default_dir)
|
|
475
|
+
if default_gem_dir.subpath?(exec_prefix)
|
|
476
|
+
gem_paths << default_gem_dir.relative_path_from(exec_prefix)
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
builder.set_env_path("GEM_PATH", *gem_paths)
|
|
480
|
+
|
|
481
|
+
# Add the opcode to launch the script
|
|
482
|
+
installed_ruby_exe = BINDIR / ruby_executable
|
|
483
|
+
target_script = builder.resolve_source_path(@option.script, inst_src_prefix)
|
|
484
|
+
builder.exec(installed_ruby_exe, target_script, *@option.argv)
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def to_proc
|
|
488
|
+
method(:construct).to_proc
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def build_inno_setup_installer
|
|
492
|
+
require_relative "inno_setup_script_builder"
|
|
493
|
+
iss_builder = InnoSetupScriptBuilder.new(@option.inno_setup_script)
|
|
494
|
+
|
|
495
|
+
require_relative "launcher_batch_builder"
|
|
496
|
+
launcher_builder = LauncherBatchBuilder.new(
|
|
497
|
+
chdir_before: @option.chdir_before?,
|
|
498
|
+
title: @option.output_executable.basename.sub_ext("")
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
require_relative "build_facade"
|
|
502
|
+
builder = BuildFacade.new(iss_builder, launcher_builder)
|
|
503
|
+
|
|
504
|
+
if @option.icon_filename
|
|
505
|
+
builder.cp(@option.icon_filename, File.basename(@option.icon_filename))
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
construct(builder)
|
|
509
|
+
|
|
510
|
+
say "Build launcher batch file"
|
|
511
|
+
launcher_path = launcher_builder.build
|
|
512
|
+
verbose File.read(launcher_path)
|
|
513
|
+
builder.cp(launcher_path, "launcher.bat")
|
|
514
|
+
|
|
515
|
+
say "Build inno setup script file"
|
|
516
|
+
iss_path = iss_builder.build
|
|
517
|
+
verbose File.read(iss_path)
|
|
518
|
+
|
|
519
|
+
say "Running Inno Setup Command-Line compiler (ISCC)"
|
|
520
|
+
iss_builder.compile(verbose: @option.verbose?)
|
|
521
|
+
|
|
522
|
+
say "Finished building installer file"
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def build_output_dir(path)
|
|
526
|
+
require_relative "dir_builder"
|
|
527
|
+
|
|
528
|
+
path = Pathname(path)
|
|
529
|
+
say "Building directory #{path}"
|
|
530
|
+
DirBuilder.new(path, &to_proc)
|
|
531
|
+
say "Finished building directory #{path}"
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def build_zip(path)
|
|
535
|
+
require_relative "dir_builder"
|
|
536
|
+
require "tmpdir"
|
|
537
|
+
|
|
538
|
+
path = Pathname(path)
|
|
539
|
+
say "Building zip #{path}"
|
|
540
|
+
Dir.mktmpdir("ocran") do |tmpdir|
|
|
541
|
+
build_output_dir(tmpdir)
|
|
542
|
+
DirBuilder.create_zip(path, tmpdir)
|
|
543
|
+
end
|
|
544
|
+
say "Finished building #{path} (#{File.size(path)} bytes)"
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def build_macosx_bundle(bundle_path)
|
|
548
|
+
require_relative "stub_builder"
|
|
549
|
+
require "fileutils"
|
|
550
|
+
|
|
551
|
+
bundle_path = Pathname(bundle_path)
|
|
552
|
+
app_name = bundle_path.basename.sub_ext("").to_s
|
|
553
|
+
contents_dir = bundle_path / "Contents"
|
|
554
|
+
macos_dir = contents_dir / "MacOS"
|
|
555
|
+
resources_dir = contents_dir / "Resources"
|
|
556
|
+
|
|
557
|
+
FileUtils.mkdir_p(macos_dir.to_s)
|
|
558
|
+
|
|
559
|
+
executable_path = macos_dir / app_name
|
|
560
|
+
say "Building app bundle #{bundle_path}"
|
|
561
|
+
|
|
562
|
+
StubBuilder.new(executable_path,
|
|
563
|
+
chdir_before: @option.chdir_before?,
|
|
564
|
+
debug_extract: @option.enable_debug_extract?,
|
|
565
|
+
debug_mode: @option.enable_debug_mode?,
|
|
566
|
+
enable_compression: @option.enable_compression?,
|
|
567
|
+
gui_mode: false,
|
|
568
|
+
icon_path: nil,
|
|
569
|
+
&to_proc) => builder
|
|
570
|
+
|
|
571
|
+
if @option.icon_filename
|
|
572
|
+
FileUtils.mkdir_p(resources_dir.to_s)
|
|
573
|
+
icon_dest = resources_dir / "AppIcon#{@option.icon_filename.extname}"
|
|
574
|
+
FileUtils.cp(@option.icon_filename.to_s, icon_dest.to_s)
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
bundle_id = @option.bundle_identifier || "com.example.#{app_name}"
|
|
578
|
+
icon_entry = @option.icon_filename ? " <key>CFBundleIconFile</key>\n <string>AppIcon</string>\n" : ""
|
|
579
|
+
|
|
580
|
+
File.write(contents_dir / "Info.plist", <<~PLIST)
|
|
581
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
582
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
583
|
+
<plist version="1.0">
|
|
584
|
+
<dict>
|
|
585
|
+
<key>CFBundleName</key>
|
|
586
|
+
<string>#{app_name}</string>
|
|
587
|
+
<key>CFBundleDisplayName</key>
|
|
588
|
+
<string>#{app_name}</string>
|
|
589
|
+
<key>CFBundleIdentifier</key>
|
|
590
|
+
<string>#{bundle_id}</string>
|
|
591
|
+
<key>CFBundleVersion</key>
|
|
592
|
+
<string>1.0</string>
|
|
593
|
+
<key>CFBundlePackageType</key>
|
|
594
|
+
<string>APPL</string>
|
|
595
|
+
<key>CFBundleExecutable</key>
|
|
596
|
+
<string>#{app_name}</string>
|
|
597
|
+
#{icon_entry}</dict>
|
|
598
|
+
</plist>
|
|
599
|
+
PLIST
|
|
600
|
+
|
|
601
|
+
say "Finished building #{bundle_path} (#{builder.data_size} bytes decompressed)"
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
def build_stab_exe
|
|
605
|
+
require_relative "stub_builder"
|
|
606
|
+
|
|
607
|
+
if @option.enable_debug_mode?
|
|
608
|
+
say "Enabling debug mode in executable"
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
StubBuilder.new(@option.output_executable,
|
|
612
|
+
chdir_before: @option.chdir_before?,
|
|
613
|
+
debug_extract: @option.enable_debug_extract?,
|
|
614
|
+
debug_mode: @option.enable_debug_mode?,
|
|
615
|
+
enable_compression: @option.enable_compression?,
|
|
616
|
+
gui_mode: @option.windowed?,
|
|
617
|
+
icon_path: @option.icon_filename,
|
|
618
|
+
&to_proc) => builder
|
|
619
|
+
say "Finished building #{@option.output_executable} (#{@option.output_executable.size} bytes)"
|
|
620
|
+
say "After decompression, the data will expand to #{builder.data_size} bytes."
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "pathname"
|
|
3
|
+
require_relative "refine_pathname"
|
|
4
|
+
|
|
5
|
+
module Ocran
|
|
6
|
+
class FilePathSet
|
|
7
|
+
using RefinePathname
|
|
8
|
+
include Enumerable
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@set = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def add(source, target)
|
|
15
|
+
add?(source, target)
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Adds a source and target path pair to the set and validates the paths before adding.
|
|
20
|
+
# This method performs various checks to ensure the source path is an absolute path
|
|
21
|
+
# and the target path is a relative path that does not include '.' or '..'.
|
|
22
|
+
# If a conflict is detected (i.e., different source for the same target),
|
|
23
|
+
# it raises an exception.
|
|
24
|
+
#
|
|
25
|
+
# @param [String, Pathname] source - The source file path; must be an absolute path.
|
|
26
|
+
# @param [String, Pathname] target - The target file path; must be a relative path.
|
|
27
|
+
# @return [self, nil] Returns self if the path pair is added successfully,
|
|
28
|
+
# returns nil if the same source and target pair is already present.
|
|
29
|
+
# @raise [ArgumentError] If the source is not an absolute path, if the target is not a relative path,
|
|
30
|
+
# or if the target includes '.' or '..'.
|
|
31
|
+
# @raise [RuntimeError] If a conflicting source is found for the same target.
|
|
32
|
+
def add?(source, target)
|
|
33
|
+
source = Pathname.new(source) unless source.is_a?(Pathname)
|
|
34
|
+
source = source.cleanpath
|
|
35
|
+
unless source.absolute?
|
|
36
|
+
raise ArgumentError, "Source path must be absolute, given: #{source}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
target = Pathname.new(target) unless target.is_a?(Pathname)
|
|
40
|
+
target = target.cleanpath
|
|
41
|
+
unless target.relative?
|
|
42
|
+
raise ArgumentError, "Target path must be relative, given: #{target}"
|
|
43
|
+
end
|
|
44
|
+
if %w(. ..).include?(target.each_filename.first)
|
|
45
|
+
raise ArgumentError, "Relative paths such as '.' or '..' are not allowed, given: #{target}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if (path = @set[target])
|
|
49
|
+
if path.eql?(source)
|
|
50
|
+
return nil
|
|
51
|
+
else
|
|
52
|
+
raise "Conflicting sources for the same target. Target: #{target}, Existing Source: #{path}, Given Source: #{source}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@set[target] = source
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def each
|
|
61
|
+
return to_enum(__method__) unless block_given?
|
|
62
|
+
@set.each { |target, source| yield(source, target) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_a
|
|
66
|
+
each.to_a
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|