reflex-packager 0.2.0 → 0.3.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/.github/workflows/package.yml +310 -0
- data/.github/workflows/release-gem.yml +2 -49
- data/.github/workflows/tag.yml +11 -28
- data/.github/workflows/test.yml +16 -37
- data/.gitignore +2 -0
- data/ChangeLog.md +28 -0
- data/Gemfile.lock +26 -0
- data/README.md +232 -27
- data/Rakefile +21 -0
- data/VERSION +1 -1
- data/bin/reflex +2 -32
- data/examples/hello/icon.png +0 -0
- data/examples/hello/main.rb +18 -0
- data/examples/hello/reflex.yml +3 -0
- data/lib/reflex/packager/app_store_connect.rb +162 -0
- data/lib/reflex/packager/cli.rb +42 -9
- data/lib/reflex/packager/config.rb +233 -36
- data/lib/reflex/packager/data_compiler.rb +59 -0
- data/lib/reflex/packager/data_file.rb +183 -0
- data/lib/reflex/packager/data_loader.rb +127 -0
- data/lib/reflex/packager/gems.rb +282 -0
- data/lib/reflex/packager/library.rb +120 -0
- data/lib/reflex/packager/macos.rb +592 -94
- data/lib/reflex/packager/platform.rb +289 -10
- data/lib/reflex/packager/profile.rb +38 -26
- data/lib/reflex/packager/profiles/reflex.rb +43 -0
- data/lib/reflex/packager/rake.rb +115 -0
- data/lib/reflex/packager/stores/app_store.rb +60 -0
- data/lib/reflex/packager/stores/itchio.rb +53 -0
- data/lib/reflex/packager/templates/macos/AppStore.entitlements.erb +8 -0
- data/lib/reflex/packager/templates/macos/AppStoreExportOptions.plist.erb +14 -0
- data/lib/reflex/packager/templates/macos/ExportOptions.plist.erb +20 -0
- data/lib/reflex/packager/templates/macos/InfoPlist.strings.erb +5 -0
- data/lib/reflex/packager/templates/macos/boot.rb.erb +29 -0
- data/lib/reflex/packager/templates/macos/compile.rb.erb +7 -0
- data/lib/reflex/packager/templates/macos/main.mm.erb +10 -7
- data/lib/reflex/packager/templates/macos/project.yml.erb +60 -3
- data/lib/reflex/packager/templates/windows/app.manifest.erb +25 -0
- data/lib/reflex/packager/templates/windows/app.rc.erb +38 -0
- data/lib/reflex/packager/templates/windows/boot.rb.erb +32 -0
- data/lib/reflex/packager/templates/windows/main.cpp.erb +47 -0
- data/lib/reflex/packager/templates/windows/runtime.manifest.erb +7 -0
- data/lib/reflex/packager/windows.rb +370 -0
- data/lib/reflex/packager.rb +18 -1
- data/reflex-packager.gemspec +8 -7
- data/test/helper.rb +50 -5
- data/test/test_packager_app_store_connect.rb +108 -0
- data/test/test_packager_cli.rb +38 -3
- data/test/test_packager_config.rb +166 -27
- data/test/test_packager_data_file.rb +96 -0
- data/test/test_packager_data_loader.rb +114 -0
- data/test/test_packager_library.rb +81 -0
- data/test/test_packager_macos.rb +714 -86
- data/test/test_packager_platform.rb +16 -0
- data/test/test_packager_rake.rb +165 -0
- data/test/test_packager_stores_app_store.rb +66 -0
- data/test/test_packager_stores_itchio.rb +38 -0
- data/test/test_packager_windows.rb +685 -0
- metadata +72 -15
- data/.github/workflows/utils.rb +0 -127
- data/lib/reflex/packager/templates/macos/Podfile.erb +0 -30
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require_relative 'data_file'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Reflex
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module Packager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Lets require, require_relative and load read the Ruby scripts of an app
|
|
11
|
+
# from its data file, as the instruction sequences the packager compiled
|
|
12
|
+
# them into, named as the scripts but with .rbc, in place of the scripts,
|
|
13
|
+
# which the app directory has none of. The other files of the app are in
|
|
14
|
+
# it as they are.
|
|
15
|
+
#
|
|
16
|
+
# The scripts are compiled with their paths relative to the app directory,
|
|
17
|
+
# which a package runs in, so that __FILE__ and __dir__ have them as they
|
|
18
|
+
# are, and the others, as the libraries, are required as they are.
|
|
19
|
+
#
|
|
20
|
+
# A package carries this file in the reflex library of it, with DataFile
|
|
21
|
+
# beside, which is all it requires.
|
|
22
|
+
#
|
|
23
|
+
module DataLoader
|
|
24
|
+
|
|
25
|
+
DATA_FILE = 'data.bin'
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
|
|
29
|
+
# Reads the scripts of the app in +dir+ from the data file in it.
|
|
30
|
+
#
|
|
31
|
+
# @param [String] dir the app directory, which has data.bin
|
|
32
|
+
#
|
|
33
|
+
def setup(dir)
|
|
34
|
+
# as the current directory has it, for the paths to compare
|
|
35
|
+
@dir = File.realpath dir
|
|
36
|
+
@data = DataFile.new File.join(@dir, DATA_FILE)
|
|
37
|
+
|
|
38
|
+
version = @data.read('.ruby-version') || '(unknown)'
|
|
39
|
+
raise "the data file is for Ruby #{version}, not #{RUBY_VERSION}" if
|
|
40
|
+
version != RUBY_VERSION
|
|
41
|
+
|
|
42
|
+
Kernel.prepend Require
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
attr_reader :dir, :data
|
|
46
|
+
|
|
47
|
+
# The name of the script in the data file for +feature+ to require, or
|
|
48
|
+
# nil.
|
|
49
|
+
#
|
|
50
|
+
def find(feature)
|
|
51
|
+
feature = feature.to_s
|
|
52
|
+
return script(File.expand_path feature) if
|
|
53
|
+
File.absolute_path?(feature) || feature.start_with?('./', '../')
|
|
54
|
+
|
|
55
|
+
$LOAD_PATH.each do |load_path|
|
|
56
|
+
name = script File.expand_path(feature, load_path.to_s)
|
|
57
|
+
return name if name
|
|
58
|
+
end
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The name of the script in the data file at +path+, or nil.
|
|
63
|
+
#
|
|
64
|
+
def script(path)
|
|
65
|
+
path = File.expand_path path, @dir
|
|
66
|
+
return nil unless path.start_with? "#{@dir}/"
|
|
67
|
+
name = path[(@dir.size + 1)..].sub(/\.rb\z/, '') + '.rbc'
|
|
68
|
+
@data.include?(name) ? name : nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Requires the script +name+ in the data file unless it is already.
|
|
72
|
+
#
|
|
73
|
+
def require_script(name)
|
|
74
|
+
path = File.join @dir, name.sub(/\.rbc\z/, '.rb')
|
|
75
|
+
return false if $LOADED_FEATURES.include? path
|
|
76
|
+
|
|
77
|
+
# before running it, as require does, for a script it requires to
|
|
78
|
+
# require it back
|
|
79
|
+
$LOADED_FEATURES << path
|
|
80
|
+
begin
|
|
81
|
+
load_script name
|
|
82
|
+
rescue Exception
|
|
83
|
+
$LOADED_FEATURES.delete path
|
|
84
|
+
raise
|
|
85
|
+
end
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def load_script(name)
|
|
90
|
+
RubyVM::InstructionSequence.load_from_binary(@data.read name).eval
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end# self
|
|
94
|
+
|
|
95
|
+
module Require
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def require(feature)
|
|
100
|
+
name = DataLoader.find feature
|
|
101
|
+
name ? DataLoader.require_script(name) : super
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def require_relative(feature)
|
|
105
|
+
location = caller_locations(1, 1).first
|
|
106
|
+
base = File.expand_path(location.absolute_path || location.path, DataLoader.dir)
|
|
107
|
+
path = File.expand_path feature, File.dirname(base)
|
|
108
|
+
name = DataLoader.script path
|
|
109
|
+
name ? DataLoader.require_script(name) : require(path)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def load(file, wrap = false)
|
|
113
|
+
name = DataLoader.script File.expand_path(file)
|
|
114
|
+
return super unless name
|
|
115
|
+
DataLoader.load_script name
|
|
116
|
+
true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
end# Require
|
|
120
|
+
|
|
121
|
+
end# DataLoader
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
end# Packager
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
end# Reflex
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'rbconfig'
|
|
5
|
+
require 'reflex/packager/library'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module Reflex
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
module Packager
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Gems shipped with an app as they are: the ones of the Gemfile of the
|
|
15
|
+
# app, and the standard gems, which were default gems once. Included by a
|
|
16
|
+
# platform, which puts them where its load path finds them, and has the
|
|
17
|
+
# profile and the config they are looked for with.
|
|
18
|
+
#
|
|
19
|
+
module Gems
|
|
20
|
+
|
|
21
|
+
# Default gems that became bundled gems, as the NEWS of each Ruby says.
|
|
22
|
+
# Gem::BUNDLED_GEMS::SINCE, which Ruby 3.3 and later have, misses some
|
|
23
|
+
# of them; it adds the ones of Ruby versions newer than this list.
|
|
24
|
+
#
|
|
25
|
+
FORMER_DEFAULT_GEMS = [
|
|
26
|
+
*%w[rexml rss], # 3.0
|
|
27
|
+
*%w[net-ftp net-imap net-pop net-smtp matrix prime debug], # 3.1
|
|
28
|
+
*%w[racc], # 3.3
|
|
29
|
+
*%w[mutex_m getoptlong base64 bigdecimal observer abbrev
|
|
30
|
+
resolv-replace rinda drb nkf syslog csv], # 3.4
|
|
31
|
+
*%w[ostruct pstore benchmark logger rdoc win32ole irb reline
|
|
32
|
+
readline fiddle win32-registry] # 4.0
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# Former default gems for development rather than for an app to run
|
|
36
|
+
# with, left out of the standard gems; with what they depend on, as
|
|
37
|
+
# prism and rbs for irb and rdoc, they would add over 10 MB. An app
|
|
38
|
+
# that runs with them lists them in its Gemfile.
|
|
39
|
+
#
|
|
40
|
+
DEVELOPMENT_GEMS = %w[irb rdoc debug]
|
|
41
|
+
|
|
42
|
+
# Extensions of the binaries a gem with a native extension has.
|
|
43
|
+
#
|
|
44
|
+
NATIVE_EXTS = %w[.so .bundle .dll]
|
|
45
|
+
|
|
46
|
+
# Names of the libraries and the gems, put on the load path in this
|
|
47
|
+
# order: a library before the ones it depends on, as bundler puts them,
|
|
48
|
+
# and the libraries before the gems, which the runtime is not to be
|
|
49
|
+
# hidden by.
|
|
50
|
+
#
|
|
51
|
+
def lib_names()
|
|
52
|
+
[*library_names.reverse, *gem_names]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Names of the gems copy_gems copies, put on the load path in this order.
|
|
56
|
+
#
|
|
57
|
+
def gem_names()
|
|
58
|
+
[*('bundler' if gemfile), *gem_dirs.keys]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Require paths of the gems by name: the ones of the Gemfile of the app,
|
|
62
|
+
# then the standard gems.
|
|
63
|
+
#
|
|
64
|
+
def gem_dirs()
|
|
65
|
+
@gem_dirs ||= standard_gem_dirs
|
|
66
|
+
.reject {|name, _| app_gem_dirs.key? name}
|
|
67
|
+
.then {app_gem_dirs.merge _1}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Require paths of the gems in the default group of the Gemfile of the
|
|
71
|
+
# app, by name. A gem with a native extension, where it is not
|
|
72
|
+
# supported, is left out if the Ruby a platform embeds has it.
|
|
73
|
+
#
|
|
74
|
+
def app_gem_dirs()
|
|
75
|
+
@app_gem_dirs ||= gemfile_specs.each.with_object({}) do |spec, dirs|
|
|
76
|
+
name, paths = spec.values_at 'name', 'require_paths'
|
|
77
|
+
next unless shipped? spec
|
|
78
|
+
if !native_gems? && native_gem?(paths)
|
|
79
|
+
next if embedded_gem? name
|
|
80
|
+
raise Error, "gem '#{name}' has a native extension, which is not supported"
|
|
81
|
+
end
|
|
82
|
+
dirs[name] = paths
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Require paths of the standard gems installed, by name, leaving out the
|
|
87
|
+
# ones with a native extension where it is not supported.
|
|
88
|
+
#
|
|
89
|
+
def standard_gem_dirs()
|
|
90
|
+
return {} unless standard_gems?
|
|
91
|
+
@standard_gem_dirs ||= standard_specs.each.with_object({}) do |spec, dirs|
|
|
92
|
+
name, paths = spec.values_at 'name', 'require_paths'
|
|
93
|
+
next unless shipped? spec
|
|
94
|
+
next if !native_gems? && native_gem?(paths)
|
|
95
|
+
dirs[name] = paths
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The libraries the app runs on: the ones of the profile, and the ones in
|
|
100
|
+
# the Gemfile of the app, with what they depend on.
|
|
101
|
+
#
|
|
102
|
+
def libraries()
|
|
103
|
+
@libraries ||= Library.collect(*gemfile_library_roots, known: profile.libraries)
|
|
104
|
+
.then {profile.libraries + _1}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Native extensions to register (Init_<name>).
|
|
108
|
+
#
|
|
109
|
+
def extensions()
|
|
110
|
+
libraries.filter_map(&:extension)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def library_names()
|
|
114
|
+
libraries.map(&:name)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
# Whether to ship the standard gems, which the Ruby a platform embeds may
|
|
120
|
+
# have in its standard library already.
|
|
121
|
+
#
|
|
122
|
+
def standard_gems?()
|
|
123
|
+
true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Whether the Ruby a platform embeds has the gem +name+ in its standard
|
|
127
|
+
# library, which the app runs with in place of the one of its Gemfile
|
|
128
|
+
# that cannot be shipped.
|
|
129
|
+
#
|
|
130
|
+
def embedded_gem?(name)
|
|
131
|
+
false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Whether a gem with a native extension can be shipped.
|
|
135
|
+
#
|
|
136
|
+
def native_gems?()
|
|
137
|
+
false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def native_gem?(paths)
|
|
141
|
+
paths.any? do |path|
|
|
142
|
+
Dir.glob('**/*', base: path).any? {NATIVE_EXTS.include? File.extname(_1)}
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Leaves out bundler, default gems, which are in the standard library
|
|
147
|
+
# already, and the libraries, known by their extension.rb.
|
|
148
|
+
#
|
|
149
|
+
def shipped?(spec)
|
|
150
|
+
name, default_gem, paths = spec.values_at 'name', 'default_gem', 'require_paths'
|
|
151
|
+
return false if name == 'bundler' || default_gem
|
|
152
|
+
library_names.none? do |lib|
|
|
153
|
+
paths.any? {File.file? File.join(_1, lib, 'extension.rb')}
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Copies the gems as they are into the directories the block returns for
|
|
158
|
+
# their names, which the platform puts on the load path.
|
|
159
|
+
#
|
|
160
|
+
def copy_gems(&lib_dir)
|
|
161
|
+
gem_dirs.each do |name, paths|
|
|
162
|
+
dest = lib_dir.call name
|
|
163
|
+
FileUtils.mkdir_p dest
|
|
164
|
+
paths.each {FileUtils.cp_r File.join(_1, '.'), dest}
|
|
165
|
+
end
|
|
166
|
+
write_bundler_setup lib_dir.call('bundler') if gemfile
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# An app with a Gemfile may require bundler/setup, which would find the
|
|
170
|
+
# bundler of the standard library, and no Gemfile to set up with. The
|
|
171
|
+
# gems are on the load path already, so the one found first does
|
|
172
|
+
# nothing.
|
|
173
|
+
#
|
|
174
|
+
def write_bundler_setup(dir)
|
|
175
|
+
path = File.join dir, 'bundler', 'setup.rb'
|
|
176
|
+
FileUtils.mkdir_p File.dirname(path)
|
|
177
|
+
File.write path, "# the gems of the Gemfile are on the load path already\n"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Directories of the gems in the Gemfile which are libraries, known by
|
|
181
|
+
# their extension.rb, and not the ones of the profile; the ones RUBYLIB
|
|
182
|
+
# points to come first, as for the libraries of the profile.
|
|
183
|
+
#
|
|
184
|
+
def gemfile_library_roots()
|
|
185
|
+
names = profile.libraries.map(&:name)
|
|
186
|
+
gemfile_specs.filter_map do |spec|
|
|
187
|
+
name = Library.library_name spec['root']
|
|
188
|
+
next if !name || names.include?(name)
|
|
189
|
+
Library.source_root(spec['name']) || spec['root']
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def gemfile()
|
|
194
|
+
path = File.join config.dir, 'Gemfile'
|
|
195
|
+
File.file?(path) ? path : nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
SPECS_TO_JSON = <<~RUBY
|
|
199
|
+
def specs_to_json(specs)
|
|
200
|
+
$stdout.write JSON.generate(specs.map {|s|
|
|
201
|
+
{
|
|
202
|
+
name: s.name,
|
|
203
|
+
root: s.full_gem_path,
|
|
204
|
+
default_gem: s.default_gem?,
|
|
205
|
+
require_paths: s.full_require_paths
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
end
|
|
209
|
+
RUBY
|
|
210
|
+
|
|
211
|
+
GEMFILE_SPECS = <<~RUBY
|
|
212
|
+
require 'bundler'
|
|
213
|
+
require 'json'
|
|
214
|
+
#{SPECS_TO_JSON}
|
|
215
|
+
Bundler.ui = Bundler::UI::Silent.new
|
|
216
|
+
begin
|
|
217
|
+
specs_to_json Bundler.definition.specs_for([:default])
|
|
218
|
+
rescue Bundler::BundlerError => e
|
|
219
|
+
abort e.message
|
|
220
|
+
end
|
|
221
|
+
RUBY
|
|
222
|
+
|
|
223
|
+
STANDARD_SPECS = <<~RUBY
|
|
224
|
+
require 'json'
|
|
225
|
+
#{SPECS_TO_JSON}
|
|
226
|
+
names, excluded = JSON.parse ARGV[0]
|
|
227
|
+
begin
|
|
228
|
+
require 'bundled_gems'
|
|
229
|
+
names |= Gem::BUNDLED_GEMS::SINCE.keys
|
|
230
|
+
rescue LoadError
|
|
231
|
+
end
|
|
232
|
+
names -= excluded
|
|
233
|
+
|
|
234
|
+
# with what they depend on, which may be neither a default gem nor
|
|
235
|
+
# one of these
|
|
236
|
+
specs = {}
|
|
237
|
+
deps = names.map {Gem::Dependency.new _1}
|
|
238
|
+
until deps.empty?
|
|
239
|
+
dep = deps.shift
|
|
240
|
+
next if specs.key? dep.name
|
|
241
|
+
specs[dep.name] = begin
|
|
242
|
+
dep.to_spec
|
|
243
|
+
rescue Gem::LoadError
|
|
244
|
+
end
|
|
245
|
+
deps.concat specs[dep.name].runtime_dependencies if specs[dep.name]
|
|
246
|
+
end
|
|
247
|
+
specs_to_json specs.values.compact
|
|
248
|
+
RUBY
|
|
249
|
+
|
|
250
|
+
# The gems of the default group of the Gemfile of the app.
|
|
251
|
+
#
|
|
252
|
+
def gemfile_specs()
|
|
253
|
+
return [] unless gemfile
|
|
254
|
+
run_ruby GEMFILE_SPECS, env: {'BUNDLE_GEMFILE' => gemfile},
|
|
255
|
+
error: 'failed to resolve the Gemfile (bundle install?)'
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The standard gems installed, and the gems they depend on.
|
|
259
|
+
#
|
|
260
|
+
def standard_specs()
|
|
261
|
+
run_ruby STANDARD_SPECS, JSON.generate([FORMER_DEFAULT_GEMS, DEVELOPMENT_GEMS]),
|
|
262
|
+
error: 'failed to look for the standard gems'
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Runs the script in a ruby of its own, out of the reach of any bundler
|
|
266
|
+
# this process runs under, which would hide the gems out of its bundle.
|
|
267
|
+
#
|
|
268
|
+
def run_ruby(script, *args, env: {}, error:)
|
|
269
|
+
env = ENV.keys.grep(/\ABUNDLER?_/).to_h {[_1, nil]}
|
|
270
|
+
.merge 'RUBYOPT' => nil, **env
|
|
271
|
+
out, err, status = Open3.capture3 env, RbConfig.ruby, '-e', script, *args
|
|
272
|
+
raise Error, "#{error}: #{err.strip}" unless status.success?
|
|
273
|
+
JSON.parse out
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
end# Gems
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
end# Packager
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
end# Reflex
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Reflex
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module Packager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# A library a packaged app runs on: a gem with lib/<name>/extension.rb, as
|
|
11
|
+
# xot, rays and reflex are, whose Ruby code a package bundles and whose
|
|
12
|
+
# extension, if it has lib/<name>/ext.rb, a package links.
|
|
13
|
+
#
|
|
14
|
+
class Library
|
|
15
|
+
|
|
16
|
+
# @param [String] name the directory of the library under lib/
|
|
17
|
+
# @param [String] root the directory of the gem
|
|
18
|
+
#
|
|
19
|
+
def initialize(name, root)
|
|
20
|
+
@name, @root = name, root
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_reader :name, :root
|
|
24
|
+
|
|
25
|
+
# The extension to register (Init_<extension>), or nil.
|
|
26
|
+
#
|
|
27
|
+
def extension()
|
|
28
|
+
return nil unless File.file? File.join(root, 'lib', name, 'ext.rb')
|
|
29
|
+
"#{name.tr '-', '_'}_ext"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_s()
|
|
33
|
+
name
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns the libraries the gems at +roots+ depend on, as their gemspecs
|
|
37
|
+
# have them, and the gems themselves, each after the ones it depends on,
|
|
38
|
+
# leaving out the +known+ ones.
|
|
39
|
+
#
|
|
40
|
+
# A gem is looked for in the directories on the load path first, as
|
|
41
|
+
# RUBYLIB points to the ones of a repository being worked on, and then in
|
|
42
|
+
# the installed gems.
|
|
43
|
+
#
|
|
44
|
+
# @param [Array<String>] roots the directories of the gems
|
|
45
|
+
# @param [Array<Library>] known the libraries collected already
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<Library>] libraries
|
|
48
|
+
#
|
|
49
|
+
def self.collect(*roots, known: [])
|
|
50
|
+
libraries, visited, sources = [], known.to_h {[_1.root, true]}, source_roots
|
|
51
|
+
visit = -> (root, spec) do
|
|
52
|
+
name = library_name root
|
|
53
|
+
next if !name || visited[root]
|
|
54
|
+
visited[root] = true
|
|
55
|
+
spec.runtime_dependencies.each {|dep| find_gem(dep, sources)&.then {visit.call(*_1)}}
|
|
56
|
+
libraries << new(name, root)
|
|
57
|
+
end
|
|
58
|
+
roots.map {File.expand_path _1}.each {visit.call _1, spec_of(_1)}
|
|
59
|
+
libraries
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The directory of the gem +name+ on the load path, which is not an
|
|
63
|
+
# installed one, as RUBYLIB points to, or nil.
|
|
64
|
+
#
|
|
65
|
+
def self.source_root(name)
|
|
66
|
+
source_roots[name]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The directory under lib/ with extension.rb, or nil.
|
|
70
|
+
#
|
|
71
|
+
def self.library_name(root)
|
|
72
|
+
path = Dir.glob(File.join root, 'lib', '*', 'extension.rb').first
|
|
73
|
+
path && File.basename(File.dirname path)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns [root, spec] of the gem +dep+ asks for, or nil.
|
|
77
|
+
#
|
|
78
|
+
def self.find_gem(dep, sources)
|
|
79
|
+
root = sources[dep.name]
|
|
80
|
+
return [root, spec_of(root)] if root
|
|
81
|
+
spec = dep.to_spec
|
|
82
|
+
[spec.full_gem_path, spec]
|
|
83
|
+
rescue Gem::LoadError
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The gems on the load path which are not installed ones, by name.
|
|
88
|
+
#
|
|
89
|
+
def self.source_roots()
|
|
90
|
+
$LOAD_PATH
|
|
91
|
+
.map {File.dirname File.expand_path(_1.to_s)}
|
|
92
|
+
.uniq
|
|
93
|
+
.reject {installed? _1}
|
|
94
|
+
.select {Dir.glob(File.join _1, '*.gemspec').any?}
|
|
95
|
+
.to_h {[spec_of(_1).name, _1]}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.spec_of(root)
|
|
99
|
+
installed = Gem::Specification.find {_1.full_gem_path == root}
|
|
100
|
+
return installed if installed
|
|
101
|
+
path = Dir.glob(File.join root, '*.gemspec').first or
|
|
102
|
+
raise Error, "no gemspec in '#{root}'"
|
|
103
|
+
# a gemspec may run git ls-files, which lists the files of the repository it is run in
|
|
104
|
+
Dir.chdir(root) {Gem::Specification.load path} or
|
|
105
|
+
raise Error, "failed to load '#{path}'"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.installed?(root)
|
|
109
|
+
Gem.path.any? {root.start_with? File.join(_1, 'gems', '')}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private_class_method :find_gem, :source_roots, :spec_of, :installed?
|
|
113
|
+
|
|
114
|
+
end# Library
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
end# Packager
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
end# Reflex
|