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,685 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
require_relative 'helper'
|
|
3
|
+
require 'reflex/packager/windows'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestPackagerWindows < Test::Unit::TestCase
|
|
7
|
+
|
|
8
|
+
RP = Reflex::Packager
|
|
9
|
+
Windows = RP::Windows
|
|
10
|
+
|
|
11
|
+
# Files of the fake libraries, laid out as an installed gem is after a
|
|
12
|
+
# gem build on Windows: the extension and the archives are left in lib/.
|
|
13
|
+
LIBS = {
|
|
14
|
+
'fakebase' => %w[lib/fakebase.rb lib/libfakebase.a VERSION],
|
|
15
|
+
'fakenative' => %w[
|
|
16
|
+
lib/fakenative.rb lib/fakenative/sub.rb lib/fakenative/ext.rb
|
|
17
|
+
lib/fakenative_ext.so lib/libfakenative.a lib/libfakenative.dll.a
|
|
18
|
+
ext/fakenative/b.o ext/fakenative/a.o ext/fakenative/Makefile VERSION],
|
|
19
|
+
'fakepure' => %w[lib/fakepure.rb res/icon.png VERSION]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# With reflex, which a packed app needs to carry what reads its data file.
|
|
23
|
+
LIBS_WITH_REFLEX = LIBS.merge('reflex' => %w[lib/reflex.rb VERSION])
|
|
24
|
+
|
|
25
|
+
RBCONFIG = {
|
|
26
|
+
'CXX' => 'g++ -std=gnu++11',
|
|
27
|
+
'rubyhdrdir' => '/ruby/include/ruby-4.0.0',
|
|
28
|
+
'rubyarchhdrdir' => '/ruby/include/ruby-4.0.0/x64-mingw-ucrt',
|
|
29
|
+
'libdir' => '/ruby/lib',
|
|
30
|
+
'LIBRUBYARG_SHARED' => '-lx64-ucrt-ruby400'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Makes a directory of the files for each library, as its gem.
|
|
34
|
+
def fake_libs(libs = LIBS, &block)
|
|
35
|
+
Dir.mktmpdir do |root|
|
|
36
|
+
libs.each do |name, files|
|
|
37
|
+
files.each do |file|
|
|
38
|
+
path = File.join root, name, file
|
|
39
|
+
FileUtils.mkdir_p File.dirname(path)
|
|
40
|
+
File.write path, "# #{file}\n"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
@fake_root = root
|
|
44
|
+
block.call root
|
|
45
|
+
ensure
|
|
46
|
+
@fake_root = nil
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
module FakeExtension
|
|
51
|
+
module_function
|
|
52
|
+
def name() = 'Fake'
|
|
53
|
+
def version() = '1.0'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# A profile of the fake libraries, which the gemspecs of real gems would
|
|
57
|
+
# tell otherwise.
|
|
58
|
+
def profile(libraries: LIBS.keys, boot: nil)
|
|
59
|
+
libs = libraries.map {RP::Library.new _1, File.join(@fake_root.to_s, _1)}
|
|
60
|
+
RP::Profile.new(
|
|
61
|
+
extension: FakeExtension,
|
|
62
|
+
config_files: %w[reflex.yml],
|
|
63
|
+
templates: {'main.rb': ''},
|
|
64
|
+
boot: boot
|
|
65
|
+
).tap {|profile| profile.define_singleton_method(:libraries) {libs}}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def packager(
|
|
69
|
+
yaml = nil, profile: self.profile, files: {'main.rb' => ''}, standard_gems: {}, pack: false,
|
|
70
|
+
&block)
|
|
71
|
+
|
|
72
|
+
Dir.mktmpdir do |dir|
|
|
73
|
+
files.each do |file, content|
|
|
74
|
+
path = File.join dir, file
|
|
75
|
+
FileUtils.mkdir_p File.dirname(path)
|
|
76
|
+
File.write path, content
|
|
77
|
+
end
|
|
78
|
+
File.write File.join(dir, 'reflex.yml'), yaml if yaml
|
|
79
|
+
pkg = Windows.new RP::Config.load(profile, dir), pack: pack
|
|
80
|
+
# keep the tests off the gems of the Ruby running them
|
|
81
|
+
pkg.define_singleton_method(:standard_gem_dirs) {standard_gems}
|
|
82
|
+
block.call pkg, dir
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def build_path(dir, path = '')
|
|
87
|
+
File.join dir, '.build', 'windows', path
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def read(dir, path)
|
|
91
|
+
File.read build_path(dir, path)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# --- generate ----------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
def test_generate_creates_files()
|
|
97
|
+
fake_libs do
|
|
98
|
+
packager do |pkg, dir|
|
|
99
|
+
pkg.generate
|
|
100
|
+
%w[
|
|
101
|
+
src/main.cpp src/app.manifest src/app.rc lib/boot.rb lib/app/main.rb
|
|
102
|
+
lib/fakebase/lib/fakebase.rb
|
|
103
|
+
lib/fakenative/lib/fakenative.rb lib/fakenative/lib/fakenative/sub.rb
|
|
104
|
+
lib/fakepure/lib/fakepure.rb
|
|
105
|
+
].each do |path|
|
|
106
|
+
assert File.exist?(build_path dir, path), "missing #{path}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
error = assert_raise(RP::Error) {Windows.new(pkg.config, notarize: %i[submit]).package}
|
|
110
|
+
assert_include error.message, 'only for macos'
|
|
111
|
+
error = assert_raise(RP::Error) {Windows.new(pkg.config, appstore: true).package}
|
|
112
|
+
assert_include error.message, 'only for macos'
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def test_libs_leave_out_binaries()
|
|
118
|
+
fake_libs do
|
|
119
|
+
packager "files: [native.so]", files: {'main.rb' => '', 'native.so' => ''} do |pkg, dir|
|
|
120
|
+
pkg.generate
|
|
121
|
+
# a rays_ext.so on the load path would win over the one linked in
|
|
122
|
+
lib = build_path dir, 'lib'
|
|
123
|
+
files = pkg.load_dirs.flat_map {Dir.glob "#{_1}/**/*", base: lib}
|
|
124
|
+
assert_empty files.grep(/\.(so|a|o)\z/)
|
|
125
|
+
assert_not_include files, 'fakenative/ext'
|
|
126
|
+
# the app itself ships what it likes
|
|
127
|
+
assert File.exist?(File.join lib, 'app/native.so')
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_libs_carry_version_and_res()
|
|
133
|
+
fake_libs do
|
|
134
|
+
packager do |pkg, dir|
|
|
135
|
+
pkg.generate
|
|
136
|
+
# Extension.version reads VERSION and reight finds res/ from lib/
|
|
137
|
+
assert File.exist?(build_path dir, 'lib/fakenative/VERSION')
|
|
138
|
+
assert File.exist?(build_path dir, 'lib/fakepure/res/icon.png')
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_libs_carry_standard_gems()
|
|
144
|
+
fake_libs do |root|
|
|
145
|
+
gems = {'fakegem' => [File.join(root, 'fakepure', 'lib')]}
|
|
146
|
+
packager standard_gems: gems do |pkg, dir|
|
|
147
|
+
pkg.generate
|
|
148
|
+
assert File.exist?(build_path dir, 'lib/gems/fakegem/lib/fakepure.rb')
|
|
149
|
+
assert_equal %w[fakepure fakenative fakebase fakegem], pkg.lib_names
|
|
150
|
+
assert_equal %w[fakepure fakenative fakebase gems/fakegem], pkg.load_dirs
|
|
151
|
+
|
|
152
|
+
# the ones this ruby has, looked for out of any bundle, with what they
|
|
153
|
+
# depend on: rexml has been a bundled gem since ruby 3.0, and rss
|
|
154
|
+
# depends on it
|
|
155
|
+
names = pkg.__send__(:standard_specs).map {_1['name']}
|
|
156
|
+
assert_include names, 'rexml'
|
|
157
|
+
assert_equal 1, names.count('rexml')
|
|
158
|
+
# but none of the tools for development
|
|
159
|
+
assert_empty names & Windows::DEVELOPMENT_GEMS
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_libs_carry_the_gems_of_the_gemfile()
|
|
165
|
+
# resolved by bundler: the default group, with what it depends on
|
|
166
|
+
gemfile = <<~RUBY
|
|
167
|
+
source 'https://rubygems.org'
|
|
168
|
+
gem 'test-unit'
|
|
169
|
+
group :test do
|
|
170
|
+
gem 'rake'
|
|
171
|
+
end
|
|
172
|
+
RUBY
|
|
173
|
+
# bundler/setup, which apps with a Gemfile often require, does nothing
|
|
174
|
+
main = <<~RUBY
|
|
175
|
+
require 'bundler/setup'
|
|
176
|
+
require 'test/unit'
|
|
177
|
+
File.write '../result', $LOADED_FEATURES.grep(%r{bundler/setup}).join
|
|
178
|
+
RUBY
|
|
179
|
+
fake_libs do
|
|
180
|
+
packager files: {'main.rb' => main, 'Gemfile' => gemfile} do |pkg, dir|
|
|
181
|
+
pkg.generate
|
|
182
|
+
assert File.exist?(build_path dir, 'lib/gems/test-unit/lib/test/unit.rb')
|
|
183
|
+
assert File.exist?(build_path dir, 'lib/gems/power_assert/lib/power_assert.rb')
|
|
184
|
+
assert !File.exist?(build_path dir, 'lib/gems/rake')
|
|
185
|
+
assert_include pkg.lib_names, 'test-unit'
|
|
186
|
+
assert_equal 'bundler', pkg.lib_names[pkg.library_names.size]
|
|
187
|
+
|
|
188
|
+
# out of the bundle the tests may run under
|
|
189
|
+
env = ENV.keys.grep(/\ABUNDLER?_/).to_h {[_1, nil]}.merge 'RUBYOPT' => nil
|
|
190
|
+
assert system(env, RbConfig.ruby, build_path(dir, 'lib/boot.rb'))
|
|
191
|
+
assert_equal File.realpath(build_path dir, 'lib/gems/bundler/lib/bundler/setup.rb'),
|
|
192
|
+
File.realpath(read dir, 'lib/result')
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# no bundler/setup of its own for an app with no Gemfile
|
|
197
|
+
fake_libs do
|
|
198
|
+
packager do |pkg, dir|
|
|
199
|
+
pkg.generate
|
|
200
|
+
assert !File.exist?(build_path dir, 'lib/gems/bundler')
|
|
201
|
+
assert_not_include pkg.lib_names, 'bundler'
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# what is left out, and what goes as it is
|
|
206
|
+
fake_libs do |root|
|
|
207
|
+
Dir.mktmpdir do |gems|
|
|
208
|
+
files = %w[
|
|
209
|
+
native/lib/native.rb ext/native/native.so
|
|
210
|
+
rexml/lib/rexml.rb fakebase/lib/fakebase/extension.rb
|
|
211
|
+
]
|
|
212
|
+
files.each do |file|
|
|
213
|
+
path = File.join gems, file
|
|
214
|
+
FileUtils.mkdir_p File.dirname(path)
|
|
215
|
+
File.write path, ''
|
|
216
|
+
end
|
|
217
|
+
spec = -> (name, *paths, default: false) {
|
|
218
|
+
{
|
|
219
|
+
'name' => name,
|
|
220
|
+
'root' => File.join(gems, name),
|
|
221
|
+
'default_gem' => default,
|
|
222
|
+
'require_paths' => paths.map {File.join gems, _1}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
specs = [
|
|
226
|
+
spec['native', 'native/lib', 'ext/native'],
|
|
227
|
+
spec['rexml', 'rexml/lib'],
|
|
228
|
+
spec['fakebase', 'fakebase/lib'],# a library, as it has extension.rb
|
|
229
|
+
spec['bundler', 'bundler/lib'],
|
|
230
|
+
spec['json', 'json/lib', default: true]
|
|
231
|
+
]
|
|
232
|
+
standard = {'rexml' => ['standard/rexml/lib'], 'ostruct' => [File.join(root, 'fakepure/lib')]}
|
|
233
|
+
packager standard_gems: standard do |pkg, dir|
|
|
234
|
+
pkg.define_singleton_method(:gemfile_specs) {specs}
|
|
235
|
+
pkg.generate
|
|
236
|
+
assert_equal %w[native rexml ostruct], pkg.gem_dirs.keys
|
|
237
|
+
assert_equal [File.join(gems, 'rexml/lib')], pkg.gem_dirs['rexml']
|
|
238
|
+
# an extension comes along, unlike the ones of the libraries
|
|
239
|
+
assert File.exist?(build_path dir, 'lib/gems/native/lib/native.so')
|
|
240
|
+
assert File.exist?(build_path dir, 'lib/gems/native/lib/native.rb')
|
|
241
|
+
end
|
|
242
|
+
# where a native extension is not supported
|
|
243
|
+
packager standard_gems: standard do |pkg, _|
|
|
244
|
+
pkg.define_singleton_method(:gemfile_specs) {specs}
|
|
245
|
+
pkg.define_singleton_method(:native_gems?) {false}
|
|
246
|
+
error = assert_raise(RP::Error) {pkg.gem_dirs}
|
|
247
|
+
assert_include error.message, "'native'"
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def test_main_cpp_manifest_and_rc()
|
|
254
|
+
fake_libs do
|
|
255
|
+
packager "version: 1.2.3.4.5\ncopyright: © 2026 Me\nlocalizations: {ja: {name: アプリ}}" do |pkg, dir|
|
|
256
|
+
stub(RP::Windows, :lcid, 0x0411) {pkg.generate}
|
|
257
|
+
str = read dir, 'src/main.cpp'
|
|
258
|
+
assert_include str, 'void Init_fakenative_ext ();'
|
|
259
|
+
assert_include str, 'ruby_init_ext("fakenative_ext.so", Init_fakenative_ext);'
|
|
260
|
+
assert_include str, 'L"lib\\\\boot.rb"'
|
|
261
|
+
|
|
262
|
+
str = read dir, 'src/app.manifest'
|
|
263
|
+
assert_include str, %(name="#{pkg.target}" version="1.2.3.0")
|
|
264
|
+
assert_include str, '<dependentAssembly>'
|
|
265
|
+
assert_include str, %(name="lib" version="1.0.0.0")
|
|
266
|
+
assert_include str, '<supportedOS '
|
|
267
|
+
assert_include str, '<activeCodePage '
|
|
268
|
+
assert_include str, '>UTF-8<'
|
|
269
|
+
|
|
270
|
+
rc = read dir, 'src/app.rc'
|
|
271
|
+
assert_include rc, %(1 24 "app.manifest")
|
|
272
|
+
assert_not_include rc, 'ICON'
|
|
273
|
+
assert !File.exist?(build_path dir, 'src/app.ico')
|
|
274
|
+
assert_include rc, 'FILEVERSION 1,2,3,4'
|
|
275
|
+
assert_include rc, 'PRODUCTVERSION 1,2,3,0'
|
|
276
|
+
assert_include rc, %(VALUE "FileVersion", "1.2.3.4.5")
|
|
277
|
+
assert_include rc, %(VALUE "ProductVersion", "1.2.3")
|
|
278
|
+
assert_include rc, %(VALUE "OriginalFilename", "#{pkg.target}.exe")
|
|
279
|
+
assert_include rc, %(VALUE "LegalCopyright", "© 2026 Me")
|
|
280
|
+
assert_include rc, %(BLOCK "041104B0")
|
|
281
|
+
assert_include rc, %(VALUE "ProductName", "アプリ")
|
|
282
|
+
assert_include rc, %(VALUE "Translation", 0x409, 1200, 0x411, 1200)
|
|
283
|
+
assert_equal 2, rc.scan(%(VALUE "LegalCopyright", "© 2026 Me")).size # in both of the languages
|
|
284
|
+
end
|
|
285
|
+
packager %(name: 'ア"プ\\リ'\nbundle_id: com.example.app) do |pkg, dir|
|
|
286
|
+
pkg.generate
|
|
287
|
+
rc = read dir, 'src/app.rc'
|
|
288
|
+
assert_include rc, %(VALUE "FileDescription", "ア""プ\\\\リ")
|
|
289
|
+
assert_include rc, %(VALUE "ProductName", "ア""プ\\\\リ")
|
|
290
|
+
end
|
|
291
|
+
packager "version: '2.3'" do |pkg, dir|
|
|
292
|
+
pkg.generate
|
|
293
|
+
rc = read dir, 'src/app.rc'
|
|
294
|
+
assert_include rc, 'FILEVERSION 2,3,0,0'
|
|
295
|
+
assert_include rc, 'PRODUCTVERSION 2,3,0,0'
|
|
296
|
+
assert_include rc, %(VALUE "FileVersion", "2.3")
|
|
297
|
+
assert_include rc, %(VALUE "ProductVersion", "2.3")
|
|
298
|
+
assert_not_include rc, 'LegalCopyright'
|
|
299
|
+
assert_include rc, %(BLOCK "040904B0")
|
|
300
|
+
assert_include rc, %(VALUE "Translation", 0x409, 1200)
|
|
301
|
+
end
|
|
302
|
+
packager "version: 1.2.65536" do |pkg, _|
|
|
303
|
+
assert_raise(RP::Error) {pkg.file_version}
|
|
304
|
+
end
|
|
305
|
+
packager do |pkg, _|
|
|
306
|
+
assert_equal 0x0411, pkg.langid('ja')
|
|
307
|
+
assert_equal 0x0804, pkg.langid('zh-Hans')
|
|
308
|
+
assert_raise(RP::Error) {pkg.langid 'x-y'}
|
|
309
|
+
end if Gem.win_platform? # asks windows
|
|
310
|
+
assert_equal '1.2.3.4', RP::Windows.four_numbers('1.2.3.4.65536') # the fifth is not used
|
|
311
|
+
packager do |pkg, _|
|
|
312
|
+
# templates see the packager and what render is given, nothing else
|
|
313
|
+
assert_empty pkg.__send__(:template_binding).local_variables
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# the icon: drawn by rays at each size, so fake the pngs here
|
|
317
|
+
pngs = [[16, "\x89PNG16".b], [256, "éPNG256"]]
|
|
318
|
+
packager "icon: icon.png", files: {'main.rb' => '', 'icon.png' => ''} do |pkg, dir|
|
|
319
|
+
pkg.define_singleton_method(:icon_pngs) {pngs}
|
|
320
|
+
pkg.generate
|
|
321
|
+
assert_include read(dir, 'src/app.rc'), %(1 ICON "app.ico")
|
|
322
|
+
|
|
323
|
+
ico = File.binread build_path(dir, 'src/app.ico')
|
|
324
|
+
a, b = pngs.map {_1.last.bytesize}
|
|
325
|
+
assert_equal [0, 1, 2], ico[0, 6].unpack('v3')
|
|
326
|
+
assert_equal [16, 16, 0, 0, 1, 32, a, 38], ico[6, 16].unpack('C4v2V2')
|
|
327
|
+
assert_equal [0, 0, 0, 0, 1, 32, b, 38 + a], ico[22, 16].unpack('C4v2V2')
|
|
328
|
+
assert_equal pngs.map {_1.last.b}.join, ico[38..]
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def test_boot_rb_starts_the_app()
|
|
334
|
+
main = <<~RUBY
|
|
335
|
+
require 'fakepure'
|
|
336
|
+
File.write '../result', [Dir.pwd, *$LOAD_PATH.first(4)].join("\\n")
|
|
337
|
+
RUBY
|
|
338
|
+
fake_libs do
|
|
339
|
+
packager files: {'main.rb' => main} do |pkg, dir|
|
|
340
|
+
pkg.generate
|
|
341
|
+
assert system(RbConfig.ruby, build_path(dir, 'lib/boot.rb'))
|
|
342
|
+
|
|
343
|
+
pwd, *paths = read(dir, 'lib/result').lines chomp: true
|
|
344
|
+
assert_equal File.realpath(build_path dir, 'lib/app'), File.realpath(pwd)
|
|
345
|
+
assert_equal pwd, paths.first
|
|
346
|
+
assert_equal %w[lib/fakepure/lib lib/fakenative/lib lib/fakebase/lib],
|
|
347
|
+
paths[1..].map {_1.split('/').last(3).join '/'}
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def test_boot_rb_reads_the_packed_app()
|
|
353
|
+
main = <<~RUBY
|
|
354
|
+
require 'fakepure'
|
|
355
|
+
require_relative 'sub'
|
|
356
|
+
File.write ENV['RESULT'], [__FILE__, $sub, File.read('data.txt'), *Dir.children('.').sort].join("\\n")
|
|
357
|
+
RUBY
|
|
358
|
+
files = {'main.rb' => main, 'sub.rb' => '$sub = :sub', 'data.txt' => 'data'}
|
|
359
|
+
fake_libs LIBS_WITH_REFLEX do
|
|
360
|
+
packager "files: [sub.rb, data.txt]", files: files, pack: true,
|
|
361
|
+
profile: profile(libraries: LIBS_WITH_REFLEX.keys) do |pkg, dir|
|
|
362
|
+
|
|
363
|
+
pkg.generate
|
|
364
|
+
assert_equal %w[data.bin data.txt], Dir.children(build_path dir, 'lib/app').sort # but the scripts as they are
|
|
365
|
+
assert_equal %w[data.txt main.rb sub.rb], Dir.children(File.join dir, '.build/app').sort # as they are
|
|
366
|
+
assert File.exist?(build_path dir, 'lib/reflex/lib/reflex/packager/data_file.rb')
|
|
367
|
+
assert File.exist?(build_path dir, 'lib/reflex/lib/reflex/packager/data_loader.rb')
|
|
368
|
+
|
|
369
|
+
assert system({'RESULT' => build_path(dir, 'lib/result')}, RbConfig.ruby, build_path(dir, 'lib/boot.rb'))
|
|
370
|
+
assert_equal %w[main.rb sub data data.bin data.txt], read(dir, 'lib/result').lines(chomp: true)
|
|
371
|
+
end
|
|
372
|
+
packager profile: profile(libraries: LIBS_WITH_REFLEX.keys) do |pkg, dir|
|
|
373
|
+
pkg.generate
|
|
374
|
+
assert_not_include read(dir, 'lib/boot.rb'), 'DataLoader'
|
|
375
|
+
assert !File.exist?(build_path dir, 'lib/reflex/lib/reflex/packager')
|
|
376
|
+
end
|
|
377
|
+
packager pack: true do |pkg, _| # with no reflex
|
|
378
|
+
assert_raise(RP::Error) {pkg.generate}
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def test_boot_rb_starts_boot_main()
|
|
384
|
+
fake_libs do
|
|
385
|
+
packager profile: profile(boot: "puts 1\n") do |pkg, dir|
|
|
386
|
+
pkg.generate
|
|
387
|
+
assert_include read(dir, 'lib/boot.rb'), '"__reflex_main__.rb"'
|
|
388
|
+
assert_equal "puts 1\n", read(dir, 'lib/app/__reflex_main__.rb')
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def test_boot_rb_shows_the_error()
|
|
394
|
+
# app/reflex.rb stands in for reflex, found first on the load path
|
|
395
|
+
reflex = <<~RUBY
|
|
396
|
+
module Reflex
|
|
397
|
+
def self.alert(message, title:) = File.write(ENV['ALERT'], "\#{title}\\n\#{message}")
|
|
398
|
+
end
|
|
399
|
+
RUBY
|
|
400
|
+
boot = -> (main, yaml = '', pack: false) {
|
|
401
|
+
files = {'main.rb' => main, 'reflex.rb' => reflex}
|
|
402
|
+
fake_libs LIBS_WITH_REFLEX do
|
|
403
|
+
packager "name: My App\nfiles: [reflex.rb]\n#{yaml}", files: files, pack: pack,
|
|
404
|
+
profile: profile(libraries: LIBS_WITH_REFLEX.keys) do |pkg, dir|
|
|
405
|
+
|
|
406
|
+
pkg.generate
|
|
407
|
+
env = {'ALERT' => build_path(dir, 'lib/alert')}
|
|
408
|
+
ok = system env, RbConfig.ruby, build_path(dir, 'lib/boot.rb'), err: File::NULL
|
|
409
|
+
alert = File.exist?(build_path dir, 'lib/alert') ? read(dir, 'lib/alert') : nil
|
|
410
|
+
return [ok, $?.exitstatus, alert]
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
ok, status, alert = boot["raise 'boom'"]
|
|
416
|
+
assert_equal [false, 1], [ok, status]
|
|
417
|
+
assert_equal 'My App', alert.lines.first.chomp
|
|
418
|
+
assert_include alert, 'boom (RuntimeError)'
|
|
419
|
+
assert_not_include alert, 'boot.rb'
|
|
420
|
+
assert_match(/^app\/main\.rb:1:/, alert) # from the directory of boot.rb
|
|
421
|
+
|
|
422
|
+
ok, status, alert = boot.call "raise 'boom'", pack: true
|
|
423
|
+
assert_equal [false, 1], [ok, status]
|
|
424
|
+
assert_include alert, 'boom (RuntimeError)'
|
|
425
|
+
assert_not_include alert, 'data_loader.rb'
|
|
426
|
+
assert_match(/^main\.rb:1:/, alert) # compiled with the paths in the app directory
|
|
427
|
+
|
|
428
|
+
assert_equal [false, 3, nil], boot['exit 3']
|
|
429
|
+
assert_equal [true, 0, nil], boot['']
|
|
430
|
+
assert_equal [false, 1, nil], boot["raise 'boom'", 'windows: {console: true}']
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def test_libraries_of_the_gemfile()
|
|
434
|
+
fake_libs do |root|
|
|
435
|
+
# a library the app lists in its Gemfile, as rays-video
|
|
436
|
+
video = File.join root, 'fakevideo'
|
|
437
|
+
%w[lib/fakevideo/extension.rb lib/fakevideo/ext.rb lib/libfakevideo.a ext/fakevideo/a.o]
|
|
438
|
+
.each {FileUtils.mkdir_p File.dirname(File.join video, _1); FileUtils.touch File.join(video, _1)}
|
|
439
|
+
File.write File.join(video, 'fakevideo.gemspec'),
|
|
440
|
+
"Gem::Specification.new {|s| s.name = 'fakevideo'; s.version = '1.0'}"
|
|
441
|
+
specs = [{
|
|
442
|
+
'name' => 'fakevideo',
|
|
443
|
+
'root' => video,
|
|
444
|
+
'default_gem' => false,
|
|
445
|
+
'require_paths' => [File.join(video, 'lib')]
|
|
446
|
+
}]
|
|
447
|
+
packager do |pkg, _|
|
|
448
|
+
stub pkg, :gemfile_specs, specs do
|
|
449
|
+
assert_equal %w[fakebase fakenative fakepure fakevideo], pkg.libraries.map(&:name)
|
|
450
|
+
assert_equal %w[fakenative_ext fakevideo_ext], pkg.extensions
|
|
451
|
+
assert_empty pkg.gem_dirs # linked as a library, not shipped as a gem
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# --- link --------------------------------------------------------------
|
|
458
|
+
|
|
459
|
+
def test_native_libraries()
|
|
460
|
+
fake_libs do
|
|
461
|
+
packager do |pkg, _|
|
|
462
|
+
assert_equal %w[fakebase fakenative], pkg.native_libraries.map(&:name)
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def test_link_command()
|
|
468
|
+
fake_libs do |root|
|
|
469
|
+
packager do |pkg, _|
|
|
470
|
+
cmd = pkg.link_command RBCONFIG
|
|
471
|
+
assert_equal %w[g++ -std=gnu++11 src/main.cpp src/app.res.o -o], cmd.first(5)
|
|
472
|
+
assert_equal "#{pkg.target}.exe", cmd[5]
|
|
473
|
+
|
|
474
|
+
objs = %w[a.o b.o].map {File.join root, 'fakenative/ext/fakenative', _1}
|
|
475
|
+
assert_equal objs, cmd.grep(/\.o\z/) - %w[src/app.res.o]
|
|
476
|
+
|
|
477
|
+
# every object of the archives, the ones depending on others first
|
|
478
|
+
from, to = cmd.index('-Wl,--whole-archive'), cmd.index('-Wl,--no-whole-archive')
|
|
479
|
+
assert_equal %w[fakenative/lib/libfakenative.a fakebase/lib/libfakebase.a]
|
|
480
|
+
.map {File.join root, _1}, cmd[(from + 1)...to]
|
|
481
|
+
assert_operator cmd.index(objs.last), :<, from
|
|
482
|
+
|
|
483
|
+
assert_include cmd, '-lx64-ucrt-ruby400'
|
|
484
|
+
# the c++ runtime is shipped as dlls, not linked in
|
|
485
|
+
assert_empty cmd.grep(/static|winpthread/)
|
|
486
|
+
assert_equal '-mwindows', cmd.last
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def test_link_command_with_console()
|
|
492
|
+
fake_libs do
|
|
493
|
+
packager "windows: {console: true}" do |pkg, _|
|
|
494
|
+
assert_not_include pkg.link_command(RBCONFIG), '-mwindows'
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def test_ext_objects_missing()
|
|
500
|
+
libs = LIBS.merge 'fakenative' => %w[lib/fakenative.rb lib/fakenative/ext.rb lib/libfakenative.a]
|
|
501
|
+
fake_libs libs do
|
|
502
|
+
packager do |pkg, _|
|
|
503
|
+
error = assert_raise(RP::Error) {pkg.ext_objects}
|
|
504
|
+
assert_include error.message, 'fakenative_ext'
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def test_system_libs()
|
|
510
|
+
makefile = <<~MAKEFILE
|
|
511
|
+
LOCAL_LIBS = -lrays.dll -lrucy -lxot -lstdc++
|
|
512
|
+
LIBS = $(LIBRUBYARG_SHARED) -lglew32 -lopengl32 -lgdi32 -lshell32 -lws2_32
|
|
513
|
+
DLDFLAGS = -L. -Wl,--out-implib=libfakenative.dll.a
|
|
514
|
+
MAKEFILE
|
|
515
|
+
assert_equal %w[glew32 opengl32 gdi32 shell32 ws2_32], Windows.makefile_libs(makefile)
|
|
516
|
+
assert_equal [], Windows.makefile_libs('')
|
|
517
|
+
|
|
518
|
+
# from the Makefiles the gem builds of the native libraries leave
|
|
519
|
+
fake_libs do |root|
|
|
520
|
+
File.write File.join(root, 'fakenative/ext/fakenative/Makefile'), makefile
|
|
521
|
+
packager do |pkg, _|
|
|
522
|
+
assert_equal %w[glew32 opengl32 gdi32 shell32 ws2_32], pkg.system_libs
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def test_compiler_and_tools()
|
|
528
|
+
packager profile: profile(libraries: []) do |pkg, _|
|
|
529
|
+
assert_equal %w[g++ -std=gnu++11], pkg.compiler(RBCONFIG)
|
|
530
|
+
assert_equal %w[g++], pkg.compiler('CXX' => 'g++')
|
|
531
|
+
|
|
532
|
+
with_env 'PATH' => '' do
|
|
533
|
+
error = assert_raise(RP::Error) {pkg.__send__ :check_tools, pkg.tools}
|
|
534
|
+
assert_include error.message, 'windres'
|
|
535
|
+
assert_include error.message, 'objdump'
|
|
536
|
+
assert_include error.message, Windows::TOOLCHAIN_HINT
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
# --- runtime -----------------------------------------------------------
|
|
542
|
+
|
|
543
|
+
def with_ruby(&block)
|
|
544
|
+
Dir.mktmpdir do |ruby|
|
|
545
|
+
%w[
|
|
546
|
+
bin/x64-ucrt-ruby400.dll
|
|
547
|
+
bin/ruby_builtin_dlls/libgmp-10.dll
|
|
548
|
+
bin/ruby_builtin_dlls/libwinpthread-1.dll
|
|
549
|
+
bin/ruby_builtin_dlls/ruby_builtin_dlls.manifest
|
|
550
|
+
lib/ruby/4.0.0/json.rb
|
|
551
|
+
lib/ruby/4.0.0/x64-mingw-ucrt/json/ext/parser.so
|
|
552
|
+
msys64/ucrt64/bin/glew32.dll
|
|
553
|
+
msys64/ucrt64/bin/libstdc++-6.dll
|
|
554
|
+
msys64/ucrt64/bin/libgcc_s_seh-1.dll
|
|
555
|
+
msys64/ucrt64/bin/libwinpthread-1.dll
|
|
556
|
+
windows/System32/KERNEL32.dll
|
|
557
|
+
windows/System32/OPENGL32.dll
|
|
558
|
+
].each do |path|
|
|
559
|
+
path = File.join ruby, path
|
|
560
|
+
FileUtils.mkdir_p File.dirname(path)
|
|
561
|
+
File.write path, path
|
|
562
|
+
end
|
|
563
|
+
# the compiler, which the dlls of the toolchain are beside
|
|
564
|
+
# with .exe on windows, which takes nothing else as executable
|
|
565
|
+
compiler = File.join ruby, "msys64/ucrt64/bin/fake-g++#{'.exe' if Gem.win_platform?}"
|
|
566
|
+
File.write compiler, ''
|
|
567
|
+
File.chmod 0755, compiler
|
|
568
|
+
rbconfig = {
|
|
569
|
+
'CXX' => 'fake-g++ -std=gnu++11',
|
|
570
|
+
'bindir' => File.join(ruby, 'bin'),
|
|
571
|
+
'LIBRUBY_SO' => 'x64-ucrt-ruby400.dll',
|
|
572
|
+
'rubylibprefix' => File.join(ruby, 'lib/ruby'),
|
|
573
|
+
'ruby_version' => '4.0.0'
|
|
574
|
+
}
|
|
575
|
+
with_env 'SystemRoot' => File.join(ruby, 'windows') do
|
|
576
|
+
block.call rbconfig, File.join(ruby, 'msys64/ucrt64/bin')
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# What objdump would find in the import tables, by file name.
|
|
582
|
+
IMPORTS = {
|
|
583
|
+
'exe' => %w[
|
|
584
|
+
x64-ucrt-ruby400.dll glew32.dll libstdc++-6.dll KERNEL32.dll
|
|
585
|
+
api-ms-win-crt-heap-l1-1-0.dll],
|
|
586
|
+
'glew32.dll' => %w[OPENGL32.dll KERNEL32.dll],
|
|
587
|
+
'libstdc++-6.dll' => %w[libgcc_s_seh-1.dll libwinpthread-1.dll KERNEL32.dll],
|
|
588
|
+
'libgcc_s_seh-1.dll' => %w[libwinpthread-1.dll],
|
|
589
|
+
'libwinpthread-1.dll' => %w[KERNEL32.dll]
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
def fake_imports(pkg, imports = IMPORTS)
|
|
593
|
+
pkg.define_singleton_method(:dll_imports) do |path|
|
|
594
|
+
imports[path.end_with?('.exe') ? 'exe' : File.basename(path)] || []
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
def test_dist_paths()
|
|
599
|
+
packager "name: My App\nversion: 1.2.3" do |pkg, dir|
|
|
600
|
+
dist = File.join dir, 'dist/generic'
|
|
601
|
+
assert_equal "#{dist}/MyApp", pkg.dist_path
|
|
602
|
+
assert_match %r{^#{dist}/MyApp-1\.2\.3-windows-(x64|arm64)\.zip$}, pkg.archive_path
|
|
603
|
+
assert_equal 'windows-x64', pkg.archive_platform('host_cpu' => 'x86_64')
|
|
604
|
+
assert_equal 'windows-arm64', pkg.archive_platform('host_cpu' => 'aarch64')
|
|
605
|
+
|
|
606
|
+
with_env 'SystemRoot' => 'C:\Windows' do
|
|
607
|
+
assert_equal [
|
|
608
|
+
'C:\Windows/System32/tar.exe', '-a', '-c', '-f', pkg.archive_path, '-C', dist, 'MyApp'
|
|
609
|
+
], pkg.archive_command
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def test_copy_runtime()
|
|
615
|
+
with_ruby do |rbconfig, msys_bin|
|
|
616
|
+
packager profile: profile(libraries: []) do |pkg, dir|
|
|
617
|
+
fake_imports pkg
|
|
618
|
+
# another toolchain earlier in PATH, as the one of git
|
|
619
|
+
other = File.join dir, 'other/bin'
|
|
620
|
+
FileUtils.mkdir_p other
|
|
621
|
+
File.write File.join(other, 'libstdc++-6.dll'), 'other'
|
|
622
|
+
with_env 'PATH' => [other, msys_bin].join(File::PATH_SEPARATOR) do
|
|
623
|
+
pkg.copy_runtime dir, rbconfig
|
|
624
|
+
end
|
|
625
|
+
# the one beside the compiler
|
|
626
|
+
assert_equal File.join(msys_bin, 'libstdc++-6.dll'),
|
|
627
|
+
File.read(File.join dir, 'lib/libstdc++-6.dll')
|
|
628
|
+
|
|
629
|
+
# a compiler given by its path, wherever PATH points
|
|
630
|
+
abs = rbconfig.merge 'CXX' => File.join(msys_bin, 'fake-g++')
|
|
631
|
+
with_env 'PATH' => other do
|
|
632
|
+
assert_equal msys_bin, pkg.__send__(:toolchain_dir, abs)
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
%w[
|
|
636
|
+
lib/x64-ucrt-ruby400.dll
|
|
637
|
+
lib/ruby_builtin_dlls/ruby_builtin_dlls.manifest
|
|
638
|
+
lib/ruby_builtin_dlls/libgmp-10.dll
|
|
639
|
+
lib/glew32.dll
|
|
640
|
+
lib/lib.manifest
|
|
641
|
+
lib/ruby/4.0.0/json.rb
|
|
642
|
+
lib/ruby/4.0.0/x64-mingw-ucrt/json/ext/parser.so
|
|
643
|
+
].each do |path|
|
|
644
|
+
assert File.exist?(File.join dir, path), "missing #{path}"
|
|
645
|
+
end
|
|
646
|
+
# the ruby dll resolves them through the manifest in the directory
|
|
647
|
+
assert !File.exist?(File.join dir, 'lib/libgmp-10.dll')
|
|
648
|
+
|
|
649
|
+
manifest = File.read File.join(dir, 'lib/lib.manifest')
|
|
650
|
+
assert_include manifest, %(name="lib" version="1.0.0.0")
|
|
651
|
+
# the ones of the toolchain the executable loads, directly or through
|
|
652
|
+
# another one, but none of windows
|
|
653
|
+
assert_equal %w[
|
|
654
|
+
x64-ucrt-ruby400.dll
|
|
655
|
+
glew32.dll libstdc++-6.dll libgcc_s_seh-1.dll libwinpthread-1.dll
|
|
656
|
+
], manifest.scan(/<file name="(.+?)"/).flatten
|
|
657
|
+
# the one of the toolchain for the executable, besides the one of ruby
|
|
658
|
+
assert File.exist?(File.join dir, 'lib/libwinpthread-1.dll')
|
|
659
|
+
assert File.exist?(File.join dir, 'lib/ruby_builtin_dlls/libwinpthread-1.dll')
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
def test_copy_runtime_without_system_dll()
|
|
665
|
+
with_ruby do |rbconfig, msys_bin|
|
|
666
|
+
packager profile: profile(libraries: []) do |pkg, dir|
|
|
667
|
+
# the toolchain without openal installed
|
|
668
|
+
fake_imports pkg, IMPORTS.merge('exe' => [*IMPORTS['exe'], 'libopenal-1.dll'])
|
|
669
|
+
with_env 'PATH' => msys_bin do
|
|
670
|
+
error = assert_raise(RP::Error) {pkg.copy_runtime dir, rbconfig}
|
|
671
|
+
assert_include error.message, "'libopenal-1.dll' needed by #{pkg.target}.exe"
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
# one a dll of the toolchain needs
|
|
675
|
+
packager profile: profile(libraries: []) do |pkg, dir|
|
|
676
|
+
fake_imports pkg, IMPORTS.merge('libgcc_s_seh-1.dll' => %w[libnone-1.dll])
|
|
677
|
+
with_env 'PATH' => msys_bin do
|
|
678
|
+
error = assert_raise(RP::Error) {pkg.copy_runtime dir, rbconfig}
|
|
679
|
+
assert_include error.message, "'libnone-1.dll' needed by libgcc_s_seh-1.dll"
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
end# TestPackagerWindows
|