ocran 1.3.15 → 1.3.16

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+ require "rubygems"
3
+ require "pathname"
4
+ require_relative "refine_pathname"
5
+
6
+ module Ocran
7
+ module GemSpecQueryable
8
+ using RefinePathname
9
+
10
+ GEM_SCRIPT_RE = /\.rbw?$/
11
+ GEM_EXTRA_RE = %r{(
12
+ # Auxiliary files in the root of the gem
13
+ ^(\.\/)?(History|Install|Manifest|README|CHANGES|Licen[sc]e|Contributors|ChangeLog|BSD|GPL).*$ |
14
+ # Installation files in the root of the gem
15
+ ^(\.\/)?(Rakefile|setup.rb|extconf.rb)$ |
16
+ # Documentation/test directories in the root of the gem
17
+ ^(\.\/)?(doc|ext|examples|test|tests|benchmarks|spec)\/ |
18
+ # Directories anywhere
19
+ (^|\/)(\.autotest|\.svn|\.cvs|\.git)(\/|$) |
20
+ # Unlikely extensions
21
+ \.(rdoc|c|cpp|c\+\+|cxx|h|hxx|hpp|obj|o|a)$
22
+ )}xi
23
+ GEM_NON_FILE_RE = /(#{GEM_EXTRA_RE}|#{GEM_SCRIPT_RE})/
24
+
25
+ class << self
26
+ # find_gem_path method searches for the path of the gem containing the
27
+ # specified path. The 'path' argument is a file or directory path.
28
+ # It checks each gem's installation path in Gem.path to see if the
29
+ # specified path is a subpath. Returns the gem's path if found, or
30
+ # nil if not found.
31
+ def find_gem_path(path)
32
+ return unless defined?(Gem)
33
+
34
+ Gem.path.find { |gem_path| Pathname(path).subpath?(gem_path) }
35
+ end
36
+
37
+ # find_spec_file method searches for the path of the gemspec file of
38
+ # the gem containing the specified path. The 'path' argument is a file
39
+ # or directory path. It searches within the "gems" directory in each
40
+ # directory listed in Gem.path to check if the specified path is a
41
+ # subpath. If the gemspec file exists, it returns its path; otherwise,
42
+ # it returns nil.
43
+ def find_spec_file(path)
44
+ return unless defined?(Gem)
45
+
46
+ feature = Pathname(path)
47
+ Gem.path.each do |gem_path|
48
+ gems_dir = File.join(gem_path, "gems")
49
+ next unless feature.subpath?(gems_dir)
50
+
51
+ full_name = feature.relative_path_from(gems_dir).each_filename.first
52
+ spec_path = File.join(gem_path, "specifications", "#{full_name}.gemspec")
53
+ return spec_path if File.exist?(spec_path)
54
+ end
55
+ nil
56
+ end
57
+
58
+ # find_spec method searches and returns a Gem::Specification object
59
+ # based on the specified path. Internally, it uses find_spec_file to
60
+ # obtain the path to the gemspec file, and if that file exists, it
61
+ # calls Gem::Specification.load to load the gem's specifications.
62
+ # Returns the loaded Gem::Specification object, or nil if the gemspec
63
+ # file does not exist.
64
+ def find_spec(path)
65
+ return unless defined?(Gem)
66
+
67
+ spec_file = find_spec_file(path)
68
+ spec_file && Gem::Specification.load(spec_file)
69
+ end
70
+
71
+ def scanning_gemfile(gemfile_path)
72
+ # Ensure the necessary libraries are loaded to scan the Gemfile.
73
+ # This is particularly useful in custom-built Ruby environments or
74
+ # where certain libraries might be excluded.
75
+ %w[rubygems bundler].each do |lib|
76
+ require lib
77
+ rescue LoadError
78
+ raise "Couldn't scan Gemfile, unable to load #{lib}"
79
+ end
80
+
81
+ ENV["BUNDLE_GEMFILE"] = gemfile_path.to_s
82
+ # Bundler.load.specs includes the spec for Bundler itself
83
+ Bundler.load.specs.to_a
84
+ end
85
+
86
+ # Fall back to gem detection
87
+ def detect_gems_from(features, verbose: false)
88
+ features.inject([]) do |gems, feature|
89
+ if gems.any? { |spec| feature.subpath?(spec.gem_dir) }
90
+ # Skip if found in known Gem dir
91
+ elsif (spec = GemSpecQueryable.find_spec(feature))
92
+ gems << spec
93
+ else
94
+ puts "Failed to load gemspec for #{feature}" if verbose
95
+ end
96
+
97
+ gems
98
+ end
99
+ end
100
+
101
+ def gem_inclusion_set(spec_name, gem_options)
102
+ include = [:loaded, :files]
103
+ gem_options.each do |negate, option, list|
104
+ next unless list.nil? || list.include?(spec_name)
105
+
106
+ case option
107
+ when :minimal
108
+ include = [:loaded]
109
+ when :guess
110
+ include = [:loaded, :files]
111
+ when :all
112
+ include = [:scripts, :files]
113
+ when :full
114
+ include = [:scripts, :files, :extras]
115
+ when :spec
116
+ include = [:spec]
117
+ when :scripts, :files, :extras
118
+ if negate
119
+ include.delete(option)
120
+ else
121
+ include.push(option)
122
+ end
123
+ else
124
+ raise "Invalid Gem content detection option: #{option}"
125
+ end
126
+ end
127
+ include.uniq!
128
+ include
129
+ end
130
+ end
131
+
132
+ def gem_root = Pathname(gem_dir)
133
+
134
+ # Find the selected files
135
+ def gem_root_files = gem_root.find.select(&:file?)
136
+
137
+ def script_files
138
+ gem_root_files.select { |path| path.extname =~ GEM_SCRIPT_RE }
139
+ end
140
+
141
+ def extra_files
142
+ gem_root_files.select { |path| path.relative_path_from(gem_root).to_posix =~ GEM_EXTRA_RE }
143
+ end
144
+
145
+ def resource_files
146
+ files = gem_root_files.select { |path| path.relative_path_from(gem_root).to_posix !~ GEM_NON_FILE_RE }
147
+ files << Pathname(gem_build_complete_path) if File.exist?(gem_build_complete_path)
148
+ files
149
+ end
150
+
151
+ def find_gem_files(file_sets, features_from_gems)
152
+ actual_files = file_sets.flat_map do |set|
153
+ case set
154
+ when :spec
155
+ files.map { |file| Pathname(file) }
156
+ when :loaded
157
+ features_from_gems.select { |feature| feature.subpath?(gem_dir) }
158
+ when :files
159
+ resource_files
160
+ when :extras
161
+ extra_files
162
+ when :scripts
163
+ script_files
164
+ else
165
+ raise "Invalid file set: #{set}. Please specify a valid file set (:spec, :loaded, :files, :extras, :scripts)."
166
+ end
167
+ end
168
+ actual_files.uniq
169
+ actual_files
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,37 @@
1
+ require "rbconfig"
2
+ require "pathname"
3
+
4
+ module Ocran
5
+ # Variables describing the host's build environment.
6
+ module HostConfigHelper
7
+ module_function
8
+
9
+ def exec_prefix
10
+ @exec_prefix ||= Pathname.new(RbConfig::CONFIG["exec_prefix"])
11
+ end
12
+
13
+ def sitelibdir
14
+ @sitelibdir ||= Pathname.new(RbConfig::CONFIG["sitelibdir"])
15
+ end
16
+
17
+ def bindir
18
+ @bindir ||= Pathname.new(RbConfig::CONFIG["bindir"])
19
+ end
20
+
21
+ def libruby_so
22
+ @libruby_so ||= Pathname.new(RbConfig::CONFIG["LIBRUBY_SO"])
23
+ end
24
+
25
+ def exe_extname
26
+ RbConfig::CONFIG["EXEEXT"] || ".exe"
27
+ end
28
+
29
+ def rubyw_exe
30
+ @rubyw_exe ||= (RbConfig::CONFIG["rubyw_install_name"] || "rubyw") + exe_extname
31
+ end
32
+
33
+ def ruby_exe
34
+ @ruby_exe ||= (RbConfig::CONFIG["ruby_install_name"] || "ruby") + exe_extname
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+ require "tempfile"
3
+ require_relative "file_path_set"
4
+ require_relative "windows_command_escaping"
5
+
6
+ module Ocran
7
+ class InnoSetupScriptBuilder
8
+ ISCC_CMD = "ISCC"
9
+ ISCC_SUCCESS = 0
10
+ ISCC_INVALID_PARAMS = 1
11
+ ISCC_COMPILATION_FAILED = 2
12
+
13
+ extend WindowsCommandEscaping
14
+
15
+ class << self
16
+ def compile(iss_filename, quiet: false)
17
+ unless system("where #{quote_and_escape(ISCC_CMD)} >NUL 2>&1")
18
+ raise "ISCC command not found. Is the InnoSetup directory in your PATH?"
19
+ end
20
+
21
+ cmd_line = [ISCC_CMD]
22
+ cmd_line << "/Q" if quiet
23
+ cmd_line << iss_filename
24
+ system(*cmd_line)
25
+
26
+ case $?&.exitstatus
27
+ when ISCC_SUCCESS
28
+ # ISCC reported success
29
+ when ISCC_INVALID_PARAMS
30
+ raise "ISCC reports invalid command line parameters"
31
+ when ISCC_COMPILATION_FAILED
32
+ raise "ISCC reports that compilation failed"
33
+ else
34
+ raise "ISCC failed to run"
35
+ end
36
+ end
37
+ end
38
+
39
+ include WindowsCommandEscaping
40
+
41
+ def initialize(inno_setup_script)
42
+ # ISSC generates the installer files relative to the directory of the
43
+ # ISS file. Therefore, it is necessary to create Tempfiles in the
44
+ # working directory.
45
+ @build_file = Tempfile.new("", Dir.pwd)
46
+ if inno_setup_script
47
+ IO.copy_stream(inno_setup_script, @build_file)
48
+ end
49
+ @dirs = FilePathSet.new
50
+ @files = FilePathSet.new
51
+ end
52
+
53
+ def build
54
+ @build_file.tap do |f|
55
+ if @dirs.any?
56
+ f.puts
57
+ f.puts "[Dirs]"
58
+ @dirs.each { |_source, target| f.puts build_dir_item(target) }
59
+ end
60
+
61
+ if @files.any?
62
+ f.puts
63
+ f.puts "[Files]"
64
+ @files.each { |source, target| f.puts build_file_item(source, target) }
65
+ end
66
+ end.close
67
+ path
68
+ end
69
+
70
+ def path
71
+ @build_file.to_path
72
+ end
73
+
74
+ def compile(verbose: false)
75
+ InnoSetupScriptBuilder.compile(path, quiet: !verbose)
76
+ end
77
+
78
+ def mkdir(target)
79
+ @dirs.add?("/", target)
80
+ end
81
+
82
+ def cp(source, target)
83
+ unless File.exist?(source)
84
+ raise "The file does not exist (#{source})"
85
+ end
86
+
87
+ @files.add?(source, target)
88
+ end
89
+
90
+ def build_dir_item(target)
91
+ name = File.join("{app}", target)
92
+ "Name: #{quote_and_escape(name)};"
93
+ end
94
+ private :build_dir_item
95
+
96
+ def build_file_item(source, target)
97
+ dest_dir = File.join("{app}", File.dirname(target))
98
+ s = [
99
+ "Source: #{quote_and_escape(source)};",
100
+ "DestDir: #{quote_and_escape(dest_dir)};"
101
+ ]
102
+ src_name = File.basename(source)
103
+ dest_name = File.basename(target)
104
+ if src_name != dest_name
105
+ s << "DestName: #{quote_and_escape(dest_name)};"
106
+ end
107
+ s.join(" ")
108
+ end
109
+ private :build_file_item
110
+ end
111
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+ require "tempfile"
3
+ require_relative "windows_command_escaping"
4
+ require_relative "build_constants"
5
+
6
+ module Ocran
7
+ class LauncherBatchBuilder
8
+ include BuildConstants, WindowsCommandEscaping
9
+
10
+ # BATCH_FILE_DIR is a parameter expansion used in Windows batch files,
11
+ # representing the full path to the directory where the batch file resides.
12
+ # It allows for the use of pseudo-relative paths by referencing the
13
+ # batch file's own location without changing the working directory.
14
+ BATCH_FILE_DIR = "%~dp0"
15
+
16
+ # BATCH_FILE_PATH is a parameter expansion used in Windows batch files,
17
+ # representing the full path to the batch file itself, including the file name.
18
+ BATCH_FILE_PATH = "%~f0"
19
+
20
+ def initialize(chdir_before: nil, title: nil)
21
+ @build_file = Tempfile.new
22
+ @title = title
23
+ @chdir_before = chdir_before
24
+ @environments = {}
25
+ end
26
+
27
+ def build
28
+ @build_file.tap do |f|
29
+ f.puts "@echo off"
30
+ @environments.each { |name, val| f.puts build_set_command(name, val) }
31
+ f.puts build_set_command("OCRAN_EXECUTABLE", BATCH_FILE_PATH)
32
+ f.puts build_start_command(@title, @executable, @script, *@args, chdir_before: @chdir_before)
33
+ f
34
+ end.close
35
+ path
36
+ end
37
+
38
+ def path
39
+ @build_file.to_path
40
+ end
41
+
42
+ def export(name, value)
43
+ @environments[name] = value
44
+ end
45
+
46
+ def exec(executable, script, *args)
47
+ @executable, @script, @args = executable, script, args
48
+ end
49
+
50
+ def replace_inst_dir_placeholder(s)
51
+ s.to_s.gsub(/#{Regexp.escape(EXTRACT_ROOT.to_s)}[\/\\]/, BATCH_FILE_DIR)
52
+ end
53
+ private :replace_inst_dir_placeholder
54
+
55
+ def build_set_command(name, value)
56
+ "set \"#{name}=#{replace_inst_dir_placeholder(value)}\""
57
+ end
58
+ private :build_set_command
59
+
60
+ def build_start_command(title, executable, script, *args, chdir_before: nil)
61
+ cmd = ["start"]
62
+
63
+ # Title for Command Prompt window title bar
64
+ cmd << quote_and_escape(title)
65
+
66
+ # Use /d to set the startup directory for the process,
67
+ # which will be BATCH_FILE_DIR/SRCDIR. This path is where
68
+ # the script is located, establishing the working directory
69
+ # at process start.
70
+ if chdir_before
71
+ cmd << "/d #{quote_and_escape("#{BATCH_FILE_DIR}#{SRCDIR}")}"
72
+ end
73
+
74
+ cmd << quote_and_escape("#{BATCH_FILE_DIR}#{executable}")
75
+ cmd << quote_and_escape("#{BATCH_FILE_DIR}#{script}")
76
+ cmd += args.map { |arg| quote_and_escape(replace_inst_dir_placeholder(arg)) }
77
+
78
+ # Forward batch file arguments to the command with `%*`
79
+ cmd << "%*"
80
+
81
+ cmd.join(" ")
82
+ end
83
+ private :build_start_command
84
+ end
85
+ end
@@ -0,0 +1,61 @@
1
+ require "fiddle/import"
2
+ require "fiddle/types"
3
+
4
+ module Ocran
5
+ module LibraryDetector
6
+ # Windows API functions for handling files may return long paths,
7
+ # with a maximum character limit of 32,767.
8
+ # "\\?\" prefix(4 characters) + long path(32762 characters) + NULL = 32767 characters
9
+ # https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
10
+ MAX_PATH = 32767
11
+
12
+ # The byte size of the buffer given as an argument to the EnumProcessModules function.
13
+ # This buffer is used to store the handles of the loaded modules.
14
+ # If the buffer size is smaller than the number of loaded modules,
15
+ # it will automatically increase the buffer size and call the EnumProcessModules function again.
16
+ # Increasing the initial buffer size can reduce the number of iterations required.
17
+ # https://learn.microsoft.com/en-us/windows/win32/psapi/enumerating-all-modules-for-a-process
18
+ DEFAULT_HMODULE_BUFFER_SIZE = 1024
19
+
20
+ extend Fiddle::Importer
21
+ dlload "kernel32.dll", "psapi.dll"
22
+
23
+ include Fiddle::Win32Types
24
+ # https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types
25
+ typealias "HMODULE", "HINSTANCE"
26
+ typealias "LPDWORD", "PDWORD"
27
+ typealias "LPWSTR", "char*"
28
+
29
+ extern "BOOL EnumProcessModules(HANDLE, HMODULE*, DWORD, LPDWORD)"
30
+ extern "DWORD GetModuleFileNameW(HMODULE, LPWSTR, DWORD)"
31
+ extern "HANDLE GetCurrentProcess()"
32
+ extern "DWORD GetLastError()"
33
+
34
+ class << self
35
+ def loaded_dlls
36
+ dword = "L" # A DWORD is a 32-bit unsigned integer.
37
+ bytes_needed = [0].pack(dword)
38
+ bytes = DEFAULT_HMODULE_BUFFER_SIZE
39
+ process_handle = GetCurrentProcess()
40
+ handles = while true
41
+ buffer = "\x00" * bytes
42
+ if EnumProcessModules(process_handle, buffer, buffer.bytesize, bytes_needed) == 0
43
+ raise "EnumProcessModules failed with error code #{GetLastError()}"
44
+ end
45
+ bytes = bytes_needed.unpack1(dword)
46
+ if bytes <= buffer.bytesize
47
+ break buffer.unpack("J#{bytes / Fiddle::SIZEOF_VOIDP}")
48
+ end
49
+ end
50
+ str = "\x00".encode("UTF-16LE") * MAX_PATH
51
+ handles.map do |handle|
52
+ length = GetModuleFileNameW(handle, str, str.bytesize)
53
+ if length == 0
54
+ raise "GetModuleFileNameW failed with error code #{GetLastError()}"
55
+ end
56
+ str[0, length].encode("UTF-8")
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end