ocran 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/bin/ocran +1278 -0
- data/lib/ocran.rb +3 -0
- data/share/ocran/edicon.exe +0 -0
- data/share/ocran/lzma.exe +0 -0
- data/share/ocran/stub.exe +0 -0
- data/share/ocran/stubw.exe +0 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d3a58068c425f2d336d76700e029910fb63f307f43a3323e72ccf3f6f80cc575
|
4
|
+
data.tar.gz: 9876cc77dbccd0ce1f0278f326e381d78656fc75417be8693d6218af0073cbd7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ab3b92cd5d2a1e93a09bcfd99d9992e48c110b38e775d68a1ee5104f0601eb11ba13b428b447831bb771330c300c719b633413d4350d01205bd8700423408fc8
|
7
|
+
data.tar.gz: 4dc1c362a1f6589c1cf5df5ba10211c00f7e6b792f380c58da3cc987e02758152b5c410671e43f5c099943ced230328e7be6cd0fa65f9f16d0a4e6b0c3584a46
|
data/bin/ocran
ADDED
@@ -0,0 +1,1278 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- ruby -*-
|
3
|
+
# encoding: UTF-8
|
4
|
+
|
5
|
+
module Ocran
|
6
|
+
# Path handling class. Ruby's Pathname class is not used because it
|
7
|
+
# is case sensitive and doesn't handle paths with mixed path
|
8
|
+
# separators.
|
9
|
+
class Pathname
|
10
|
+
def Pathname.pwd
|
11
|
+
Pathname.new(Dir.pwd.encode("UTF-8"))
|
12
|
+
end
|
13
|
+
|
14
|
+
def Pathname.pathequal(a, b)
|
15
|
+
a.downcase == b.downcase
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :path
|
19
|
+
if File::ALT_SEPARATOR.nil?
|
20
|
+
File::ALT_SEPARATOR = "\\"
|
21
|
+
end
|
22
|
+
SEPARATOR_PAT = /[#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}]/ # }
|
23
|
+
ABSOLUTE_PAT = /\A([A-Z]:)?#{SEPARATOR_PAT}/i
|
24
|
+
|
25
|
+
def initialize(path)
|
26
|
+
@path = path && path.encode("UTF-8")
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_native
|
30
|
+
@path.tr File::SEPARATOR, File::ALT_SEPARATOR
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_posix
|
34
|
+
@path.tr File::ALT_SEPARATOR, File::SEPARATOR
|
35
|
+
end
|
36
|
+
|
37
|
+
# Compute the relative path from the 'src' path (directory) to 'tgt'
|
38
|
+
# (directory or file). Return the absolute path to 'tgt' if it can't
|
39
|
+
# be reached from 'src'.
|
40
|
+
def relative_path_from(other)
|
41
|
+
a = @path.split(SEPARATOR_PAT)
|
42
|
+
b = other.path.split(SEPARATOR_PAT)
|
43
|
+
while a.first && b.first && Pathname.pathequal(a.first, b.first)
|
44
|
+
a.shift
|
45
|
+
b.shift
|
46
|
+
end
|
47
|
+
return other if Pathname.new(b.first).absolute?
|
48
|
+
b.size.times { a.unshift ".." }
|
49
|
+
return Pathname.new(a.join("/"))
|
50
|
+
end
|
51
|
+
|
52
|
+
# Determines if 'src' is contained in 'tgt' (i.e. it is a subpath of
|
53
|
+
# 'tgt'). Both must be absolute paths and not contain '..'
|
54
|
+
def subpath?(other)
|
55
|
+
other = Ocran.Pathname(other)
|
56
|
+
src_normalized = to_posix.downcase
|
57
|
+
tgt_normalized = other.to_posix.downcase
|
58
|
+
src_normalized =~ /^#{Regexp.escape tgt_normalized}#{SEPARATOR_PAT}/i
|
59
|
+
end
|
60
|
+
|
61
|
+
# Join two pathnames together. Returns the right-hand side if it
|
62
|
+
# is an absolute path. Otherwise, returns the full path of the
|
63
|
+
# left + right.
|
64
|
+
def /(other)
|
65
|
+
other = Ocran.Pathname(other)
|
66
|
+
if other.absolute?
|
67
|
+
other
|
68
|
+
else
|
69
|
+
Ocran.Pathname(@path + "/" + other.path)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def append_to_filename!(s)
|
74
|
+
@path.sub!(/(\.[^.]*?|)$/) { s.to_s + $1 }
|
75
|
+
end
|
76
|
+
|
77
|
+
def ext(new_ext = nil)
|
78
|
+
if new_ext
|
79
|
+
Pathname.new(@path.sub(/(\.[^.]*?)?$/) { new_ext })
|
80
|
+
else
|
81
|
+
File.extname(@path)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def ext?(expected_ext)
|
86
|
+
Pathname.pathequal(ext, expected_ext)
|
87
|
+
end
|
88
|
+
|
89
|
+
def entries
|
90
|
+
Dir.entries(@path).map { |e| self / e.encode("UTF-8") }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Recursively find all files which match a specified regular
|
94
|
+
# expression.
|
95
|
+
def find_all_files(re)
|
96
|
+
entries.map do |pn|
|
97
|
+
if pn.directory?
|
98
|
+
if pn.basename =~ /^\.\.?$/
|
99
|
+
[]
|
100
|
+
else
|
101
|
+
pn.find_all_files(re)
|
102
|
+
end
|
103
|
+
elsif pn.file?
|
104
|
+
if pn.basename =~ re
|
105
|
+
pn
|
106
|
+
else
|
107
|
+
[]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end.flatten
|
111
|
+
end
|
112
|
+
|
113
|
+
def ==(other); to_posix.downcase == other.to_posix.downcase; end
|
114
|
+
def =~(o); @path =~ o; end
|
115
|
+
def <=>(other); @path.casecmp(other.path); end
|
116
|
+
def exist?; File.exist?(@path); end
|
117
|
+
def file?; File.file?(@path); end
|
118
|
+
def directory?; File.directory?(@path); end
|
119
|
+
def absolute?; @path =~ ABSOLUTE_PAT; end
|
120
|
+
def dirname; Pathname.new(File.dirname(@path)); end
|
121
|
+
def basename; Pathname.new(File.basename(@path)); end
|
122
|
+
def expand(dir = nil); Pathname.new(File.expand_path(@path, dir && Ocran.Pathname(dir))); end
|
123
|
+
def size; File.size(@path); end
|
124
|
+
|
125
|
+
alias to_s to_posix
|
126
|
+
alias to_str to_posix
|
127
|
+
end
|
128
|
+
|
129
|
+
# Type conversion for the Pathname class. Works with Pathname,
|
130
|
+
# String, NilClass and arrays of any of these.
|
131
|
+
def self.Pathname(obj)
|
132
|
+
case obj
|
133
|
+
when Pathname
|
134
|
+
obj
|
135
|
+
when Array
|
136
|
+
obj.map { |x| Pathname(x) }
|
137
|
+
when String
|
138
|
+
Pathname.new(obj)
|
139
|
+
when NilClass
|
140
|
+
nil
|
141
|
+
else
|
142
|
+
raise ArgumentError, obj
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Variables describing the host's build environment.
|
147
|
+
module Host
|
148
|
+
class << self
|
149
|
+
def exec_prefix
|
150
|
+
@exec_prefix ||= Ocran.Pathname(RbConfig::CONFIG["exec_prefix"])
|
151
|
+
end
|
152
|
+
|
153
|
+
def sitelibdir
|
154
|
+
@sitelibdir ||= Ocran.Pathname(RbConfig::CONFIG["sitelibdir"])
|
155
|
+
end
|
156
|
+
|
157
|
+
def bindir
|
158
|
+
@bindir ||= Ocran.Pathname(RbConfig::CONFIG["bindir"])
|
159
|
+
end
|
160
|
+
|
161
|
+
def libruby_so
|
162
|
+
@libruby_so ||= Ocran.Pathname(RbConfig::CONFIG["LIBRUBY_SO"])
|
163
|
+
end
|
164
|
+
|
165
|
+
def exeext
|
166
|
+
RbConfig::CONFIG["EXEEXT"] || ".exe"
|
167
|
+
end
|
168
|
+
|
169
|
+
def rubyw_exe
|
170
|
+
@rubyw_exe ||= (RbConfig::CONFIG["rubyw_install_name"] || "rubyw") + exeext
|
171
|
+
end
|
172
|
+
|
173
|
+
def ruby_exe
|
174
|
+
@ruby_exe ||= (RbConfig::CONFIG["ruby_install_name"] || "ruby") + exeext
|
175
|
+
end
|
176
|
+
|
177
|
+
def tempdir
|
178
|
+
@tempdir ||= Ocran.Pathname(ENV["TEMP"])
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Sorts and returns an array without duplicates. Works with complex
|
184
|
+
# objects (such as Pathname), in contrast to Array#uniq.
|
185
|
+
def self.sort_uniq(a)
|
186
|
+
a.sort.inject([]) { |r, e| r.last == e ? r : r << e }
|
187
|
+
end
|
188
|
+
|
189
|
+
VERSION = "1.3.12"
|
190
|
+
|
191
|
+
IGNORE_MODULE_NAMES = /\/(enumerator.so|rational.so|complex.so|fiber.so|thread.rb|ruby2_keywords.rb)$/
|
192
|
+
|
193
|
+
GEM_SCRIPT_RE = /\.rbw?$/
|
194
|
+
GEM_EXTRA_RE = %r{(
|
195
|
+
# Auxiliary files in the root of the gem
|
196
|
+
^(\.\/)?(History|Install|Manifest|README|CHANGES|Licen[sc]e|Contributors|ChangeLog|BSD|GPL).*$ |
|
197
|
+
# Installation files in the root of the gem
|
198
|
+
^(\.\/)?(Rakefile|setup.rb|extconf.rb)$ |
|
199
|
+
# Documentation/test directories in the root of the gem
|
200
|
+
^(\.\/)?(doc|ext|examples|test|tests|benchmarks|spec)\/ |
|
201
|
+
# Directories anywhere
|
202
|
+
(^|\/)(\.autotest|\.svn|\.cvs|\.git)(\/|$) |
|
203
|
+
# Unlikely extensions
|
204
|
+
\.(rdoc|c|cpp|c\+\+|cxx|h|hxx|hpp|obj|o|a)$/
|
205
|
+
)}xi
|
206
|
+
|
207
|
+
GEM_NON_FILE_RE = /(#{GEM_EXTRA_RE}|#{GEM_SCRIPT_RE})/
|
208
|
+
|
209
|
+
# Alias for the temporary directory where files are extracted.
|
210
|
+
TEMPDIR_ROOT = Pathname.new("|")
|
211
|
+
# Directory for source files in temporary directory.
|
212
|
+
SRCDIR = Pathname.new("src")
|
213
|
+
# Directory for Ruby binaries in temporary directory.
|
214
|
+
BINDIR = Pathname.new("bin")
|
215
|
+
# Directory for GEMHOME files in temporary directory.
|
216
|
+
GEMHOMEDIR = Pathname.new("gemhome")
|
217
|
+
|
218
|
+
IGNORE_MODULES = []
|
219
|
+
|
220
|
+
@options = {
|
221
|
+
:lzma_mode => true,
|
222
|
+
:extra_dlls => [],
|
223
|
+
:files => [],
|
224
|
+
:run_script => true,
|
225
|
+
:add_all_core => false,
|
226
|
+
:output_override => nil,
|
227
|
+
:load_autoload => true,
|
228
|
+
:chdir_first => false,
|
229
|
+
:force_windows => false,
|
230
|
+
:force_console => false,
|
231
|
+
:icon_filename => nil,
|
232
|
+
:gemfile => nil,
|
233
|
+
:inno_script => nil,
|
234
|
+
:quiet => false,
|
235
|
+
:verbose => false,
|
236
|
+
:autodll => true,
|
237
|
+
:show_warnings => true,
|
238
|
+
:debug => false,
|
239
|
+
:debug_extract => false,
|
240
|
+
:arg => [],
|
241
|
+
:enc => true,
|
242
|
+
:gem => [],
|
243
|
+
}
|
244
|
+
|
245
|
+
@options.each_key { |opt| eval("def self.#{opt}; @options[:#{opt}]; end") }
|
246
|
+
|
247
|
+
class << self
|
248
|
+
attr_reader :lzmapath
|
249
|
+
attr_reader :ediconpath
|
250
|
+
attr_reader :stubimage
|
251
|
+
attr_reader :stubwimage
|
252
|
+
end
|
253
|
+
|
254
|
+
def Ocran.msg(s)
|
255
|
+
puts "=== #{s}" unless Ocran.quiet
|
256
|
+
end
|
257
|
+
|
258
|
+
def Ocran.verbose_msg(s)
|
259
|
+
puts s if Ocran.verbose and not Ocran.quiet
|
260
|
+
end
|
261
|
+
|
262
|
+
def Ocran.warn(s)
|
263
|
+
msg "WARNING: #{s}" if Ocran.show_warnings
|
264
|
+
end
|
265
|
+
|
266
|
+
def Ocran.fatal_error(s)
|
267
|
+
puts "ERROR: #{s}"
|
268
|
+
exit 1
|
269
|
+
end
|
270
|
+
|
271
|
+
# Returns a binary blob store embedded in the current Ruby script.
|
272
|
+
def Ocran.get_next_embedded_image
|
273
|
+
DATA.read(DATA.readline.to_i).unpack("m")[0]
|
274
|
+
end
|
275
|
+
|
276
|
+
def Ocran.save_environment
|
277
|
+
@load_path_before = $LOAD_PATH.dup
|
278
|
+
@pwd_before = Dir.pwd
|
279
|
+
@env_before = {}; ENV.each { |key, value| @env_before[key] = value }
|
280
|
+
end
|
281
|
+
|
282
|
+
def Ocran.restore_environment
|
283
|
+
@env_before.each { |key, value| ENV[key] = value }
|
284
|
+
ENV.each_key { |key| ENV.delete(key) unless @env_before.has_key?(key) }
|
285
|
+
Dir.chdir @pwd_before
|
286
|
+
end
|
287
|
+
|
288
|
+
def Ocran.find_stubs
|
289
|
+
if defined?(DATA)
|
290
|
+
@stubimage = get_next_embedded_image
|
291
|
+
@stubwimage = get_next_embedded_image
|
292
|
+
lzmaimage = get_next_embedded_image
|
293
|
+
@lzmapath = Host.tempdir / "lzma.exe"
|
294
|
+
File.open(@lzmapath, "wb") { |file| file << lzmaimage }
|
295
|
+
ediconimage = get_next_embedded_image
|
296
|
+
@ediconpath = Host.tempdir / "edicon.exe"
|
297
|
+
File.open(@ediconpath, "wb") { |file| file << ediconimage }
|
298
|
+
else
|
299
|
+
ocranpath = Pathname(File.dirname(__FILE__))
|
300
|
+
@stubimage = File.open(ocranpath / "../share/ocran/stub.exe", "rb") { |file| file.read }
|
301
|
+
@stubwimage = File.open(ocranpath / "../share/ocran/stubw.exe", "rb") { |file| file.read }
|
302
|
+
@lzmapath = (ocranpath / "../share/ocran/lzma.exe").expand
|
303
|
+
@ediconpath = (ocranpath / "../share/ocran/edicon.exe").expand
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def Ocran.parseargs(argv)
|
308
|
+
usage = <<EOF
|
309
|
+
ocran [options] script.rb
|
310
|
+
|
311
|
+
Ocran options:
|
312
|
+
|
313
|
+
--help Display this information.
|
314
|
+
--quiet Suppress output while building executable.
|
315
|
+
--verbose Show extra output while building executable.
|
316
|
+
--version Display version number and exit.
|
317
|
+
|
318
|
+
Packaging options:
|
319
|
+
|
320
|
+
--dll dllname Include additional DLLs from the Ruby bindir.
|
321
|
+
--add-all-core Add all core ruby libraries to the executable.
|
322
|
+
--gemfile <file> Add all gems and dependencies listed in a Bundler Gemfile.
|
323
|
+
--no-enc Exclude encoding support files
|
324
|
+
|
325
|
+
Gem content detection modes:
|
326
|
+
|
327
|
+
--gem-minimal[=gem1,..] Include only loaded scripts
|
328
|
+
--gem-guess=[gem1,...] Include loaded scripts & best guess (DEFAULT)
|
329
|
+
--gem-all[=gem1,..] Include all scripts & files
|
330
|
+
--gem-full[=gem1,..] Include EVERYTHING
|
331
|
+
--gem-spec[=gem1,..] Include files in gemspec (Does not work with Rubygems 1.7+)
|
332
|
+
|
333
|
+
minimal: loaded scripts
|
334
|
+
guess: loaded scripts and other files
|
335
|
+
all: loaded scripts, other scripts, other files (except extras)
|
336
|
+
full: Everything found in the gem directory
|
337
|
+
|
338
|
+
--[no-]gem-scripts[=..] Other script files than those loaded
|
339
|
+
--[no-]gem-files[=..] Other files (e.g. data files)
|
340
|
+
--[no-]gem-extras[=..] Extra files (README, etc.)
|
341
|
+
|
342
|
+
scripts: .rb/.rbw files
|
343
|
+
extras: C/C++ sources, object files, test, spec, README
|
344
|
+
files: all other files
|
345
|
+
|
346
|
+
Auto-detection options:
|
347
|
+
|
348
|
+
--no-dep-run Don't run script.rb to check for dependencies.
|
349
|
+
--no-autoload Don't load/include script.rb's autoloads.
|
350
|
+
--no-autodll Disable detection of runtime DLL dependencies.
|
351
|
+
|
352
|
+
Output options:
|
353
|
+
|
354
|
+
--output <file> Name the exe to generate. Defaults to ./<scriptname>.exe.
|
355
|
+
--no-lzma Disable LZMA compression of the executable.
|
356
|
+
--innosetup <file> Use given Inno Setup script (.iss) to create an installer.
|
357
|
+
|
358
|
+
Executable options:
|
359
|
+
|
360
|
+
--windows Force Windows application (rubyw.exe)
|
361
|
+
--console Force console application (ruby.exe)
|
362
|
+
--chdir-first When exe starts, change working directory to app dir.
|
363
|
+
--icon <ico> Replace icon with a custom one.
|
364
|
+
--debug Executable will be verbose.
|
365
|
+
--debug-extract Executable will unpack to local dir and not delete after.
|
366
|
+
EOF
|
367
|
+
|
368
|
+
while arg = argv.shift
|
369
|
+
case arg
|
370
|
+
when /\A--(no-)?lzma\z/
|
371
|
+
@options[:lzma_mode] = !$1
|
372
|
+
when /\A--no-dep-run\z/
|
373
|
+
@options[:run_script] = false
|
374
|
+
when /\A--add-all-core\z/
|
375
|
+
@options[:add_all_core] = true
|
376
|
+
when /\A--output\z/
|
377
|
+
@options[:output_override] = Pathname(argv.shift)
|
378
|
+
when /\A--dll\z/
|
379
|
+
@options[:extra_dlls] << argv.shift
|
380
|
+
when /\A--quiet\z/
|
381
|
+
@options[:quiet] = true
|
382
|
+
when /\A--verbose\z/
|
383
|
+
@options[:verbose] = true
|
384
|
+
when /\A--windows\z/
|
385
|
+
@options[:force_windows] = true
|
386
|
+
when /\A--console\z/
|
387
|
+
@options[:force_console] = true
|
388
|
+
when /\A--no-autoload\z/
|
389
|
+
@options[:load_autoload] = false
|
390
|
+
when /\A--chdir-first\z/
|
391
|
+
@options[:chdir_first] = true
|
392
|
+
when /\A--icon\z/
|
393
|
+
@options[:icon_filename] = Pathname(argv.shift)
|
394
|
+
Ocran.fatal_error "Icon file #{icon_filename} not found.\n" unless icon_filename.exist?
|
395
|
+
when /\A--gemfile\z/
|
396
|
+
@options[:gemfile] = Pathname(argv.shift)
|
397
|
+
Ocran.fatal_error "Gemfile #{gemfile} not found.\n" unless gemfile.exist?
|
398
|
+
when /\A--innosetup\z/
|
399
|
+
@options[:inno_script] = Pathname(argv.shift)
|
400
|
+
Ocran.fatal_error "Inno Script #{inno_script} not found.\n" unless inno_script.exist?
|
401
|
+
when /\A--no-autodll\z/
|
402
|
+
@options[:autodll] = false
|
403
|
+
when /\A--version\z/
|
404
|
+
puts "Ocran #{VERSION}"
|
405
|
+
exit 0
|
406
|
+
when /\A--no-warnings\z/
|
407
|
+
@options[:show_warnings] = false
|
408
|
+
when /\A--debug\z/
|
409
|
+
@options[:debug] = true
|
410
|
+
when /\A--debug-extract\z/
|
411
|
+
@options[:debug_extract] = true
|
412
|
+
when /\A--\z/
|
413
|
+
@options[:arg] = ARGV.dup
|
414
|
+
ARGV.clear
|
415
|
+
when /\A--(no-)?enc\z/
|
416
|
+
@options[:enc] = !$1
|
417
|
+
when /\A--(no-)?gem-(\w+)(?:=(.*))?$/
|
418
|
+
negate, group, list = $1, $2, $3
|
419
|
+
@options[:gem] ||= []
|
420
|
+
@options[:gem] << [negate, group.to_sym, list && list.split(",")]
|
421
|
+
when /\A--help\z/, /\A--./
|
422
|
+
puts usage
|
423
|
+
exit 0
|
424
|
+
else
|
425
|
+
if __FILE__.respond_to?(:encoding)
|
426
|
+
@options[:files] << arg.dup.force_encoding(__FILE__.encoding)
|
427
|
+
else
|
428
|
+
@options[:files] << arg
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
if Ocran.debug_extract && Ocran.inno_script
|
434
|
+
Ocran.fatal_error "The --debug-extract option conflicts with use of Inno Setup"
|
435
|
+
end
|
436
|
+
|
437
|
+
if Ocran.lzma_mode && Ocran.inno_script
|
438
|
+
Ocran.fatal_error "LZMA compression must be disabled (--no-lzma) when using Inno Setup"
|
439
|
+
end
|
440
|
+
|
441
|
+
if !Ocran.chdir_first && Ocran.inno_script
|
442
|
+
Ocran.fatal_error "Chdir-first mode must be enabled (--chdir-first) when using Inno Setup"
|
443
|
+
end
|
444
|
+
|
445
|
+
if files.empty?
|
446
|
+
puts usage
|
447
|
+
exit 1
|
448
|
+
end
|
449
|
+
|
450
|
+
@options[:files].map! { |path|
|
451
|
+
path = path.encode("UTF-8").tr('\\', "/")
|
452
|
+
if File.directory?(path)
|
453
|
+
# If a directory is passed, we want all files under that directory
|
454
|
+
path = "#{path}/**/*"
|
455
|
+
end
|
456
|
+
files = Dir[path]
|
457
|
+
Ocran.fatal_error "#{path} not found!" if files.empty?
|
458
|
+
files.map { |path| Pathname(path).expand }
|
459
|
+
}.flatten!
|
460
|
+
end
|
461
|
+
|
462
|
+
def Ocran.init(argv)
|
463
|
+
save_environment
|
464
|
+
parseargs(argv)
|
465
|
+
find_stubs
|
466
|
+
IGNORE_MODULES.push(*ObjectSpace.each_object(Module).to_a)
|
467
|
+
end
|
468
|
+
|
469
|
+
# Force loading autoloaded constants. Searches through all modules
|
470
|
+
# (and hence classes), and checks their constants for autoloaded
|
471
|
+
# ones, then attempts to load them.
|
472
|
+
def Ocran.attempt_load_autoload
|
473
|
+
modules_checked = {}
|
474
|
+
IGNORE_MODULES.each { |m| modules_checked[m] = true }
|
475
|
+
loop do
|
476
|
+
modules_to_check = []
|
477
|
+
ObjectSpace.each_object(Module) do |mod|
|
478
|
+
modules_to_check << mod unless modules_checked.include?(mod)
|
479
|
+
end
|
480
|
+
break if modules_to_check.empty?
|
481
|
+
modules_to_check.each do |mod|
|
482
|
+
modules_checked[mod] = true
|
483
|
+
mod.constants.each do |const|
|
484
|
+
# Module::Config causes warning on Ruby 1.9.3 - prevent autoloading
|
485
|
+
next if Module === mod && const == :Config
|
486
|
+
if mod.autoload?(const)
|
487
|
+
Ocran.msg "Attempting to trigger autoload of #{mod}::#{const}"
|
488
|
+
begin
|
489
|
+
mod.const_get(const)
|
490
|
+
rescue NameError
|
491
|
+
Ocran.warn "#{mod}::#{const} was defined autoloadable, but caused NameError"
|
492
|
+
rescue LoadError
|
493
|
+
Ocran.warn "#{mod}::#{const} was not loadable"
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
# Guess the load path (from 'paths') that was used to load
|
502
|
+
# 'path'. This is primarily relevant on Ruby 1.8 which stores
|
503
|
+
# "unqualified" paths in $LOADED_FEATURES.
|
504
|
+
def Ocran.find_load_path(loadpaths, feature)
|
505
|
+
if feature.absolute?
|
506
|
+
# Choose those loadpaths which contain the feature
|
507
|
+
candidate_loadpaths = loadpaths.select { |loadpath| feature.subpath?(loadpath.expand) }
|
508
|
+
# Guess the require'd feature
|
509
|
+
feature_pairs = candidate_loadpaths.map { |loadpath| [loadpath, feature.relative_path_from(loadpath.expand)] }
|
510
|
+
# Select the shortest possible require-path (longest load-path)
|
511
|
+
if feature_pairs.empty?
|
512
|
+
nil
|
513
|
+
else
|
514
|
+
feature_pairs.sort_by { |loadpath, feature| feature.path.size }.first[0]
|
515
|
+
end
|
516
|
+
else
|
517
|
+
# Select the loadpaths that contain 'feature' and select the shortest
|
518
|
+
candidates = loadpaths.select { |loadpath| feature.expand(loadpath).exist? }
|
519
|
+
candidates.sort_by { |loadpath| loadpath.path.size }.last
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
# Find the root of all files specified on the command line and use
|
524
|
+
# it as the "src" of the output.
|
525
|
+
def Ocran.find_src_root(files)
|
526
|
+
src_files = files.map { |file| file.expand }
|
527
|
+
src_prefix = src_files.inject(src_files.first.dirname) do |srcroot, path|
|
528
|
+
if path.subpath?(Host.exec_prefix)
|
529
|
+
srcroot
|
530
|
+
else
|
531
|
+
loop do
|
532
|
+
relpath = path.relative_path_from(srcroot)
|
533
|
+
if relpath.absolute?
|
534
|
+
Ocran.fatal_error "No common directory contains all specified files"
|
535
|
+
end
|
536
|
+
if relpath.to_s =~ /^\.\.\//
|
537
|
+
srcroot = srcroot.dirname
|
538
|
+
else
|
539
|
+
break
|
540
|
+
end
|
541
|
+
end
|
542
|
+
srcroot
|
543
|
+
end
|
544
|
+
end
|
545
|
+
src_files = src_files.map do |file|
|
546
|
+
if file.subpath?(src_prefix)
|
547
|
+
file.relative_path_from(src_prefix)
|
548
|
+
else
|
549
|
+
file
|
550
|
+
end
|
551
|
+
end
|
552
|
+
return src_prefix, src_files
|
553
|
+
end
|
554
|
+
|
555
|
+
# Searches for features that are loaded from gems, then produces a
|
556
|
+
# list of files included in those gems' manifests. Also returns a
|
557
|
+
# list of original features that are caused gems to be
|
558
|
+
# included. Ruby 1.8 provides Gem.loaded_specs to detect gems, but
|
559
|
+
# this is empty with Ruby 1.9. So instead, we look for any loaded
|
560
|
+
# file from a gem path.
|
561
|
+
def Ocran.find_gem_files(features)
|
562
|
+
features_from_gems = []
|
563
|
+
gems = {}
|
564
|
+
|
565
|
+
# If a Bundler Gemfile was provided, add all gems it specifies
|
566
|
+
if Ocran.gemfile
|
567
|
+
Ocran.msg "Scanning Gemfile"
|
568
|
+
# Load Rubygems and Bundler so we can scan the Gemfile
|
569
|
+
["rubygems", "bundler"].each do |lib|
|
570
|
+
begin
|
571
|
+
require lib
|
572
|
+
rescue LoadError
|
573
|
+
Ocran.fatal_error "Couldn't scan Gemfile, unable to load #{lib}"
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
ENV["BUNDLE_GEMFILE"] = Ocran.gemfile
|
578
|
+
Bundler.load.specs.each do |spec|
|
579
|
+
Ocran.verbose_msg "From Gemfile, adding gem #{spec.full_name}"
|
580
|
+
gems[spec.name] ||= spec
|
581
|
+
end
|
582
|
+
|
583
|
+
unless gems.any? { |name, spec| name == "bundler" }
|
584
|
+
# Bundler itself wasn't added for some reason, let's put it in directly
|
585
|
+
Ocran.verbose_msg "From Gemfile, forcing inclusion of bundler gem itself"
|
586
|
+
bundler_spec = Gem.loaded_specs["bundler"]
|
587
|
+
bundler_spec or Ocran.fatal_error "Unable to locate bundler gem"
|
588
|
+
gems["bundler"] ||= spec
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
if defined?(Gem)
|
593
|
+
# Include Gems that are loaded
|
594
|
+
Gem.loaded_specs.each { |gemname, spec| gems[gemname] ||= spec }
|
595
|
+
# Fall back to gem detection (loaded_specs are not population on
|
596
|
+
# all Ruby versions)
|
597
|
+
features.each do |feature|
|
598
|
+
# Detect load path unless absolute
|
599
|
+
if not feature.absolute?
|
600
|
+
feature = find_load_path(Pathname($:), feature)
|
601
|
+
next if feature.nil? # Could be enumerator.so
|
602
|
+
end
|
603
|
+
# Skip if found in known Gem dir
|
604
|
+
if gems.find { |gem, spec| feature.subpath?(spec.gem_dir) }
|
605
|
+
features_from_gems << feature
|
606
|
+
next
|
607
|
+
end
|
608
|
+
gempaths = Pathname(Gem.path)
|
609
|
+
gempaths.each do |gempath|
|
610
|
+
geminstallpath = Pathname(gempath) / "gems"
|
611
|
+
if feature.subpath?(geminstallpath)
|
612
|
+
gemlocalpath = feature.relative_path_from(geminstallpath)
|
613
|
+
fullgemname = gemlocalpath.path.split("/").first
|
614
|
+
gemspecpath = gempath / "specifications" / "#{fullgemname}.gemspec"
|
615
|
+
if spec = Gem::Specification.load(gemspecpath)
|
616
|
+
gems[spec.name] ||= spec
|
617
|
+
features_from_gems << feature
|
618
|
+
else
|
619
|
+
Ocran.warn "Failed to load gemspec for '#{fullgemname}'"
|
620
|
+
end
|
621
|
+
end
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
gem_files = []
|
626
|
+
|
627
|
+
gems.each do |gemname, spec|
|
628
|
+
if File.exist?(spec.spec_file) then
|
629
|
+
@gemspecs << Pathname(spec.spec_file)
|
630
|
+
else
|
631
|
+
spec_name = File.basename(spec.spec_file)
|
632
|
+
spec_path = File.dirname(spec.spec_file)
|
633
|
+
default_spec_file = spec_path + "/default/" + spec_name
|
634
|
+
if File.exist?(default_spec_file) then
|
635
|
+
@gemspecs << Pathname(default_spec_file)
|
636
|
+
Ocran.msg "Using default specification #{default_spec_file} for gem #{spec.full_name}"
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
# Determine which set of files to include for this particular gem
|
641
|
+
include = [:loaded, :files]
|
642
|
+
Ocran.gem.each do |negate, option, list|
|
643
|
+
if list.nil? or list.include?(spec.name)
|
644
|
+
case option
|
645
|
+
when :minimal
|
646
|
+
include = [:loaded]
|
647
|
+
when :guess
|
648
|
+
include = [:loaded, :files]
|
649
|
+
when :all
|
650
|
+
include = [:scripts, :files]
|
651
|
+
when :full
|
652
|
+
include = [:scripts, :files, :extras]
|
653
|
+
when :spec
|
654
|
+
include = [:spec]
|
655
|
+
when :scripts
|
656
|
+
if negate
|
657
|
+
include.delete(:scripts)
|
658
|
+
else
|
659
|
+
include.push(:scripts)
|
660
|
+
end
|
661
|
+
when :files
|
662
|
+
if negate
|
663
|
+
include.delete(:files)
|
664
|
+
else
|
665
|
+
include.push(:files)
|
666
|
+
end
|
667
|
+
when :extras
|
668
|
+
if negate
|
669
|
+
include.delete(:extras)
|
670
|
+
else
|
671
|
+
include.push(:extras)
|
672
|
+
end
|
673
|
+
end
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
Ocran.msg "Detected gem #{spec.full_name} (#{include.join(", ")})"
|
678
|
+
|
679
|
+
gem_root = Pathname(spec.gem_dir)
|
680
|
+
gem_extension = (gem_root / ".." / ".." / "extensions").expand
|
681
|
+
if gem_extension.exist?
|
682
|
+
build_complete = gem_extension.find_all_files(/gem.build_complete/).select { |p| p.dirname.basename.to_s == spec.full_name }
|
683
|
+
else
|
684
|
+
build_complete = nil
|
685
|
+
end
|
686
|
+
gem_root_files = nil
|
687
|
+
files = []
|
688
|
+
|
689
|
+
unless gem_root.directory?
|
690
|
+
Ocran.warn "Gem #{spec.full_name} root folder was not found, skipping"
|
691
|
+
next
|
692
|
+
end
|
693
|
+
|
694
|
+
# Find the selected files
|
695
|
+
include.each do |set|
|
696
|
+
case set
|
697
|
+
when :spec
|
698
|
+
files << Pathname(spec.files)
|
699
|
+
when :loaded
|
700
|
+
files << features_from_gems.select { |feature| feature.subpath?(gem_root) }
|
701
|
+
when :files
|
702
|
+
gem_root_files ||= gem_root.find_all_files(//)
|
703
|
+
files << gem_root_files.select { |path| path.relative_path_from(gem_root) !~ GEM_NON_FILE_RE }
|
704
|
+
files << build_complete if build_complete
|
705
|
+
when :extras
|
706
|
+
gem_root_files ||= gem_root.find_all_files(//)
|
707
|
+
files << gem_root_files.select { |path| path.relative_path_from(gem_root) =~ GEM_EXTRA_RE }
|
708
|
+
when :scripts
|
709
|
+
gem_root_files ||= gem_root.find_all_files(//)
|
710
|
+
files << gem_root_files.select { |path| path.relative_path_from(gem_root) =~ GEM_SCRIPT_RE }
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
files.flatten!
|
715
|
+
actual_files = files.select { |file| file.file? }
|
716
|
+
|
717
|
+
(files - actual_files).each do |missing_file|
|
718
|
+
Ocran.warn "#{missing_file} was not found"
|
719
|
+
end
|
720
|
+
|
721
|
+
total_size = actual_files.inject(0) { |size, path| size + path.size }
|
722
|
+
Ocran.msg "\t#{actual_files.size} files, #{total_size} bytes"
|
723
|
+
|
724
|
+
gem_files += actual_files
|
725
|
+
end
|
726
|
+
gem_files = sort_uniq(gem_files)
|
727
|
+
else
|
728
|
+
gem_files = []
|
729
|
+
end
|
730
|
+
features_from_gems -= gem_files
|
731
|
+
return gem_files, features_from_gems
|
732
|
+
end
|
733
|
+
|
734
|
+
def Ocran.build_exe
|
735
|
+
all_load_paths = $LOAD_PATH.map { |loadpath| Pathname(loadpath).expand }
|
736
|
+
@added_load_paths = ($LOAD_PATH - @load_path_before).map { |loadpath| Pathname(loadpath).expand }
|
737
|
+
working_directory = Pathname.pwd.expand
|
738
|
+
|
739
|
+
restore_environment
|
740
|
+
|
741
|
+
# If the script was run, then detect the features it used
|
742
|
+
if Ocran.run_script
|
743
|
+
# Attempt to autoload libraries before doing anything else.
|
744
|
+
attempt_load_autoload if Ocran.load_autoload
|
745
|
+
end
|
746
|
+
|
747
|
+
# Store the currently loaded files (before we require rbconfig for
|
748
|
+
# our own use).
|
749
|
+
features = $LOADED_FEATURES.map { |feature| Pathname(feature) }
|
750
|
+
|
751
|
+
# Since https://github.com/rubygems/rubygems/commit/cad4cf16cf8fcc637d9da643ef97cf0be2ed63cb
|
752
|
+
# rubygems/core_ext/kernel_require.rb is evaled and thus missing in $LOADED_FEATURES, so we can't find it and need to add it manually
|
753
|
+
features.push(Pathname("rubygems/core_ext/kernel_require.rb"))
|
754
|
+
|
755
|
+
# Find gemspecs to include
|
756
|
+
if defined?(Gem)
|
757
|
+
@gemspecs = Gem.loaded_specs.map { |name, info| Pathname(info.loaded_from) }
|
758
|
+
else
|
759
|
+
@gemspecs = []
|
760
|
+
end
|
761
|
+
|
762
|
+
require "rbconfig"
|
763
|
+
instsitelibdir = Host.sitelibdir.relative_path_from(Host.exec_prefix)
|
764
|
+
|
765
|
+
load_path = []
|
766
|
+
src_load_path = []
|
767
|
+
|
768
|
+
# Find gems files and remove them from features
|
769
|
+
gem_files, features_from_gems = find_gem_files(features)
|
770
|
+
features -= features_from_gems
|
771
|
+
|
772
|
+
# Find the source root and adjust paths
|
773
|
+
src_prefix, src_files = find_src_root(Ocran.files)
|
774
|
+
|
775
|
+
# Include encoding support files
|
776
|
+
if Ocran.enc
|
777
|
+
all_load_paths.each do |path|
|
778
|
+
if path.subpath?(Host.exec_prefix)
|
779
|
+
encpath = path / "enc"
|
780
|
+
if encpath.exist?
|
781
|
+
encfiles = encpath.find_all_files(/\.so$/)
|
782
|
+
size = encfiles.inject(0) { |sum, pn| sum + pn.size }
|
783
|
+
Ocran.msg "Including #{encfiles.size} encoding support files (#{size} bytes, use --no-enc to exclude)"
|
784
|
+
features.push(*encfiles)
|
785
|
+
end
|
786
|
+
end
|
787
|
+
end
|
788
|
+
else
|
789
|
+
Ocran.msg "Not including encoding support files"
|
790
|
+
end
|
791
|
+
|
792
|
+
# Find features and decide where to put them in the temporary
|
793
|
+
# directory layout.
|
794
|
+
libs = []
|
795
|
+
features.each do |feature|
|
796
|
+
path = find_load_path(all_load_paths, feature)
|
797
|
+
if path.nil? || path.expand == Pathname.pwd
|
798
|
+
Ocran.files << feature
|
799
|
+
else
|
800
|
+
if feature.absolute?
|
801
|
+
feature = feature.relative_path_from(path.expand)
|
802
|
+
end
|
803
|
+
fullpath = feature.expand(path)
|
804
|
+
|
805
|
+
if fullpath.subpath?(Host.exec_prefix)
|
806
|
+
# Features found in the Ruby installation are put in the
|
807
|
+
# temporary Ruby installation.
|
808
|
+
libs << [fullpath, fullpath.relative_path_from(Host.exec_prefix)]
|
809
|
+
elsif defined?(Gem) and gemhome = Gem.path.find { |pth| fullpath.subpath?(pth) }
|
810
|
+
# Features found in any other Gem path (e.g. ~/.gems) is put
|
811
|
+
# in a special 'gemhome' folder.
|
812
|
+
targetpath = GEMHOMEDIR / fullpath.relative_path_from(Pathname(gemhome))
|
813
|
+
libs << [fullpath, targetpath]
|
814
|
+
elsif fullpath.subpath?(src_prefix) || path == working_directory
|
815
|
+
# Any feature found inside the src_prefix automatically gets
|
816
|
+
# added as a source file (to go in 'src').
|
817
|
+
Ocran.files << fullpath
|
818
|
+
# Add the load path unless it was added by the script while
|
819
|
+
# running (or we assume that the script can also set it up
|
820
|
+
# correctly when running from the resulting executable).
|
821
|
+
src_load_path << path unless @added_load_paths.include?(path)
|
822
|
+
elsif @added_load_paths.include?(path)
|
823
|
+
# Any feature that exist in a load path added by the script
|
824
|
+
# itself is added as a file to go into the 'src' (src_prefix
|
825
|
+
# will be adjusted below to point to the common parent).
|
826
|
+
Ocran.files << fullpath
|
827
|
+
else
|
828
|
+
# All other feature that can not be resolved go in the the
|
829
|
+
# Ruby sitelibdir. This is automatically in the load path
|
830
|
+
# when Ruby starts.
|
831
|
+
libs << [fullpath, instsitelibdir / feature]
|
832
|
+
end
|
833
|
+
end
|
834
|
+
end
|
835
|
+
|
836
|
+
# Recompute the src_prefix. Files may have been added implicitly
|
837
|
+
# while scanning through features.
|
838
|
+
src_prefix, src_files = find_src_root(Ocran.files)
|
839
|
+
Ocran.files.replace(src_files)
|
840
|
+
|
841
|
+
# Add the load path that are required with the correct path after
|
842
|
+
# src_prefix was adjusted.
|
843
|
+
load_path += src_load_path.map { |loadpath| TEMPDIR_ROOT / SRCDIR / loadpath.relative_path_from(src_prefix) }
|
844
|
+
|
845
|
+
# Decide where to put gem files, either the system gem folder, or
|
846
|
+
# GEMHOME.
|
847
|
+
gem_files.each do |gemfile|
|
848
|
+
if gemfile.subpath?(Host.exec_prefix)
|
849
|
+
libs << [gemfile, gemfile.relative_path_from(Host.exec_prefix)]
|
850
|
+
elsif defined?(Gem) and gemhome = Gem.path.find { |pth| gemfile.subpath?(pth) }
|
851
|
+
targetpath = GEMHOMEDIR / gemfile.relative_path_from(Pathname(gemhome))
|
852
|
+
libs << [gemfile, targetpath]
|
853
|
+
else
|
854
|
+
Ocran.fatal_error "Don't know where to put gemfile #{gemfile}"
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
# If requested, add all ruby standard libraries
|
859
|
+
if Ocran.add_all_core
|
860
|
+
Ocran.msg "Will include all ruby core libraries"
|
861
|
+
@load_path_before.each do |lp|
|
862
|
+
path = Pathname.new(lp)
|
863
|
+
next unless path.to_posix =~
|
864
|
+
/\/(ruby\/(?:site_ruby\/|vendor_ruby\/)?[0-9.]+)\/?$/i
|
865
|
+
subdir = $1
|
866
|
+
Dir["#{lp}/**/*"].each do |f|
|
867
|
+
fpath = Pathname.new(f)
|
868
|
+
next if fpath.directory?
|
869
|
+
tgt = "lib/#{subdir}/#{fpath.relative_path_from(path).to_posix}"
|
870
|
+
libs << [f, tgt]
|
871
|
+
end
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
# Detect additional DLLs
|
876
|
+
dlls = Ocran.autodll ? LibraryDetector.detect_dlls : []
|
877
|
+
|
878
|
+
# Detect external manifests
|
879
|
+
manifests = Host.exec_prefix.find_all_files(/\.manifest$/)
|
880
|
+
|
881
|
+
executable = nil
|
882
|
+
if Ocran.output_override
|
883
|
+
executable = Ocran.output_override
|
884
|
+
else
|
885
|
+
executable = Ocran.files.first.basename.ext(".exe")
|
886
|
+
executable.append_to_filename!("-debug") if Ocran.debug
|
887
|
+
end
|
888
|
+
|
889
|
+
windowed = (Ocran.files.first.ext?(".rbw") || Ocran.force_windows) && !Ocran.force_console
|
890
|
+
|
891
|
+
Ocran.msg "Building #{executable}"
|
892
|
+
target_script = nil
|
893
|
+
OcranBuilder.new(executable, windowed) do |sb|
|
894
|
+
# Add explicitly mentioned files
|
895
|
+
Ocran.msg "Adding user-supplied source files"
|
896
|
+
Ocran.files.each do |file|
|
897
|
+
file = src_prefix / file
|
898
|
+
if file.subpath?(Host.exec_prefix)
|
899
|
+
target = file.relative_path_from(Host.exec_prefix)
|
900
|
+
elsif file.subpath?(src_prefix)
|
901
|
+
target = SRCDIR / file.relative_path_from(src_prefix)
|
902
|
+
else
|
903
|
+
target = SRCDIR / file.basename
|
904
|
+
end
|
905
|
+
|
906
|
+
target_script ||= target
|
907
|
+
|
908
|
+
if file.directory?
|
909
|
+
sb.ensuremkdir(target)
|
910
|
+
else
|
911
|
+
begin
|
912
|
+
sb.createfile(file, target)
|
913
|
+
rescue Errno::ENOENT
|
914
|
+
raise unless file =~ IGNORE_MODULE_NAMES
|
915
|
+
end
|
916
|
+
end
|
917
|
+
end
|
918
|
+
|
919
|
+
# Add the ruby executable and DLL
|
920
|
+
if windowed
|
921
|
+
rubyexe = Host.rubyw_exe
|
922
|
+
else
|
923
|
+
rubyexe = Host.ruby_exe
|
924
|
+
end
|
925
|
+
Ocran.msg "Adding ruby executable #{rubyexe}"
|
926
|
+
sb.createfile(Host.bindir / rubyexe, BINDIR / rubyexe)
|
927
|
+
if Host.libruby_so
|
928
|
+
sb.createfile(Host.bindir / Host.libruby_so, BINDIR / Host.libruby_so)
|
929
|
+
end
|
930
|
+
|
931
|
+
# Add detected DLLs
|
932
|
+
dlls.each do |dll|
|
933
|
+
Ocran.msg "Adding detected DLL #{dll}"
|
934
|
+
if dll.subpath?(Host.exec_prefix)
|
935
|
+
target = dll.relative_path_from(Host.exec_prefix)
|
936
|
+
else
|
937
|
+
target = BINDIR / File.basename(dll)
|
938
|
+
end
|
939
|
+
sb.createfile(dll, target)
|
940
|
+
end
|
941
|
+
|
942
|
+
# Add external manifest files
|
943
|
+
manifests.each do |manifest|
|
944
|
+
Ocran.msg "Adding external manifest #{manifest}"
|
945
|
+
target = manifest.relative_path_from(Host.exec_prefix)
|
946
|
+
sb.createfile(manifest, target)
|
947
|
+
end
|
948
|
+
|
949
|
+
# Add extra DLLs specified on the command line
|
950
|
+
Ocran.extra_dlls.each do |dll|
|
951
|
+
Ocran.msg "Adding supplied DLL #{dll}"
|
952
|
+
sb.createfile(Host.bindir / dll, BINDIR / dll)
|
953
|
+
end
|
954
|
+
|
955
|
+
# Add gemspec files
|
956
|
+
@gemspecs = sort_uniq(@gemspecs)
|
957
|
+
@gemspecs.each do |gemspec|
|
958
|
+
if gemspec.subpath?(Host.exec_prefix)
|
959
|
+
path = gemspec.relative_path_from(Host.exec_prefix)
|
960
|
+
sb.createfile(gemspec, path)
|
961
|
+
elsif defined?(Gem) and gemhome = Pathname(Gem.path.find { |pth| gemspec.subpath?(pth) })
|
962
|
+
path = GEMHOMEDIR / gemspec.relative_path_from(gemhome)
|
963
|
+
sb.createfile(gemspec, path)
|
964
|
+
else
|
965
|
+
Ocran.fatal_error "Gem spec #{gemspec} does not exist in the Ruby installation. Don't know where to put it."
|
966
|
+
end
|
967
|
+
end
|
968
|
+
|
969
|
+
# Add loaded libraries (features, gems)
|
970
|
+
Ocran.msg "Adding library files"
|
971
|
+
libs.each do |path, target|
|
972
|
+
sb.createfile(path, target)
|
973
|
+
end
|
974
|
+
|
975
|
+
# Set environment variable
|
976
|
+
sb.setenv("RUBYOPT", ENV["RUBYOPT"] || "")
|
977
|
+
sb.setenv("RUBYLIB", load_path.map { |path| path.to_native }.uniq.join(";"))
|
978
|
+
|
979
|
+
sb.setenv("GEM_PATH", (TEMPDIR_ROOT / GEMHOMEDIR).to_native)
|
980
|
+
|
981
|
+
# Add the opcode to launch the script
|
982
|
+
extra_arg = Ocran.arg.map { |arg| ' "' + arg.gsub("\"", "\\\"") + '"' }.join
|
983
|
+
installed_ruby_exe = TEMPDIR_ROOT / BINDIR / rubyexe
|
984
|
+
launch_script = (TEMPDIR_ROOT / target_script).to_native
|
985
|
+
sb.postcreateprocess(installed_ruby_exe,
|
986
|
+
"#{rubyexe} \"#{launch_script}\"#{extra_arg}")
|
987
|
+
end
|
988
|
+
|
989
|
+
unless Ocran.inno_script
|
990
|
+
Ocran.msg "Finished building #{executable} (#{File.size(executable)} bytes)"
|
991
|
+
end
|
992
|
+
end
|
993
|
+
|
994
|
+
module LibraryDetector
|
995
|
+
def LibraryDetector.init_fiddle
|
996
|
+
require "fiddle"
|
997
|
+
require "fiddle/types"
|
998
|
+
module_eval {
|
999
|
+
extend Fiddle::Importer
|
1000
|
+
dlload "psapi.dll"
|
1001
|
+
include Fiddle::Win32Types
|
1002
|
+
extern "BOOL EnumProcessModules(HANDLE, HMODULE*, DWORD, DWORD*)"
|
1003
|
+
extend Fiddle::Importer
|
1004
|
+
dlload "kernel32.dll"
|
1005
|
+
include Fiddle::Win32Types
|
1006
|
+
|
1007
|
+
# https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types
|
1008
|
+
# typedef PVOID HANDLE
|
1009
|
+
# typedef HINSTANCE HMODULE;
|
1010
|
+
|
1011
|
+
typealias "HMODULE", "voidp"
|
1012
|
+
typealias "HANDLE", "voidp"
|
1013
|
+
typealias "LPWSTR", "char*"
|
1014
|
+
|
1015
|
+
extern "DWORD GetModuleFileNameW(HMODULE, LPWSTR, DWORD)"
|
1016
|
+
extern "HANDLE GetCurrentProcess(void)"
|
1017
|
+
extern "DWORD GetLastError(void)"
|
1018
|
+
}
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
def LibraryDetector.loaded_dlls
|
1022
|
+
require "fiddle"
|
1023
|
+
psapi = Fiddle.dlopen("psapi")
|
1024
|
+
enumprocessmodules = Fiddle::Function.new(psapi["EnumProcessModules"], [Fiddle::TYPE_UINTPTR_T, Fiddle::TYPE_VOIDP, Fiddle::TYPE_LONG, Fiddle::TYPE_VOIDP], Fiddle::TYPE_LONG)
|
1025
|
+
kernel32 = Fiddle.dlopen("kernel32")
|
1026
|
+
getcurrentprocess = Fiddle::Function.new(kernel32["GetCurrentProcess"], [], Fiddle::TYPE_LONG)
|
1027
|
+
getmodulefilename = Fiddle::Function.new(kernel32["GetModuleFileNameW"], [Fiddle::TYPE_UINTPTR_T, Fiddle::TYPE_VOIDP, Fiddle::TYPE_LONG], Fiddle::TYPE_LONG)
|
1028
|
+
getlasterror = Fiddle::Function.new(kernel32["GetLastError"], [], Fiddle::TYPE_LONG)
|
1029
|
+
|
1030
|
+
# Different packing/unpacking for 64/32 bits systems
|
1031
|
+
if Fiddle::SIZEOF_VOIDP == 8 then
|
1032
|
+
f_single = "Q"
|
1033
|
+
f_array = "Q*"
|
1034
|
+
else
|
1035
|
+
f_single = "I"
|
1036
|
+
f_array = "I*"
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
bytes_needed = Fiddle::SIZEOF_VOIDP * 32
|
1040
|
+
module_handle_buffer = nil
|
1041
|
+
process_handle = getcurrentprocess.call()
|
1042
|
+
loop do
|
1043
|
+
module_handle_buffer = "\x00" * bytes_needed
|
1044
|
+
bytes_needed_buffer = [0].pack(f_single)
|
1045
|
+
r = enumprocessmodules.call(process_handle, module_handle_buffer, module_handle_buffer.size, bytes_needed_buffer)
|
1046
|
+
bytes_needed = bytes_needed_buffer.unpack(f_single)[0]
|
1047
|
+
break if bytes_needed <= module_handle_buffer.size
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
handles = module_handle_buffer.unpack(f_array)
|
1051
|
+
handles.select { |handle| handle > 0 }.map do |handle|
|
1052
|
+
str = "\x00\x00" * 256
|
1053
|
+
modulefilename_length = getmodulefilename.call(handle, str, str.size)
|
1054
|
+
unless modulefilename_length > 0
|
1055
|
+
errorcode = getlasterror.call()
|
1056
|
+
Ocran.fatal_error "LibraryDetector: GetModuleFileNameW failed with error code 0x" + errorcode.to_s(16)
|
1057
|
+
end
|
1058
|
+
modulefilename = str[0, modulefilename_length * 2].force_encoding("UTF-16LE").encode("UTF-8")
|
1059
|
+
Ocran.Pathname(modulefilename)
|
1060
|
+
end
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
def LibraryDetector.detect_dlls
|
1064
|
+
loaded = loaded_dlls
|
1065
|
+
exec_prefix = Host.exec_prefix
|
1066
|
+
loaded.select { |path| path.subpath?(exec_prefix) && path.basename.ext?(".dll") && path.basename != Host.libruby_so }
|
1067
|
+
end
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
# Utility class that produces the actual executable. Opcodes
|
1071
|
+
# (createfile, mkdir etc) are added by invoking methods on an
|
1072
|
+
# instance of OcranBuilder.
|
1073
|
+
class OcranBuilder
|
1074
|
+
Signature = [0x41, 0xb6, 0xba, 0x4e]
|
1075
|
+
OP_END = 0
|
1076
|
+
OP_CREATE_DIRECTORY = 1
|
1077
|
+
OP_CREATE_FILE = 2
|
1078
|
+
OP_CREATE_PROCESS = 3
|
1079
|
+
OP_DECOMPRESS_LZMA = 4
|
1080
|
+
OP_SETENV = 5
|
1081
|
+
OP_POST_CREATE_PROCESS = 6
|
1082
|
+
OP_ENABLE_DEBUG_MODE = 7
|
1083
|
+
OP_CREATE_INST_DIRECTORY = 8
|
1084
|
+
|
1085
|
+
def initialize(path, windowed)
|
1086
|
+
@paths = {}
|
1087
|
+
@files = {}
|
1088
|
+
File.open(path, "wb") do |ocranfile|
|
1089
|
+
image = nil
|
1090
|
+
if windowed
|
1091
|
+
image = Ocran.stubwimage
|
1092
|
+
else
|
1093
|
+
image = Ocran.stubimage
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
unless image
|
1097
|
+
Ocran.fatal_error "Stub image not available"
|
1098
|
+
end
|
1099
|
+
ocranfile.write(image)
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
if Ocran.icon_filename
|
1103
|
+
system Ocran.ediconpath, path, Ocran.icon_filename
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
opcode_offset = File.size(path)
|
1107
|
+
|
1108
|
+
File.open(path, "ab") do |ocranfile|
|
1109
|
+
tmpinpath = "tmpin"
|
1110
|
+
|
1111
|
+
if Ocran.lzma_mode
|
1112
|
+
@of = File.open(tmpinpath, "wb")
|
1113
|
+
else
|
1114
|
+
@of = ocranfile
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
if Ocran.debug
|
1118
|
+
Ocran.msg("Enabling debug mode in executable")
|
1119
|
+
ocranfile.write([OP_ENABLE_DEBUG_MODE].pack("V"))
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
createinstdir Ocran.debug_extract, !Ocran.debug_extract, Ocran.chdir_first
|
1123
|
+
|
1124
|
+
yield(self)
|
1125
|
+
|
1126
|
+
@of.close if Ocran.lzma_mode
|
1127
|
+
|
1128
|
+
if Ocran.lzma_mode and not Ocran.inno_script
|
1129
|
+
tmpoutpath = "tmpout"
|
1130
|
+
begin
|
1131
|
+
data_size = File.size(tmpinpath)
|
1132
|
+
Ocran.msg "Compressing #{data_size} bytes"
|
1133
|
+
system(Ocran.lzmapath, "e", tmpinpath, tmpoutpath) or fail
|
1134
|
+
compressed_data_size = File.size?(tmpoutpath)
|
1135
|
+
ocranfile.write([OP_DECOMPRESS_LZMA, compressed_data_size].pack("VV"))
|
1136
|
+
IO.copy_stream(tmpoutpath, ocranfile)
|
1137
|
+
ensure
|
1138
|
+
File.unlink(@of.path) if File.exist?(@of.path)
|
1139
|
+
File.unlink(tmpoutpath) if File.exist?(tmpoutpath)
|
1140
|
+
end
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
ocranfile.write([OP_END].pack("V"))
|
1144
|
+
ocranfile.write([opcode_offset].pack("V")) # Pointer to start of opcodes
|
1145
|
+
ocranfile.write(Signature.pack("C*"))
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
if Ocran.inno_script
|
1149
|
+
begin
|
1150
|
+
iss = File.read(Ocran.inno_script) + "\n\n"
|
1151
|
+
|
1152
|
+
iss << "[Dirs]\n"
|
1153
|
+
@paths.each_key do |p|
|
1154
|
+
iss << "Name: \"{app}/#{p}\"\n"
|
1155
|
+
end
|
1156
|
+
iss << "\n"
|
1157
|
+
|
1158
|
+
iss << "[Files]\n"
|
1159
|
+
path_escaped = path.to_s.gsub('"', '""')
|
1160
|
+
iss << "Source: \"#{path_escaped}\"; DestDir: \"{app}\"\n"
|
1161
|
+
@files.each do |tgt, src|
|
1162
|
+
src_escaped = src.to_s.gsub('"', '""')
|
1163
|
+
target_dir_escaped = Pathname.new(tgt).dirname.to_s.gsub('"', '""')
|
1164
|
+
iss << "Source: \"#{src_escaped}\"; DestDir: \"{app}/#{target_dir_escaped}\"\n" unless src_escaped =~ IGNORE_MODULE_NAMES
|
1165
|
+
end
|
1166
|
+
iss << "\n"
|
1167
|
+
|
1168
|
+
Ocran.verbose_msg "### INNOSETUP SCRIPT ###\n\n#{iss}\n\n"
|
1169
|
+
|
1170
|
+
f = File.open("ocrantemp.iss", "w")
|
1171
|
+
f.write(iss)
|
1172
|
+
f.close()
|
1173
|
+
|
1174
|
+
iscc_cmd = ["iscc"]
|
1175
|
+
iscc_cmd << "/Q" unless Ocran.verbose
|
1176
|
+
iscc_cmd << "ocrantemp.iss"
|
1177
|
+
Ocran.msg "Running InnoSetup compiler ISCC"
|
1178
|
+
result = system(*iscc_cmd)
|
1179
|
+
if not result
|
1180
|
+
case $?
|
1181
|
+
when 0 then raise RuntimeError.new("ISCC reported success, but system reported error?")
|
1182
|
+
when 1 then raise RuntimeError.new("ISCC reports invalid command line parameters")
|
1183
|
+
when 2 then raise RuntimeError.new("ISCC reports that compilation failed")
|
1184
|
+
else raise RuntimeError.new("ISCC failed to run. Is the InnoSetup directory in your PATH?")
|
1185
|
+
end
|
1186
|
+
end
|
1187
|
+
rescue Exception => e
|
1188
|
+
Ocran.fatal_error("InnoSetup installer creation failed: #{e.message}")
|
1189
|
+
ensure
|
1190
|
+
File.unlink("ocrantemp.iss") if File.exist?("ocrantemp.iss")
|
1191
|
+
File.unlink(path) if File.exist?(path)
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
def mkdir(path)
|
1197
|
+
return if @paths[path.path.downcase]
|
1198
|
+
@paths[path.path.downcase] = true
|
1199
|
+
Ocran.verbose_msg "m #{showtempdir path}"
|
1200
|
+
unless Ocran.inno_script # The directory will be created by InnoSetup with a [Dirs] statement
|
1201
|
+
@of << [OP_CREATE_DIRECTORY, path.to_native].pack("VZ*")
|
1202
|
+
end
|
1203
|
+
end
|
1204
|
+
|
1205
|
+
def ensuremkdir(tgt)
|
1206
|
+
tgt = Ocran.Pathname(tgt)
|
1207
|
+
return if tgt.path == "."
|
1208
|
+
if not @paths[tgt.to_posix.downcase]
|
1209
|
+
ensuremkdir(tgt.dirname)
|
1210
|
+
mkdir(tgt)
|
1211
|
+
end
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
def createinstdir(next_to_exe = false, delete_after = false, chdir_before = false)
|
1215
|
+
unless Ocran.inno_script # Creation of installation directory will be handled by InnoSetup
|
1216
|
+
@of << [OP_CREATE_INST_DIRECTORY, next_to_exe ? 1 : 0, delete_after ? 1 : 0, chdir_before ? 1 : 0].pack("VVVV")
|
1217
|
+
end
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
def createfile(src, tgt)
|
1221
|
+
return if @files[tgt]
|
1222
|
+
@files[tgt] = src
|
1223
|
+
src, tgt = Ocran.Pathname(src), Ocran.Pathname(tgt)
|
1224
|
+
ensuremkdir(tgt.dirname)
|
1225
|
+
str = File.open(src, "rb") { |file| file.read }
|
1226
|
+
Ocran.verbose_msg "a #{showtempdir tgt}"
|
1227
|
+
unless Ocran.inno_script # InnoSetup will install the file with a [Files] statement
|
1228
|
+
@of << [OP_CREATE_FILE, tgt.to_native, str.size].pack("VZ*V")
|
1229
|
+
@of << str
|
1230
|
+
end
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
def createprocess(image, cmdline)
|
1234
|
+
Ocran.verbose_msg "l #{showtempdir image} #{showtempdir cmdline}"
|
1235
|
+
@of << [OP_CREATE_PROCESS, image.to_native, cmdline].pack("VZ*Z*")
|
1236
|
+
end
|
1237
|
+
|
1238
|
+
def postcreateprocess(image, cmdline)
|
1239
|
+
Ocran.verbose_msg "p #{showtempdir image} #{showtempdir cmdline}"
|
1240
|
+
@of << [OP_POST_CREATE_PROCESS, image.to_native, cmdline].pack("VZ*Z*")
|
1241
|
+
end
|
1242
|
+
|
1243
|
+
def setenv(name, value)
|
1244
|
+
Ocran.verbose_msg "e #{name} #{showtempdir value}"
|
1245
|
+
@of << [OP_SETENV, name, value].pack("VZ*Z*")
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
def close
|
1249
|
+
@of.close
|
1250
|
+
end
|
1251
|
+
|
1252
|
+
def showtempdir(x)
|
1253
|
+
x.to_s.gsub(TEMPDIR_ROOT, "<tempdir>")
|
1254
|
+
end
|
1255
|
+
end # class OcranBuilder
|
1256
|
+
end # module Ocran
|
1257
|
+
|
1258
|
+
if File.basename(__FILE__) == File.basename($0)
|
1259
|
+
Ocran.init(ARGV)
|
1260
|
+
ARGV.replace(Ocran.arg)
|
1261
|
+
|
1262
|
+
if not Ocran.files.first.exist?
|
1263
|
+
Ocran.fatal_error "#{Ocran.files[0]} was not found!"
|
1264
|
+
end
|
1265
|
+
|
1266
|
+
at_exit do
|
1267
|
+
if $!.nil? or $!.kind_of?(SystemExit)
|
1268
|
+
Ocran.build_exe
|
1269
|
+
exit 0
|
1270
|
+
end
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
if Ocran.run_script
|
1274
|
+
Ocran.msg "Loading script to check dependencies"
|
1275
|
+
$0 = Ocran.files.first
|
1276
|
+
load Ocran.files.first
|
1277
|
+
end
|
1278
|
+
end
|
data/lib/ocran.rb
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ocran
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.12
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andi Idogawa
|
8
|
+
- Lars Christensen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2023-05-16 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: OCRAN (One-Click Ruby Application Next) builds Windows executables from
|
15
|
+
Ruby source code. The executable is a self-extracting, self-running executable that
|
16
|
+
contains the Ruby interpreter, your source code and any additionally needed ruby
|
17
|
+
libraries or DLL.
|
18
|
+
email:
|
19
|
+
- andi@idogawa.com
|
20
|
+
executables:
|
21
|
+
- ocran
|
22
|
+
extensions: []
|
23
|
+
extra_rdoc_files: []
|
24
|
+
files:
|
25
|
+
- bin/ocran
|
26
|
+
- lib/ocran.rb
|
27
|
+
- share/ocran/edicon.exe
|
28
|
+
- share/ocran/lzma.exe
|
29
|
+
- share/ocran/stub.exe
|
30
|
+
- share/ocran/stubw.exe
|
31
|
+
homepage: https://github.com/largo/ocran
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
metadata:
|
35
|
+
homepage_uri: https://github.com/largo/ocran
|
36
|
+
source_code_uri: https://github.com/largo/ocran
|
37
|
+
changelog_uri: https://github.com/largo/ocran/History.txt
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.0.0
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubygems_version: 3.4.13
|
54
|
+
signing_key:
|
55
|
+
specification_version: 4
|
56
|
+
summary: OCRAN (One-Click Ruby Application Next) builds Windows executables from Ruby
|
57
|
+
source code.
|
58
|
+
test_files: []
|