aibika 1.3.12
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/Gemfile +11 -0
- data/History.txt +214 -0
- data/LICENSE.md +30 -0
- data/Manifest.txt +11 -0
- data/README.adoc +564 -0
- data/Rakefile +30 -0
- data/aibika.gemspec +50 -0
- data/bin/aibika +25 -0
- data/lib/aibika/aibika_builder.rb +191 -0
- data/lib/aibika/cli.rb +181 -0
- data/lib/aibika/host.rb +40 -0
- data/lib/aibika/library_detector.rb +88 -0
- data/lib/aibika/pathname.rb +158 -0
- data/lib/aibika/version.rb +5 -0
- data/lib/aibika.rb +675 -0
- data/samples/activerecord_sample.rb +6 -0
- data/samples/bundler_git/Gemfile +3 -0
- data/samples/bundler_git/bundler_git.rb +5 -0
- data/samples/mech.rb +8 -0
- data/samples/mime-types_sample.rb +4 -0
- data/samples/pg_sample.rb +4 -0
- data/samples/prawn_sample.rb +9 -0
- data/samples/readchar.rb +4 -0
- data/samples/sysproctable.rb +12 -0
- data/samples/tk.rb +13 -0
- data/samples/tkextlib.rb +4 -0
- data/samples/watir_sample.rb +31 -0
- data/samples/win32_api_sample.rb +5 -0
- data/samples/win32ole.rb +4 -0
- data/samples/wxruby_sample.rbw +29 -0
- data/share/aibika/lzma.exe +0 -0
- data/src/Makefile +37 -0
- data/src/edicon.c +146 -0
- data/src/lzma/LzmaDec.c +1007 -0
- data/src/lzma/LzmaDec.h +223 -0
- data/src/lzma/Types.h +208 -0
- data/src/seb.exe +0 -0
- data/src/stub.c +703 -0
- data/src/stub.rc +1 -0
- data/src/vit-ruby.ico +0 -0
- metadata +109 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aibika
|
4
|
+
# Utility class that produces the actual executable. Opcodes
|
5
|
+
# (createfile, mkdir etc) are added by invoking methods on an
|
6
|
+
# instance of AibikaBuilder.
|
7
|
+
class AibikaBuilder
|
8
|
+
Signature = [0x41, 0xb6, 0xba, 0x4e].freeze
|
9
|
+
OP_END = 0
|
10
|
+
OP_CREATE_DIRECTORY = 1
|
11
|
+
OP_CREATE_FILE = 2
|
12
|
+
OP_CREATE_PROCESS = 3
|
13
|
+
OP_DECOMPRESS_LZMA = 4
|
14
|
+
OP_SETENV = 5
|
15
|
+
OP_POST_CREATE_PROCESS = 6
|
16
|
+
OP_ENABLE_DEBUG_MODE = 7
|
17
|
+
OP_CREATE_INST_DIRECTORY = 8
|
18
|
+
|
19
|
+
def initialize(path, windowed)
|
20
|
+
@paths = {}
|
21
|
+
@files = {}
|
22
|
+
File.open(path, 'wb') do |aibikafile|
|
23
|
+
image = if windowed
|
24
|
+
Aibika.stubwimage
|
25
|
+
else
|
26
|
+
Aibika.stubimage
|
27
|
+
end
|
28
|
+
|
29
|
+
Aibika.fatal_error 'Stub image not available' unless image
|
30
|
+
aibikafile.write(image)
|
31
|
+
end
|
32
|
+
|
33
|
+
system Aibika.ediconpath, path, Aibika.icon_filename if Aibika.icon_filename
|
34
|
+
|
35
|
+
opcode_offset = File.size(path)
|
36
|
+
|
37
|
+
File.open(path, 'ab') do |aibikafile|
|
38
|
+
tmpinpath = 'tmpin'
|
39
|
+
|
40
|
+
@of = if Aibika.lzma_mode
|
41
|
+
File.open(tmpinpath, 'wb')
|
42
|
+
else
|
43
|
+
aibikafile
|
44
|
+
end
|
45
|
+
|
46
|
+
if Aibika.debug
|
47
|
+
Aibika.msg('Enabling debug mode in executable')
|
48
|
+
aibikafile.write([OP_ENABLE_DEBUG_MODE].pack('V'))
|
49
|
+
end
|
50
|
+
|
51
|
+
createinstdir Aibika.debug_extract, !Aibika.debug_extract, Aibika.chdir_first
|
52
|
+
|
53
|
+
yield(self)
|
54
|
+
|
55
|
+
@of.close if Aibika.lzma_mode
|
56
|
+
|
57
|
+
if Aibika.lzma_mode && !Aibika.inno_script
|
58
|
+
tmpoutpath = 'tmpout'
|
59
|
+
begin
|
60
|
+
data_size = File.size(tmpinpath)
|
61
|
+
Aibika.msg "Compressing #{data_size} bytes"
|
62
|
+
system(Aibika.lzmapath, 'e', tmpinpath, tmpoutpath) or raise
|
63
|
+
compressed_data_size = File.size?(tmpoutpath)
|
64
|
+
aibikafile.write([OP_DECOMPRESS_LZMA, compressed_data_size].pack('VV'))
|
65
|
+
IO.copy_stream(tmpoutpath, aibikafile)
|
66
|
+
ensure
|
67
|
+
File.unlink(@of.path) if File.exist?(@of.path)
|
68
|
+
File.unlink(tmpoutpath) if File.exist?(tmpoutpath)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
aibikafile.write([OP_END].pack('V'))
|
73
|
+
aibikafile.write([opcode_offset].pack('V')) # Pointer to start of opcodes
|
74
|
+
aibikafile.write(Signature.pack('C*'))
|
75
|
+
end
|
76
|
+
|
77
|
+
return unless Aibika.inno_script
|
78
|
+
|
79
|
+
begin
|
80
|
+
iss = "#{File.read(Aibika.inno_script)}\n\n"
|
81
|
+
|
82
|
+
iss << "[Dirs]\n"
|
83
|
+
@paths.each_key do |p|
|
84
|
+
iss << "Name: \"{app}/#{p}\"\n"
|
85
|
+
end
|
86
|
+
iss << "\n"
|
87
|
+
|
88
|
+
iss << "[Files]\n"
|
89
|
+
path_escaped = path.to_s.gsub('"', '""')
|
90
|
+
iss << "Source: \"#{path_escaped}\"; DestDir: \"{app}\"\n"
|
91
|
+
@files.each do |tgt, src|
|
92
|
+
src_escaped = src.to_s.gsub('"', '""')
|
93
|
+
target_dir_escaped = Pathname.new(tgt).dirname.to_s.gsub('"', '""')
|
94
|
+
unless src_escaped =~ IGNORE_MODULE_NAMES
|
95
|
+
iss << "Source: \"#{src_escaped}\"; DestDir: \"{app}/#{target_dir_escaped}\"\n"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
iss << "\n"
|
99
|
+
|
100
|
+
Aibika.verbose_msg "### INNOSETUP SCRIPT ###\n\n#{iss}\n\n"
|
101
|
+
|
102
|
+
f = File.open('aibikatemp.iss', 'w')
|
103
|
+
f.write(iss)
|
104
|
+
f.close
|
105
|
+
|
106
|
+
iscc_cmd = ['iscc']
|
107
|
+
iscc_cmd << '/Q' unless Aibika.verbose
|
108
|
+
iscc_cmd << 'aibikatemp.iss'
|
109
|
+
Aibika.msg 'Running InnoSetup compiler ISCC'
|
110
|
+
result = system(*iscc_cmd)
|
111
|
+
unless result
|
112
|
+
case $CHILD_STATUS
|
113
|
+
when 0 then raise 'ISCC reported success, but system reported error?'
|
114
|
+
when 1 then raise 'ISCC reports invalid command line parameters'
|
115
|
+
when 2 then raise 'ISCC reports that compilation failed'
|
116
|
+
else raise 'ISCC failed to run. Is the InnoSetup directory in your PATH?'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
rescue Exception => e
|
120
|
+
Aibika.fatal_error("InnoSetup installer creation failed: #{e.message}")
|
121
|
+
ensure
|
122
|
+
File.unlink('aibikatemp.iss') if File.exist?('aibikatemp.iss')
|
123
|
+
File.unlink(path) if File.exist?(path)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def mkdir(path)
|
128
|
+
return if @paths[path.path.downcase]
|
129
|
+
|
130
|
+
@paths[path.path.downcase] = true
|
131
|
+
Aibika.verbose_msg "m #{showtempdir path}"
|
132
|
+
return if Aibika.inno_script # The directory will be created by InnoSetup with a [Dirs] statement
|
133
|
+
|
134
|
+
@of << [OP_CREATE_DIRECTORY, path.to_native].pack('VZ*')
|
135
|
+
end
|
136
|
+
|
137
|
+
def ensuremkdir(tgt)
|
138
|
+
tgt = Aibika.Pathname(tgt)
|
139
|
+
return if tgt.path == '.'
|
140
|
+
|
141
|
+
return if @paths[tgt.to_posix.downcase]
|
142
|
+
|
143
|
+
ensuremkdir(tgt.dirname)
|
144
|
+
mkdir(tgt)
|
145
|
+
end
|
146
|
+
|
147
|
+
def createinstdir(next_to_exe = false, delete_after = false, chdir_before = false)
|
148
|
+
return if Aibika.inno_script # Creation of installation directory will be handled by InnoSetup
|
149
|
+
|
150
|
+
@of << [OP_CREATE_INST_DIRECTORY, next_to_exe ? 1 : 0, delete_after ? 1 : 0, chdir_before ? 1 : 0].pack('VVVV')
|
151
|
+
end
|
152
|
+
|
153
|
+
def createfile(src, tgt)
|
154
|
+
return if @files[tgt]
|
155
|
+
|
156
|
+
@files[tgt] = src
|
157
|
+
src = Aibika.Pathname(src)
|
158
|
+
tgt = Aibika.Pathname(tgt)
|
159
|
+
ensuremkdir(tgt.dirname)
|
160
|
+
str = File.open(src, 'rb', &:read)
|
161
|
+
Aibika.verbose_msg "a #{showtempdir tgt}"
|
162
|
+
return if Aibika.inno_script # InnoSetup will install the file with a [Files] statement
|
163
|
+
|
164
|
+
@of << [OP_CREATE_FILE, tgt.to_native, str.size].pack('VZ*V')
|
165
|
+
@of << str
|
166
|
+
end
|
167
|
+
|
168
|
+
def createprocess(image, cmdline)
|
169
|
+
Aibika.verbose_msg "l #{showtempdir image} #{showtempdir cmdline}"
|
170
|
+
@of << [OP_CREATE_PROCESS, image.to_native, cmdline].pack('VZ*Z*')
|
171
|
+
end
|
172
|
+
|
173
|
+
def postcreateprocess(image, cmdline)
|
174
|
+
Aibika.verbose_msg "p #{showtempdir image} #{showtempdir cmdline}"
|
175
|
+
@of << [OP_POST_CREATE_PROCESS, image.to_native, cmdline].pack('VZ*Z*')
|
176
|
+
end
|
177
|
+
|
178
|
+
def setenv(name, value)
|
179
|
+
Aibika.verbose_msg "e #{name} #{showtempdir value}"
|
180
|
+
@of << [OP_SETENV, name, value].pack('VZ*Z*')
|
181
|
+
end
|
182
|
+
|
183
|
+
def close
|
184
|
+
@of.close
|
185
|
+
end
|
186
|
+
|
187
|
+
def showtempdir(tdir)
|
188
|
+
tdir.to_s.gsub(TEMPDIR_ROOT, '<tempdir>')
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
data/lib/aibika/cli.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Aibika front-end
|
4
|
+
module Aibika
|
5
|
+
def self.parseargs(argv)
|
6
|
+
usage = <<~USG
|
7
|
+
aibika [options] script.rb
|
8
|
+
|
9
|
+
Aibika options:
|
10
|
+
|
11
|
+
--help Display this information.
|
12
|
+
--quiet Suppress output while building executable.
|
13
|
+
--verbose Show extra output while building executable.
|
14
|
+
--version Display version number and exit.
|
15
|
+
|
16
|
+
Packaging options:
|
17
|
+
|
18
|
+
--dll dllname Include additional DLLs from the Ruby bindir.
|
19
|
+
--add-all-core Add all core ruby libraries to the executable.
|
20
|
+
--gemfile <file> Add all gems and dependencies listed in a Bundler Gemfile.
|
21
|
+
--no-enc Exclude encoding support files
|
22
|
+
--allow-self Include self (aibika gem) if detected or specified
|
23
|
+
This option is required only if aibika gem is deployed as a part
|
24
|
+
of broader bundled your solution
|
25
|
+
|
26
|
+
Gem content detection modes:
|
27
|
+
|
28
|
+
--gem-minimal[=gem1,..] Include only loaded scripts
|
29
|
+
--gem-guess=[gem1,...] Include loaded scripts & best guess (DEFAULT)
|
30
|
+
--gem-all[=gem1,..] Include all scripts & files
|
31
|
+
--gem-full[=gem1,..] Include EVERYTHING
|
32
|
+
--gem-spec[=gem1,..] Include files in gemspec (Does not work with Rubygems 1.7+)
|
33
|
+
|
34
|
+
minimal: loaded scripts
|
35
|
+
guess: loaded scripts and other files
|
36
|
+
all: loaded scripts, other scripts, other files (except extras)
|
37
|
+
full: Everything found in the gem directory
|
38
|
+
|
39
|
+
--[no-]gem-scripts[=..] Other script files than those loaded
|
40
|
+
--[no-]gem-files[=..] Other files (e.g. data files)
|
41
|
+
--[no-]gem-extras[=..] Extra files (README, etc.)
|
42
|
+
|
43
|
+
scripts: .rb/.rbw files
|
44
|
+
extras: C/C++ sources, object files, test, spec, README
|
45
|
+
files: all other files
|
46
|
+
|
47
|
+
Auto-detection options:
|
48
|
+
|
49
|
+
--no-dep-run Don't run script.rb to check for dependencies.
|
50
|
+
--no-autoload Don't load/include script.rb's autoloads.
|
51
|
+
--no-autodll Disable detection of runtime DLL dependencies.
|
52
|
+
|
53
|
+
Output options:
|
54
|
+
|
55
|
+
--output <file> Name the exe to generate. Defaults to ./<scriptname>.exe.
|
56
|
+
--no-lzma Disable LZMA compression of the executable.
|
57
|
+
--innosetup <file> Use given Inno Setup script (.iss) to create an installer.
|
58
|
+
|
59
|
+
Executable options:
|
60
|
+
|
61
|
+
--windows Force Windows application (rubyw.exe)
|
62
|
+
--console Force console application (ruby.exe)
|
63
|
+
--chdir-first When exe starts, change working directory to app dir.
|
64
|
+
--icon <ico> Replace icon with a custom one.
|
65
|
+
--debug Executable will be verbose.
|
66
|
+
--debug-extract Executable will unpack to local dir and not delete after.
|
67
|
+
USG
|
68
|
+
|
69
|
+
while (arg = argv.shift)
|
70
|
+
case arg
|
71
|
+
when /\A--(no-)?lzma\z/
|
72
|
+
@options[:lzma_mode] = !::Regexp.last_match(1)
|
73
|
+
when /\A--no-dep-run\z/
|
74
|
+
@options[:run_script] = false
|
75
|
+
when /\A--add-all-core\z/
|
76
|
+
@options[:add_all_core] = true
|
77
|
+
when /\A--output\z/
|
78
|
+
@options[:output_override] = Pathname(argv.shift)
|
79
|
+
when /\A--dll\z/
|
80
|
+
@options[:extra_dlls] << argv.shift
|
81
|
+
when /\A--quiet\z/
|
82
|
+
@options[:quiet] = true
|
83
|
+
when /\A--verbose\z/
|
84
|
+
@options[:verbose] = true
|
85
|
+
when /\A--windows\z/
|
86
|
+
@options[:force_windows] = true
|
87
|
+
when /\A--console\z/
|
88
|
+
@options[:force_console] = true
|
89
|
+
when /\A--no-autoload\z/
|
90
|
+
@options[:load_autoload] = false
|
91
|
+
when /\A--chdir-first\z/
|
92
|
+
@options[:chdir_first] = true
|
93
|
+
when /\A--icon\z/
|
94
|
+
@options[:icon_filename] = Pathname(argv.shift)
|
95
|
+
Aibika.fatal_error "Icon file #{icon_filename} not found.\n" unless icon_filename.exist?
|
96
|
+
when /\A--gemfile\z/
|
97
|
+
@options[:gemfile] = Pathname(argv.shift)
|
98
|
+
Aibika.fatal_error "Gemfile #{gemfile} not found.\n" unless gemfile.exist?
|
99
|
+
when /\A--innosetup\z/
|
100
|
+
@options[:inno_script] = Pathname(argv.shift)
|
101
|
+
Aibika.fatal_error "Inno Script #{inno_script} not found.\n" unless inno_script.exist?
|
102
|
+
when /\A--no-autodll\z/
|
103
|
+
@options[:autodll] = false
|
104
|
+
when /\A--version\z/
|
105
|
+
puts "Aibika #{VERSION}"
|
106
|
+
exit 0
|
107
|
+
when /\A--no-warnings\z/
|
108
|
+
@options[:show_warnings] = false
|
109
|
+
when /\A--debug\z/
|
110
|
+
@options[:debug] = true
|
111
|
+
when /\A--debug-extract\z/
|
112
|
+
@options[:debug_extract] = true
|
113
|
+
when /\A--\z/
|
114
|
+
@options[:arg] = ARGV.dup
|
115
|
+
ARGV.clear
|
116
|
+
when /\A--(no-)?enc\z/
|
117
|
+
@options[:enc] = !::Regexp.last_match(1)
|
118
|
+
when /\A--(no-)?gem-(\w+)(?:=(.*))?$/
|
119
|
+
negate = ::Regexp.last_match(1)
|
120
|
+
group = ::Regexp.last_match(2)
|
121
|
+
list = ::Regexp.last_match(3)
|
122
|
+
@options[:gem] ||= []
|
123
|
+
@options[:gem] << [negate, group.to_sym, list && list.split(',')]
|
124
|
+
when /\A--help\z/, /\A--./
|
125
|
+
puts usage
|
126
|
+
exit 0
|
127
|
+
else
|
128
|
+
@options[:files] << if __FILE__.respond_to?(:encoding)
|
129
|
+
arg.dup.force_encoding(__FILE__.encoding)
|
130
|
+
else
|
131
|
+
arg
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
if Aibika.debug_extract && Aibika.inno_script
|
137
|
+
Aibika.fatal_error 'The --debug-extract option conflicts with use of Inno Setup'
|
138
|
+
end
|
139
|
+
|
140
|
+
if Aibika.lzma_mode && Aibika.inno_script
|
141
|
+
Aibika.fatal_error 'LZMA compression must be disabled (--no-lzma) when using Inno Setup'
|
142
|
+
end
|
143
|
+
|
144
|
+
if !Aibika.chdir_first && Aibika.inno_script
|
145
|
+
Aibika.fatal_error 'Chdir-first mode must be enabled (--chdir-first) when using Inno Setup'
|
146
|
+
end
|
147
|
+
|
148
|
+
if files.empty?
|
149
|
+
puts usage
|
150
|
+
exit 1
|
151
|
+
end
|
152
|
+
|
153
|
+
@options[:files].map! do |path|
|
154
|
+
path = path.encode('UTF-8').tr('\\', '/')
|
155
|
+
if File.directory?(path)
|
156
|
+
# If a directory is passed, we want all files under that directory
|
157
|
+
path = "#{path}/**/*"
|
158
|
+
end
|
159
|
+
files = Dir[path]
|
160
|
+
Aibika.fatal_error "#{path} not found!" if files.empty?
|
161
|
+
files.map { |pth| Pathname(pth).expand }
|
162
|
+
end.flatten!
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.msg(msg)
|
166
|
+
puts "=== #{msg}" unless Aibika.quiet
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.verbose_msg(msg)
|
170
|
+
puts msg if Aibika.verbose && !Aibika.quiet
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.warn(msg)
|
174
|
+
msg "WARNING: #{msg}" if Aibika.show_warnings
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.fatal_error(msg)
|
178
|
+
puts "ERROR: #{msg}"
|
179
|
+
exit 1
|
180
|
+
end
|
181
|
+
end
|
data/lib/aibika/host.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aibika
|
4
|
+
# Variables describing the host's build environment.
|
5
|
+
module Host
|
6
|
+
class << self
|
7
|
+
def exec_prefix
|
8
|
+
@exec_prefix ||= Aibika.Pathname(RbConfig::CONFIG['exec_prefix'])
|
9
|
+
end
|
10
|
+
|
11
|
+
def sitelibdir
|
12
|
+
@sitelibdir ||= Aibika.Pathname(RbConfig::CONFIG['sitelibdir'])
|
13
|
+
end
|
14
|
+
|
15
|
+
def bindir
|
16
|
+
@bindir ||= Aibika.Pathname(RbConfig::CONFIG['bindir'])
|
17
|
+
end
|
18
|
+
|
19
|
+
def libruby_so
|
20
|
+
@libruby_so ||= Aibika.Pathname(RbConfig::CONFIG['LIBRUBY_SO'])
|
21
|
+
end
|
22
|
+
|
23
|
+
def exeext
|
24
|
+
RbConfig::CONFIG['EXEEXT'] || '.exe'
|
25
|
+
end
|
26
|
+
|
27
|
+
def rubyw_exe
|
28
|
+
@rubyw_exe ||= (RbConfig::CONFIG['rubyw_install_name'] || 'rubyw') + exeext
|
29
|
+
end
|
30
|
+
|
31
|
+
def ruby_exe
|
32
|
+
@ruby_exe ||= (RbConfig::CONFIG['ruby_install_name'] || 'ruby') + exeext
|
33
|
+
end
|
34
|
+
|
35
|
+
def tempdir
|
36
|
+
@tempdir ||= Aibika.Pathname(ENV['TEMP'])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aibika
|
4
|
+
module LibraryDetector
|
5
|
+
def self.init_fiddle
|
6
|
+
require 'fiddle'
|
7
|
+
require 'fiddle/types'
|
8
|
+
module_eval do
|
9
|
+
extend Fiddle::Importer
|
10
|
+
dlload 'psapi.dll'
|
11
|
+
include Fiddle::Win32Types
|
12
|
+
extern 'BOOL EnumProcessModules(HANDLE, HMODULE*, DWORD, DWORD*)'
|
13
|
+
extend Fiddle::Importer
|
14
|
+
dlload 'kernel32.dll'
|
15
|
+
include Fiddle::Win32Types
|
16
|
+
|
17
|
+
# https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types
|
18
|
+
# typedef PVOID HANDLE
|
19
|
+
# typedef HINSTANCE HMODULE;
|
20
|
+
|
21
|
+
typealias 'HMODULE', 'voidp'
|
22
|
+
typealias 'HANDLE', 'voidp'
|
23
|
+
typealias 'LPWSTR', 'char*'
|
24
|
+
|
25
|
+
extern 'DWORD GetModuleFileNameW(HMODULE, LPWSTR, DWORD)'
|
26
|
+
extern 'HANDLE GetCurrentProcess(void)'
|
27
|
+
extern 'DWORD GetLastError(void)'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.loaded_dlls
|
32
|
+
require 'fiddle'
|
33
|
+
psapi = Fiddle.dlopen('psapi')
|
34
|
+
enumprocessmodules = Fiddle::Function.new(
|
35
|
+
psapi['EnumProcessModules'],
|
36
|
+
[Fiddle::TYPE_UINTPTR_T, Fiddle::TYPE_VOIDP, Fiddle::TYPE_LONG, Fiddle::TYPE_VOIDP], Fiddle::TYPE_LONG
|
37
|
+
)
|
38
|
+
kernel32 = Fiddle.dlopen('kernel32')
|
39
|
+
getcurrentprocess = Fiddle::Function.new(kernel32['GetCurrentProcess'], [], Fiddle::TYPE_LONG)
|
40
|
+
getmodulefilename = Fiddle::Function.new(
|
41
|
+
kernel32['GetModuleFileNameW'],
|
42
|
+
[Fiddle::TYPE_UINTPTR_T, Fiddle::TYPE_VOIDP, Fiddle::TYPE_LONG], Fiddle::TYPE_LONG
|
43
|
+
)
|
44
|
+
getlasterror = Fiddle::Function.new(kernel32['GetLastError'], [], Fiddle::TYPE_LONG)
|
45
|
+
|
46
|
+
# Different packing/unpacking for 64/32 bits systems
|
47
|
+
if Fiddle::SIZEOF_VOIDP == 8
|
48
|
+
f_single = 'Q'
|
49
|
+
f_array = 'Q*'
|
50
|
+
else
|
51
|
+
f_single = 'I'
|
52
|
+
f_array = 'I*'
|
53
|
+
end
|
54
|
+
|
55
|
+
bytes_needed = Fiddle::SIZEOF_VOIDP * 32
|
56
|
+
module_handle_buffer = nil
|
57
|
+
process_handle = getcurrentprocess.call
|
58
|
+
loop do
|
59
|
+
module_handle_buffer = "\x00" * bytes_needed
|
60
|
+
bytes_needed_buffer = [0].pack(f_single)
|
61
|
+
enumprocessmodules.call(process_handle, module_handle_buffer, module_handle_buffer.size,
|
62
|
+
bytes_needed_buffer)
|
63
|
+
bytes_needed = bytes_needed_buffer.unpack1(f_single)
|
64
|
+
break if bytes_needed <= module_handle_buffer.size
|
65
|
+
end
|
66
|
+
|
67
|
+
handles = module_handle_buffer.unpack(f_array)
|
68
|
+
handles.select(&:positive?).map do |handle|
|
69
|
+
str = "\x00\x00" * 256
|
70
|
+
modulefilename_length = getmodulefilename.call(handle, str, str.size)
|
71
|
+
unless modulefilename_length.positive?
|
72
|
+
errorcode = getlasterror.call
|
73
|
+
Aibika.fatal_error "LibraryDetector: GetModuleFileNameW failed with error code 0x#{errorcode.to_s(16)}"
|
74
|
+
end
|
75
|
+
modulefilename = str[0, modulefilename_length * 2].force_encoding('UTF-16LE').encode('UTF-8')
|
76
|
+
Aibika.Pathname(modulefilename)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.detect_dlls
|
81
|
+
loaded = loaded_dlls
|
82
|
+
exec_prefix = Host.exec_prefix
|
83
|
+
loaded.select do |path|
|
84
|
+
path.subpath?(exec_prefix) && path.basename.ext?('.dll') && path.basename != Host.libruby_so
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aibika
|
4
|
+
# Path handling class. Ruby's Pathname class is not used because it
|
5
|
+
# is case sensitive and doesn't handle paths with mixed path
|
6
|
+
# separators.
|
7
|
+
class Pathname
|
8
|
+
def self.pwd
|
9
|
+
Pathname.new(Dir.pwd.encode('UTF-8'))
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.pathequal(path_a, path_b)
|
13
|
+
path_a.downcase == path_b.downcase
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :path
|
17
|
+
|
18
|
+
File::ALT_SEPARATOR = '\\' if File::ALT_SEPARATOR.nil?
|
19
|
+
SEPARATOR_PAT = /[#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}]/.freeze # }
|
20
|
+
ABSOLUTE_PAT = /\A([A-Z]:)?#{SEPARATOR_PAT}/i.freeze
|
21
|
+
|
22
|
+
def initialize(path)
|
23
|
+
@path = path&.encode('UTF-8')
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_native
|
27
|
+
@path.tr File::SEPARATOR, File::ALT_SEPARATOR
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_posix
|
31
|
+
@path.tr File::ALT_SEPARATOR, File::SEPARATOR
|
32
|
+
end
|
33
|
+
|
34
|
+
# Compute the relative path from the 'src' path (directory) to 'tgt'
|
35
|
+
# (directory or file). Return the absolute path to 'tgt' if it can't
|
36
|
+
# be reached from 'src'.
|
37
|
+
def relative_path_from(other)
|
38
|
+
a = @path.split(SEPARATOR_PAT)
|
39
|
+
b = other.path.split(SEPARATOR_PAT)
|
40
|
+
while a.first && b.first && Pathname.pathequal(a.first, b.first)
|
41
|
+
a.shift
|
42
|
+
b.shift
|
43
|
+
end
|
44
|
+
return other if Pathname.new(b.first).absolute?
|
45
|
+
|
46
|
+
b.size.times { a.unshift '..' }
|
47
|
+
Pathname.new(a.join('/'))
|
48
|
+
end
|
49
|
+
|
50
|
+
# Determines if 'src' is contained in 'tgt' (i.e. it is a subpath of
|
51
|
+
# 'tgt'). Both must be absolute paths and not contain '..'
|
52
|
+
def subpath?(other)
|
53
|
+
other = Aibika.Pathname(other)
|
54
|
+
src_normalized = to_posix.downcase
|
55
|
+
tgt_normalized = other.to_posix.downcase
|
56
|
+
src_normalized =~ /^#{Regexp.escape tgt_normalized}#{SEPARATOR_PAT}/i
|
57
|
+
end
|
58
|
+
|
59
|
+
# Join two pathnames together. Returns the right-hand side if it
|
60
|
+
# is an absolute path. Otherwise, returns the full path of the
|
61
|
+
# left + right.
|
62
|
+
def /(other)
|
63
|
+
other = Aibika.Pathname(other)
|
64
|
+
if other.absolute?
|
65
|
+
other
|
66
|
+
else
|
67
|
+
Aibika.Pathname("#{@path}/#{other.path}")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def append_to_filename!(append)
|
72
|
+
@path.sub!(/(\.[^.]*?|)$/) { append.to_s + ::Regexp.last_match(1) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def ext(new_ext = nil)
|
76
|
+
if new_ext
|
77
|
+
Pathname.new(@path.sub(/(\.[^.]*?)?$/) { new_ext })
|
78
|
+
else
|
79
|
+
File.extname(@path)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def ext?(expected_ext)
|
84
|
+
Pathname.pathequal(ext, expected_ext)
|
85
|
+
end
|
86
|
+
|
87
|
+
def entries
|
88
|
+
Dir.entries(@path).map { |e| self / e.encode('UTF-8') }
|
89
|
+
end
|
90
|
+
|
91
|
+
# Recursively find all files which match a specified regular
|
92
|
+
# expression.
|
93
|
+
def find_all_files(reg)
|
94
|
+
entries.map do |pn|
|
95
|
+
if pn.directory?
|
96
|
+
if pn.basename =~ /^\.\.?$/
|
97
|
+
[]
|
98
|
+
else
|
99
|
+
pn.find_all_files(reg)
|
100
|
+
end
|
101
|
+
elsif pn.file?
|
102
|
+
if pn.basename =~ reg
|
103
|
+
pn
|
104
|
+
else
|
105
|
+
[]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end.flatten
|
109
|
+
end
|
110
|
+
|
111
|
+
def ==(other)
|
112
|
+
to_posix.downcase == other.to_posix.downcase
|
113
|
+
end
|
114
|
+
|
115
|
+
def =~(other)
|
116
|
+
@path =~ other
|
117
|
+
end
|
118
|
+
|
119
|
+
def <=>(other)
|
120
|
+
@path.casecmp(other.path)
|
121
|
+
end
|
122
|
+
|
123
|
+
def exist?
|
124
|
+
File.exist?(@path)
|
125
|
+
end
|
126
|
+
|
127
|
+
def file?
|
128
|
+
File.file?(@path)
|
129
|
+
end
|
130
|
+
|
131
|
+
def directory?
|
132
|
+
File.directory?(@path)
|
133
|
+
end
|
134
|
+
|
135
|
+
def absolute?
|
136
|
+
@path =~ ABSOLUTE_PAT
|
137
|
+
end
|
138
|
+
|
139
|
+
def dirname
|
140
|
+
Pathname.new(File.dirname(@path))
|
141
|
+
end
|
142
|
+
|
143
|
+
def basename
|
144
|
+
Pathname.new(File.basename(@path))
|
145
|
+
end
|
146
|
+
|
147
|
+
def expand(dir = nil)
|
148
|
+
Pathname.new(File.expand_path(@path, dir && Aibika.Pathname(dir)))
|
149
|
+
end
|
150
|
+
|
151
|
+
def size
|
152
|
+
File.size(@path)
|
153
|
+
end
|
154
|
+
|
155
|
+
alias to_s to_posix
|
156
|
+
alias to_str to_posix
|
157
|
+
end
|
158
|
+
end
|