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.
@@ -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