ocran 1.4.5 → 1.5.0
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 +4 -4
- data/CHANGELOG.txt +74 -0
- data/README.md +296 -9
- data/ext/extconf.rb +5 -1
- data/lib/ocran/aot_toolchain.rb +154 -0
- data/lib/ocran/command_output.rb +8 -1
- data/lib/ocran/compatibility_scan.rb +414 -0
- data/lib/ocran/cosmo_toolchain.rb +29 -7
- data/lib/ocran/dir_builder.rb +88 -38
- data/lib/ocran/direction.rb +232 -33
- data/lib/ocran/ed_resource.rb +113 -0
- data/lib/ocran/ed_resource_builder.rb +199 -0
- data/lib/ocran/file_path_set.rb +9 -5
- data/lib/ocran/gem_spec_queryable.rb +7 -3
- data/lib/ocran/host_config_helper.rb +11 -1
- data/lib/ocran/inno_setup_script_builder.rb +24 -14
- data/lib/ocran/launcher_batch_builder.rb +8 -11
- data/lib/ocran/npm_package.rb +106 -0
- data/lib/ocran/option.rb +433 -35
- data/lib/ocran/pe_imports.rb +96 -0
- data/lib/ocran/pe_resources.rb +226 -0
- data/lib/ocran/refine_pathname.rb +29 -32
- data/lib/ocran/roundhouse_builder.rb +316 -0
- data/lib/ocran/runner.rb +96 -11
- data/lib/ocran/runtime_environment.rb +14 -6
- data/lib/ocran/runtime_sdk.rb +252 -0
- data/lib/ocran/spinel_builder.rb +183 -0
- data/lib/ocran/spinel_compatibility.rb +149 -0
- data/lib/ocran/static_runtime/boot.rb +443 -0
- data/lib/ocran/static_runtime/build_static_ext.rb +86 -0
- data/lib/ocran/static_runtime_builder.rb +814 -0
- data/lib/ocran/stub_builder.rb +70 -25
- data/lib/ocran/version.rb +1 -1
- data/lib/ocran/wasm_builder.rb +570 -0
- data/lib/ocran/wasm_compatibility.rb +198 -0
- data/lib/ocran/windows_command_escaping.rb +21 -0
- data/lib/ocran/zip_payload_builder.rb +18 -9
- data/lib/ocran/zip_writer.rb +48 -9
- data/src/Makefile +18 -2
- data/src/Makefile.mswin +65 -0
- data/src/inst_dir.c +34 -6
- data/src/path_utils.c +144 -0
- data/src/script_info.c +5 -0
- data/src/stub.c +77 -19
- data/src/stub.rc +1 -1
- data/src/system_utils.c +276 -155
- data/src/system_utils.h +46 -3
- data/src/system_utils_posix.c +243 -148
- data/src/unpack.c +9 -0
- metadata +23 -5
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# ocran static runtime, stage 1 (ocran/boot.rb inside the archive).
|
|
2
|
+
#
|
|
3
|
+
# Serves the archive appended to the executable as a read-only directory
|
|
4
|
+
# tree rooted at the executable's own path, e.g. C:/apps/tool.exe/src/app.rb.
|
|
5
|
+
# A path below an existing *file* can never exist on disk, so archive paths
|
|
6
|
+
# cannot be shadowed by planted files, and anything this layer does not
|
|
7
|
+
# handle fails loudly (ENOTDIR) instead of silently reading something else.
|
|
8
|
+
#
|
|
9
|
+
# Intercepted, and nothing else:
|
|
10
|
+
# Kernel: require, require_relative, load, open (autoload goes through
|
|
11
|
+
# require), and gem_original_require for RubyGems
|
|
12
|
+
# IO/File: read, binread, readlines, foreach, open, File.new
|
|
13
|
+
# File: exist? file? directory? readable? size size? zero? empty?
|
|
14
|
+
# mtime ftype stat lstat realpath realdirpath symlink? executable?
|
|
15
|
+
# Dir: exist? glob [] children entries each_child foreach empty?
|
|
16
|
+
# Writes to archive paths raise Errno::EROFS.
|
|
17
|
+
module OcranRuntime
|
|
18
|
+
ROOT = EXE # archive members live at "#{ROOT}/<name>"
|
|
19
|
+
PREFIX = "#{ROOT}/".freeze
|
|
20
|
+
# Read now: once File is patched, File.mtime(EXE) is an archive lookup
|
|
21
|
+
# that would end up here again.
|
|
22
|
+
MTIME = File.mtime(EXE)
|
|
23
|
+
|
|
24
|
+
module VirtualFS
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# Member names of files; "dir/" entries only contribute to dirs.
|
|
28
|
+
def files
|
|
29
|
+
@files ||= OcranRuntime.entries.keys.reject { |n| n.end_with?("/") }.freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def dirs
|
|
33
|
+
@dirs ||= begin
|
|
34
|
+
set = { ROOT => true }
|
|
35
|
+
OcranRuntime.entries.each_key do |name|
|
|
36
|
+
parts = name.split("/")
|
|
37
|
+
parts.pop unless name.end_with?("/")
|
|
38
|
+
parts.each_index { |i| set["#{ROOT}/#{parts[0..i].join("/")}"] = true }
|
|
39
|
+
end
|
|
40
|
+
set.freeze
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def paths
|
|
45
|
+
@paths ||= (files.map { |n| "#{ROOT}/#{n}" } + dirs.keys).sort.freeze
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Absolute virtual path for +path+, or nil when it is not an archive path.
|
|
49
|
+
def vpath(path)
|
|
50
|
+
path = path.to_path if path.respond_to?(:to_path)
|
|
51
|
+
return nil unless path.is_a?(String)
|
|
52
|
+
path = File.expand_path(path)
|
|
53
|
+
path.start_with?(PREFIX) || path == ROOT ? path : nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def file?(path) = (v = vpath(path)) && v != ROOT ? OcranRuntime.entries.key?(v.delete_prefix(PREFIX)) : false
|
|
57
|
+
def directory?(path) = (v = vpath(path)) ? dirs.key?(v) : false
|
|
58
|
+
def exist?(path) = file?(path) || directory?(path)
|
|
59
|
+
|
|
60
|
+
def read(path)
|
|
61
|
+
v = vpath(path)
|
|
62
|
+
raise Errno::EISDIR, path.to_s if v && dirs.key?(v)
|
|
63
|
+
data = v && OcranRuntime.read(v.delete_prefix(PREFIX))
|
|
64
|
+
raise Errno::ENOENT, path.to_s unless data
|
|
65
|
+
data
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def children(path)
|
|
69
|
+
v = vpath(path)
|
|
70
|
+
raise Errno::ENOENT, path.to_s unless v && dirs.key?(v)
|
|
71
|
+
prefix = "#{v}/"
|
|
72
|
+
paths.filter_map { |p| p.delete_prefix(prefix).split("/").first if p.start_with?(prefix) }.uniq
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def glob(pattern, flags, base)
|
|
76
|
+
pattern = File.join(File.expand_path(base), pattern) if base
|
|
77
|
+
flags |= File::FNM_PATHNAME | File::FNM_EXTGLOB
|
|
78
|
+
found = paths.select { |p| File.fnmatch?(pattern, p, flags) }
|
|
79
|
+
base ? found.map { |p| p.delete_prefix("#{File.expand_path(base)}/") } : found
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def stat(path)
|
|
83
|
+
raise Errno::ENOENT, path.to_s unless exist?(path)
|
|
84
|
+
e = OcranRuntime.entries[vpath(path).delete_prefix(PREFIX)]
|
|
85
|
+
Stat.new(e ? e.size : 0, e.nil?)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The subset of File::Stat that libraries ask about.
|
|
89
|
+
Stat = Struct.new(:size, :directory?) do
|
|
90
|
+
def file? = !directory?
|
|
91
|
+
def ftype = directory? ? "directory" : "file"
|
|
92
|
+
def mtime = MTIME
|
|
93
|
+
alias_method :ctime, :mtime
|
|
94
|
+
alias_method :atime, :mtime
|
|
95
|
+
def readable? = true
|
|
96
|
+
def writable? = false
|
|
97
|
+
def executable? = false
|
|
98
|
+
def symlink? = false
|
|
99
|
+
def zero? = size.zero?
|
|
100
|
+
def size? = size.zero? ? nil : size
|
|
101
|
+
def mode = directory? ? 0o40555 : 0o100444
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def writing?(mode, opts)
|
|
105
|
+
mode = opts[:mode] || mode
|
|
106
|
+
case mode
|
|
107
|
+
when String then mode.match?(/[wa+]/)
|
|
108
|
+
when Integer then mode & (File::WRONLY | File::RDWR | File::CREAT) != 0
|
|
109
|
+
else false
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def contents(path, mode, opts)
|
|
114
|
+
data = read(path)
|
|
115
|
+
mode = (opts[:mode] || mode).to_s
|
|
116
|
+
enc = opts[:encoding] || opts[:external_encoding] || mode[/:([^:]+)/, 1]
|
|
117
|
+
if opts[:binmode] || mode.include?("b") && enc.nil?
|
|
118
|
+
data.force_encoding(Encoding::BINARY)
|
|
119
|
+
else
|
|
120
|
+
data.force_encoding(enc ? Encoding.find(enc.to_s.sub(/\Abom\|/i, "")) : Encoding.default_external)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def open_io(path, mode, opts)
|
|
125
|
+
raise Errno::EROFS, path.to_s if writing?(mode, opts)
|
|
126
|
+
|
|
127
|
+
ArchivedFile.new(contents(path, mode, opts), vpath(path))
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# ---- require / load -------------------------------------------------
|
|
132
|
+
@lock = Thread::Mutex.new
|
|
133
|
+
@loaded = {}
|
|
134
|
+
|
|
135
|
+
class << self
|
|
136
|
+
attr_reader :config
|
|
137
|
+
|
|
138
|
+
def synchronize(&)
|
|
139
|
+
@lock.owned? ? yield : @lock.synchronize(&)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def compile(path)
|
|
143
|
+
src = VirtualFS.read(path).force_encoding(Encoding::UTF_8)
|
|
144
|
+
RubyVM::InstructionSequence.compile(src, path, path, 1)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def load_file(path, wrap)
|
|
148
|
+
if wrap
|
|
149
|
+
mod = wrap.is_a?(Module) ? wrap : Module.new
|
|
150
|
+
mod.module_eval(VirtualFS.read(path).force_encoding(Encoding::UTF_8), path, 1)
|
|
151
|
+
else
|
|
152
|
+
compile(path).eval
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Absolute archive path for +feature+, or nil to let Ruby handle it
|
|
157
|
+
# (files on disk and the statically linked extensions).
|
|
158
|
+
def resolve(feature)
|
|
159
|
+
feature = feature.to_path if feature.respond_to?(:to_path)
|
|
160
|
+
feature = feature.to_str
|
|
161
|
+
ext = File.extname(feature)
|
|
162
|
+
return nil if ext == ".so" || ext == ".dll"
|
|
163
|
+
name = ext == ".rb" ? feature : "#{feature}.rb"
|
|
164
|
+
if File.absolute_path?(feature) || feature.start_with?("./", "../", "~")
|
|
165
|
+
path = File.expand_path(name)
|
|
166
|
+
return VirtualFS.file?(path) ? path : nil
|
|
167
|
+
end
|
|
168
|
+
$LOAD_PATH.each do |dir|
|
|
169
|
+
dir = VirtualFS.vpath(dir) or next
|
|
170
|
+
path = "#{dir}/#{name}"
|
|
171
|
+
return path if VirtualFS.file?(path)
|
|
172
|
+
end
|
|
173
|
+
nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Load-path-relative names an absolute archive path may be required by:
|
|
177
|
+
# C:/app.exe/lib/ruby/gems/4.0.0/gems/nokogiri-1.19.4/lib/nokogiri/nokogiri
|
|
178
|
+
# is nokogiri/nokogiri.
|
|
179
|
+
def linked_feature_candidates(feature)
|
|
180
|
+
feature = feature.to_path if feature.respond_to?(:to_path)
|
|
181
|
+
return [] unless feature.is_a?(String) && File.absolute_path?(feature)
|
|
182
|
+
|
|
183
|
+
path = VirtualFS.vpath(feature) or return []
|
|
184
|
+
path = path.sub(/\.(so|dll)\z/, "")
|
|
185
|
+
$LOAD_PATH.filter_map do |dir|
|
|
186
|
+
dir = VirtualFS.vpath(dir) or next
|
|
187
|
+
path.delete_prefix("#{dir}/") if path.start_with?("#{dir}/")
|
|
188
|
+
end.uniq
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def require_virtual(path)
|
|
192
|
+
synchronize do
|
|
193
|
+
return false if @loaded[path] || $LOADED_FEATURES.include?(path)
|
|
194
|
+
# Recorded as loaded *before* evaluation: a circular require then
|
|
195
|
+
# returns false, and an autoload being satisfied by this file sees
|
|
196
|
+
# its feature as provided (MRI's own require uses an internal
|
|
197
|
+
# loading table for that, which Ruby code cannot reach).
|
|
198
|
+
@loaded[path] = true
|
|
199
|
+
$LOADED_FEATURES << path
|
|
200
|
+
begin
|
|
201
|
+
compile(path).eval
|
|
202
|
+
rescue Exception
|
|
203
|
+
@loaded.delete(path)
|
|
204
|
+
$LOADED_FEATURES.delete(path)
|
|
205
|
+
raise
|
|
206
|
+
end
|
|
207
|
+
true
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Kernel's loading methods, defined in Kernel itself the way RubyGems
|
|
213
|
+
# defines its require - not prepended: a later redefinition (RubyGems,
|
|
214
|
+
# Zeitwerk) must see these as Kernel's own methods to chain to them, and
|
|
215
|
+
# Ruby does not invalidate a prepended module's cached super target when
|
|
216
|
+
# the method it overrides is redefined.
|
|
217
|
+
RUBY_REQUIRE = ::Kernel.instance_method(:require)
|
|
218
|
+
RUBY_LOAD = ::Kernel.instance_method(:load)
|
|
219
|
+
RUBY_OPEN = ::Kernel.instance_method(:open)
|
|
220
|
+
|
|
221
|
+
def self.install_kernel_methods
|
|
222
|
+
::Kernel.module_eval do
|
|
223
|
+
# Ruby's require, able to read the archive. RubyGems' Kernel#require
|
|
224
|
+
# ends in gem_original_require and keeps one that already exists.
|
|
225
|
+
def gem_original_require(feature)
|
|
226
|
+
archived = OcranRuntime.resolve(feature)
|
|
227
|
+
return OcranRuntime.require_virtual(archived) if archived
|
|
228
|
+
|
|
229
|
+
OcranRuntime::RUBY_REQUIRE.bind_call(self, feature)
|
|
230
|
+
rescue LoadError => e
|
|
231
|
+
# require_relative of a C extension (nokogiri, tiny_tds) asks for an
|
|
232
|
+
# absolute path inside the archive; it is linked into the runtime
|
|
233
|
+
# under its feature name, relative to the load path.
|
|
234
|
+
OcranRuntime.linked_feature_candidates(feature).each do |linked|
|
|
235
|
+
return OcranRuntime::RUBY_REQUIRE.bind_call(self, linked)
|
|
236
|
+
rescue LoadError
|
|
237
|
+
next
|
|
238
|
+
end
|
|
239
|
+
raise e
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def require(feature) = gem_original_require(feature)
|
|
243
|
+
|
|
244
|
+
# Ruby's own require_relative resolves against its caller, which
|
|
245
|
+
# here would be this file; it is a require of the absolute path.
|
|
246
|
+
def require_relative(feature)
|
|
247
|
+
base = caller_locations(1, 1).first&.absolute_path
|
|
248
|
+
raise LoadError, "cannot infer basepath" unless base
|
|
249
|
+
|
|
250
|
+
require(File.expand_path(feature, File.dirname(base)))
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def load(file, wrap = false)
|
|
254
|
+
return OcranRuntime::RUBY_LOAD.bind_call(self, file, wrap) unless VirtualFS.file?(file)
|
|
255
|
+
|
|
256
|
+
OcranRuntime.load_file(VirtualFS.vpath(file), wrap)
|
|
257
|
+
true
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def open(path, *args, **opts, &block)
|
|
261
|
+
return OcranRuntime::RUBY_OPEN.bind_call(self, path, *args, **opts, &block) unless VirtualFS.vpath(path)
|
|
262
|
+
|
|
263
|
+
File.open(path, *args, **opts, &block)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
private :gem_original_require, :require, :require_relative, :load, :open
|
|
267
|
+
end
|
|
268
|
+
# Kernel.require and friends, called with an explicit receiver.
|
|
269
|
+
%i[require require_relative load open].each do |name|
|
|
270
|
+
::Kernel.define_singleton_method(name) { |*args, **kw, &block| Object.new.__send__(name, *args, **kw, &block) }
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
module IOPatch
|
|
275
|
+
def read(path, *args, **opts)
|
|
276
|
+
return super unless VirtualFS.vpath(path)
|
|
277
|
+
data = VirtualFS.contents(path, "r", opts)
|
|
278
|
+
length, offset = args
|
|
279
|
+
offset ? data.byteslice(offset, length || data.bytesize) : length ? data.byteslice(0, length) : data
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def binread(path, *args)
|
|
283
|
+
return super unless VirtualFS.vpath(path)
|
|
284
|
+
read(path, *args, mode: "rb")
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def readlines(path, *args, chomp: false, **opts)
|
|
288
|
+
return super unless VirtualFS.vpath(path)
|
|
289
|
+
VirtualFS.contents(path, "r", opts).lines(*args, chomp: chomp)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def foreach(path, *args, chomp: false, **opts, &block)
|
|
293
|
+
return super unless VirtualFS.vpath(path)
|
|
294
|
+
lines = VirtualFS.contents(path, "r", opts).each_line(*args, chomp: chomp)
|
|
295
|
+
block ? lines.each(&block) && nil : lines
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def open(path, *args, **opts)
|
|
299
|
+
return super unless self <= File && VirtualFS.vpath(path)
|
|
300
|
+
io = VirtualFS.open_io(path, args.first, opts)
|
|
301
|
+
return io unless block_given?
|
|
302
|
+
begin
|
|
303
|
+
yield io
|
|
304
|
+
ensure
|
|
305
|
+
io.close
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
module FilePatch
|
|
311
|
+
def new(path, *args, **opts)
|
|
312
|
+
return super unless VirtualFS.vpath(path)
|
|
313
|
+
|
|
314
|
+
VirtualFS.open_io(path, args.first, opts)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
%i[exist? file? directory?].each do |m|
|
|
318
|
+
define_method(m) { |path| VirtualFS.vpath(path) ? VirtualFS.__send__(m, path) : super(path) }
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def readable?(path) = VirtualFS.vpath(path) ? VirtualFS.exist?(path) : super
|
|
322
|
+
def symlink?(path) = VirtualFS.vpath(path) ? false : super
|
|
323
|
+
def executable?(path) = VirtualFS.vpath(path) ? false : super
|
|
324
|
+
def size(path) = VirtualFS.vpath(path) ? VirtualFS.stat(path).size : super
|
|
325
|
+
def size?(path) = VirtualFS.vpath(path) ? (VirtualFS.exist?(path) ? VirtualFS.stat(path).size? : nil) : super
|
|
326
|
+
def zero?(path) = VirtualFS.vpath(path) ? VirtualFS.file?(path) && VirtualFS.stat(path).zero? : super
|
|
327
|
+
alias_method :empty?, :zero?
|
|
328
|
+
def mtime(path) = VirtualFS.vpath(path) ? VirtualFS.stat(path).mtime : super
|
|
329
|
+
def ftype(path) = VirtualFS.vpath(path) ? VirtualFS.stat(path).ftype : super
|
|
330
|
+
def stat(path) = VirtualFS.vpath(path) ? VirtualFS.stat(path) : super
|
|
331
|
+
alias_method :lstat, :stat
|
|
332
|
+
|
|
333
|
+
def realpath(path, dir = nil)
|
|
334
|
+
v = VirtualFS.vpath(dir ? File.expand_path(path, dir) : path)
|
|
335
|
+
return super unless v
|
|
336
|
+
raise Errno::ENOENT, path.to_s unless VirtualFS.exist?(v)
|
|
337
|
+
v
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def realdirpath(path, dir = nil)
|
|
341
|
+
v = VirtualFS.vpath(dir ? File.expand_path(path, dir) : path)
|
|
342
|
+
v || super
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
module DirPatch
|
|
347
|
+
def exist?(path) = VirtualFS.vpath(path) ? VirtualFS.directory?(path) : super
|
|
348
|
+
|
|
349
|
+
def empty?(path)
|
|
350
|
+
VirtualFS.vpath(path) ? VirtualFS.children(path).empty? : super
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def children(path, **opts) = VirtualFS.vpath(path) ? VirtualFS.children(path) : super
|
|
354
|
+
def entries(path, **opts) = VirtualFS.vpath(path) ? [".", ".."] + VirtualFS.children(path) : super
|
|
355
|
+
|
|
356
|
+
def each_child(path, **opts, &block)
|
|
357
|
+
return super unless VirtualFS.vpath(path)
|
|
358
|
+
block ? VirtualFS.children(path).each(&block) && nil : VirtualFS.children(path).each
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def foreach(path, **opts, &block)
|
|
362
|
+
return super unless VirtualFS.vpath(path)
|
|
363
|
+
block ? entries(path).each(&block) && nil : entries(path).each
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def glob(pattern, flags = 0, base: nil, sort: true, &block)
|
|
367
|
+
patterns = Array(pattern).flat_map { |p| p.respond_to?(:to_path) ? p.to_path : p }
|
|
368
|
+
virtual, real = patterns.partition do |p|
|
|
369
|
+
VirtualFS.vpath(base ? File.join(base, p) : p.sub(/[*?\[{].*/m, ""))
|
|
370
|
+
end
|
|
371
|
+
found = virtual.flat_map { |p| VirtualFS.glob(p, flags, base) }
|
|
372
|
+
found += super(real, flags, base: base, sort: sort) unless real.empty?
|
|
373
|
+
block ? found.each(&block) && nil : found
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def [](*patterns, base: nil, sort: true) = glob(patterns, base: base, sort: sort)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# ---- start ------------------------------------------------------------
|
|
380
|
+
# ocran/config.rb, written by ocran (StaticRuntimeBuilder): a Hash literal.
|
|
381
|
+
@config = TOPLEVEL_BINDING.eval(read("ocran/config.rb").force_encoding(Encoding::UTF_8),
|
|
382
|
+
"#{PREFIX}ocran/config.rb", 1).freeze
|
|
383
|
+
in_root = ->(value) { value.gsub("<ROOT>", ROOT) }
|
|
384
|
+
$stderr.puts "ocran: starting #{EXE}" if @config[:debug]
|
|
385
|
+
|
|
386
|
+
$LOAD_PATH.replace(@config.fetch(:load_path).map { |dir| dir.empty? ? ROOT : "#{PREFIX}#{dir}" })
|
|
387
|
+
install_kernel_methods
|
|
388
|
+
require "stringio" # statically linked
|
|
389
|
+
|
|
390
|
+
# What File.open returns for an archive path: its contents in memory,
|
|
391
|
+
# with the File methods that make sense for them. There is no operating
|
|
392
|
+
# system file behind it, so asking for one is an IOError, as for a
|
|
393
|
+
# closed file (logger, for one, probes File.new(io.fileno) this way).
|
|
394
|
+
class ArchivedFile < StringIO
|
|
395
|
+
attr_reader :path
|
|
396
|
+
alias_method :to_path, :path
|
|
397
|
+
|
|
398
|
+
def initialize(data, path)
|
|
399
|
+
super(data)
|
|
400
|
+
@path = path
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def fileno = raise(IOError, "#{@path} is inside the executable and has no file descriptor")
|
|
404
|
+
alias_method :to_i, :fileno
|
|
405
|
+
def stat = VirtualFS.stat(@path)
|
|
406
|
+
def size = string.bytesize
|
|
407
|
+
def mtime = stat.mtime
|
|
408
|
+
def flock(*) = 0
|
|
409
|
+
def inspect = "#<#{self.class} #{@path}>"
|
|
410
|
+
end
|
|
411
|
+
IO.singleton_class.prepend(IOPatch)
|
|
412
|
+
File.singleton_class.prepend(FilePatch)
|
|
413
|
+
Dir.singleton_class.prepend(DirPatch)
|
|
414
|
+
|
|
415
|
+
@config.fetch(:env).each { |name, value| ENV[name] = in_root.(value) }
|
|
416
|
+
|
|
417
|
+
if @config[:rubygems]
|
|
418
|
+
# What Ruby's gem_prelude.rb does at startup, which the runtime skips
|
|
419
|
+
# (--disable=gems) so that it happens only once the archive is readable.
|
|
420
|
+
require "rubygems"
|
|
421
|
+
%w[error_highlight did_you_mean syntax_suggest/core_ext].each do |feature|
|
|
422
|
+
require feature
|
|
423
|
+
rescue LoadError
|
|
424
|
+
# not packed: the application did not have it loaded at build time
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
Dir.chdir(File.dirname(ENV.fetch("OCRAN_EXECUTABLE"))) if @config[:chdir_exe_dir]
|
|
429
|
+
|
|
430
|
+
# RUBYOPT -I and -r (the runtime never reads RUBYOPT itself).
|
|
431
|
+
$LOAD_PATH.unshift(*@config.fetch(:rubyopt_includes).map(&in_root))
|
|
432
|
+
@config.fetch(:rubyopt_requires).each { |feature| require in_root.(feature) }
|
|
433
|
+
|
|
434
|
+
main = "#{PREFIX}#{@config.fetch(:main)}"
|
|
435
|
+
$0 = main
|
|
436
|
+
src = VirtualFS.read(main).force_encoding(Encoding::UTF_8)
|
|
437
|
+
if (i = src.index(/^__END__\r?\n/))
|
|
438
|
+
Object.const_set(:DATA, StringIO.new(src[src.index("\n", i) + 1..]))
|
|
439
|
+
end
|
|
440
|
+
ARGV.unshift(*@config.fetch(:argv).map(&in_root))
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
OcranRuntime.compile($0).eval
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Compiles one C extension (a gem's ext/<name>/ directory) into a static
|
|
2
|
+
# library for an ocran static runtime, the way RubyGems would build it -
|
|
3
|
+
# extconf.rb then make - except that mkmf targets the runtime and produces
|
|
4
|
+
# a static library instead of a DLL.
|
|
5
|
+
#
|
|
6
|
+
# Runs in a process of its own (mkmf is global state), under a Ruby whose
|
|
7
|
+
# RbConfig describes the runtime: for MinGW, any Ruby with the runtime's
|
|
8
|
+
# <arch>-fake.rb loaded (Ruby's own cross-build mechanism); for mswin, the
|
|
9
|
+
# runtime build's own Ruby.
|
|
10
|
+
#
|
|
11
|
+
# ruby --disable=gems -I SDK -r SDK/<arch>-fake build_static_ext.rb \
|
|
12
|
+
# INCLUDE_DIR ARCH_INCLUDE_DIR EXT_SRC_DIR OUT_DIR [extconf args...]
|
|
13
|
+
#
|
|
14
|
+
# INCLUDE_DIR holds ruby.h, ARCH_INCLUDE_DIR ruby/config.h. The compiler is
|
|
15
|
+
# RbConfig's, or OCRAN_RUNTIME_CC (with the matching ar and ranlib) when the
|
|
16
|
+
# build host names it differently; OCRAN_RUNTIME_LIBS are appended to $LIBS.
|
|
17
|
+
# Prints
|
|
18
|
+
# <feature> <Init function> <archive> <comma-separated libs>
|
|
19
|
+
# on success, for the runtime link (Ocran::RuntimeSdk#link).
|
|
20
|
+
require "rbconfig"
|
|
21
|
+
require "fileutils"
|
|
22
|
+
|
|
23
|
+
include_dir, arch_include_dir, ext_src, out, *extconf_args = ARGV
|
|
24
|
+
abort "usage: see #{__FILE__}" unless out
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
"rubyhdrdir" => File.expand_path(include_dir),
|
|
28
|
+
"rubyarchhdrdir" => File.expand_path(arch_include_dir),
|
|
29
|
+
}.each { |k, v| RbConfig.fire_update!(k, v) }
|
|
30
|
+
|
|
31
|
+
if (cc = ENV["OCRAN_RUNTIME_CC"]) && !cc.empty?
|
|
32
|
+
tool = ->(name) { cc.sub(/(?:gcc|cc|clang)(\.exe)?\z/) { "#{name}#{$1}" } }
|
|
33
|
+
{ "CC" => cc, "CPP" => "#{cc} -E", "LDSHARED" => "#{cc} -shared", "AR" => tool.("ar"),
|
|
34
|
+
"RANLIB" => tool.("ranlib") }.each { |k, v| RbConfig.fire_update!(k, v) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# RubyInstaller's RubyGems defaults (rubygems/defaults/operating_system.rb),
|
|
38
|
+
# loaded when an extconf.rb requires rubygems (nokogiri's does), set up the
|
|
39
|
+
# build host's Ruby - its DLL directories, MSYS2 - from RbConfig, which
|
|
40
|
+
# describes the target here, and fail. None of that applies to building for
|
|
41
|
+
# the target (MSYS2's gcc is on PATH already), so an empty file of that name
|
|
42
|
+
# comes first on the load path.
|
|
43
|
+
if $LOAD_PATH.any? { |dir| File.file?(File.join(dir, "ruby_installer/runtime.rb")) }
|
|
44
|
+
defaults = File.join(File.dirname(File.expand_path(out)), "ocran-rubygems-defaults")
|
|
45
|
+
FileUtils.mkdir_p(File.join(defaults, "rubygems/defaults"))
|
|
46
|
+
File.write(File.join(defaults, "rubygems/defaults/operating_system.rb"),
|
|
47
|
+
"# Left empty by ocran: see build_static_ext.rb.\n")
|
|
48
|
+
$LOAD_PATH.unshift(defaults)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Built inside a copy of the whole gem, as RubyGems does: extconf.rb files
|
|
52
|
+
# may reach into the gem's lib/ (racc's does).
|
|
53
|
+
ext_src = File.expand_path(ext_src)
|
|
54
|
+
gem_root, sep, ext_rel = ext_src.rpartition("/ext/")
|
|
55
|
+
gem_root, ext_rel = File.dirname(ext_src), File.basename(ext_src) if sep.empty?
|
|
56
|
+
ext_rel = File.join("ext", ext_rel) unless sep.empty?
|
|
57
|
+
FileUtils.rm_rf(out)
|
|
58
|
+
FileUtils.mkdir_p(File.dirname(out))
|
|
59
|
+
FileUtils.cp_r(gem_root, out)
|
|
60
|
+
Dir.chdir(File.join(out, ext_rel))
|
|
61
|
+
|
|
62
|
+
$0 = File.expand_path("extconf.rb") # mkmf takes srcdir from $0
|
|
63
|
+
ARGV.replace(extconf_args)
|
|
64
|
+
require "mkmf"
|
|
65
|
+
$static = true # after require: loading mkmf resets it
|
|
66
|
+
# The static C libraries an extension's own (-lsybdb, -lxml2) need in turn -
|
|
67
|
+
# iconv, OpenSSL, zlib, Windows system libraries - in link order. mkmf puts
|
|
68
|
+
# $LIBS after the libraries extconf.rb finds, both in its link checks and
|
|
69
|
+
# in the Makefile, which is the order a static link needs.
|
|
70
|
+
$LIBS = [$LIBS, ENV["OCRAN_RUNTIME_LIBS"]].compact.join(" ")
|
|
71
|
+
# Defines the static libraries' headers need (LIBXSLT_STATIC, ...).
|
|
72
|
+
$CPPFLAGS = [$CPPFLAGS, ENV["OCRAN_RUNTIME_CPPFLAGS"]].compact.join(" ")
|
|
73
|
+
load "./extconf.rb"
|
|
74
|
+
|
|
75
|
+
mk = File.read("Makefile")
|
|
76
|
+
target = mk[/^TARGET = (\S+)/, 1] or abort "#{ext_src}: extconf.rb produced no TARGET"
|
|
77
|
+
prefix = mk[/^target_prefix = (\S*)/, 1].to_s.delete_prefix("/")
|
|
78
|
+
feature = [prefix, target].reject(&:empty?).join("/")
|
|
79
|
+
libs = mk[/^LIBS = (.*)$/, 1].split.reject { |l| l.include?("$(RUBY_SO_NAME)") || l.include?("$(LIBRUBY") }
|
|
80
|
+
lib = "#{target}.#{RbConfig::CONFIG["LIBEXT"]}"
|
|
81
|
+
if RbConfig::CONFIG["target_os"].start_with?("mswin")
|
|
82
|
+
system("nmake", "-nologo", "STATIC_LIB=#{lib}", lib, exception: true)
|
|
83
|
+
else
|
|
84
|
+
system("make", "-j#{ENV.fetch("JOBS", "4")}", "STATIC_LIB=#{lib}", lib, exception: true)
|
|
85
|
+
end
|
|
86
|
+
puts [feature, "Init_#{target}", File.join(Dir.pwd, lib), libs.join(",")].join(" ")
|