aibika 1.3.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
|