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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
require 'erb'
|
|
2
2
|
require 'fileutils'
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'shellwords'
|
|
5
|
+
require 'tmpdir'
|
|
3
6
|
|
|
4
7
|
|
|
5
8
|
module Reflex
|
|
@@ -14,8 +17,36 @@ module Reflex
|
|
|
14
17
|
#
|
|
15
18
|
class Platform
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
class << self
|
|
21
|
+
|
|
22
|
+
# Copies the app in +dir+ to +dest+, with the Ruby scripts in its data
|
|
23
|
+
# file in place of them, compiled by DataCompiler with the Ruby running
|
|
24
|
+
# this, and the other files as they are.
|
|
25
|
+
#
|
|
26
|
+
# Unless +compile+, the data file is left out, for the package to
|
|
27
|
+
# have it written by the Ruby in it.
|
|
28
|
+
#
|
|
29
|
+
def pack_app(dir, dest, compile: true)
|
|
30
|
+
files = Dir.glob('**/*', base: dir).select {File.file? File.join(dir, _1)}.sort
|
|
31
|
+
scripts = DataCompiler.scripts dir
|
|
32
|
+
|
|
33
|
+
FileUtils.mkdir_p dest
|
|
34
|
+
DataCompiler.write dir, File.join(dest, DataLoader::DATA_FILE) if compile
|
|
35
|
+
|
|
36
|
+
(files - scripts).each do |name|
|
|
37
|
+
path = File.join dest, name
|
|
38
|
+
FileUtils.mkdir_p File.dirname(path)
|
|
39
|
+
FileUtils.cp File.join(dir, name), path
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end# self
|
|
44
|
+
|
|
45
|
+
def initialize(
|
|
46
|
+
config, verbose: false, pack: false, notarize: [], generic: false, appstore: false)
|
|
47
|
+
|
|
48
|
+
@config, @verbose, @pack, @notarize = config, verbose, pack, notarize || []
|
|
49
|
+
@generic, @appstore = generic, appstore
|
|
19
50
|
end
|
|
20
51
|
|
|
21
52
|
attr_reader :config
|
|
@@ -28,6 +59,40 @@ module Reflex
|
|
|
28
59
|
@verbose
|
|
29
60
|
end
|
|
30
61
|
|
|
62
|
+
# Whether to put the files of the app together in a data file, which
|
|
63
|
+
# the package reads them from, as for a release.
|
|
64
|
+
#
|
|
65
|
+
def pack?()
|
|
66
|
+
@pack
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The steps of the notarization, having Apple check the app for the
|
|
70
|
+
# system to open it without warnings when downloaded, as for a release:
|
|
71
|
+
# :submit to send the app, and :export to export the app notarized,
|
|
72
|
+
# waiting for it.
|
|
73
|
+
#
|
|
74
|
+
def notarize()
|
|
75
|
+
@notarize
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def notarize?()
|
|
79
|
+
!@notarize.empty?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Whether to package the generic app, for no store in particular: when
|
|
83
|
+
# asked, or notarized, which only the generic one is, or when no store
|
|
84
|
+
# is asked for.
|
|
85
|
+
#
|
|
86
|
+
def generic?()
|
|
87
|
+
@generic || notarize? || !appstore?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Whether to package the app for the Mac App Store.
|
|
91
|
+
#
|
|
92
|
+
def appstore?()
|
|
93
|
+
@appstore
|
|
94
|
+
end
|
|
95
|
+
|
|
31
96
|
# Package the application as a distributable bundle.
|
|
32
97
|
#
|
|
33
98
|
# @param [Boolean] generate_only generate the project files but do
|
|
@@ -38,17 +103,218 @@ module Reflex
|
|
|
38
103
|
build unless generate_only
|
|
39
104
|
end
|
|
40
105
|
|
|
106
|
+
# Generates the files to build the app from, in build_dir.
|
|
107
|
+
#
|
|
108
|
+
def generate()
|
|
109
|
+
raise "#{self.class} must implement #{__method__}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Builds the app from the files generated, into dist_path.
|
|
113
|
+
#
|
|
114
|
+
def build()
|
|
115
|
+
raise "#{self.class} must implement #{__method__}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The directory of what the packager makes for the platform.
|
|
119
|
+
#
|
|
120
|
+
def platform_dir()
|
|
121
|
+
File.join config.dir, '.build', platform_name
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The directory the app is generated and built in.
|
|
125
|
+
#
|
|
126
|
+
def build_dir()
|
|
127
|
+
platform_dir
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# The variant of the package, which the directory of the package is
|
|
131
|
+
# named after: generic, for no store in particular.
|
|
132
|
+
#
|
|
133
|
+
def variant()
|
|
134
|
+
'generic'
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# The directory of the package of the variant, in dist/.
|
|
138
|
+
#
|
|
139
|
+
def dist_dir(variant = self.variant)
|
|
140
|
+
File.join config.dir, 'dist', variant
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# The packaged app in dist_dir.
|
|
144
|
+
#
|
|
145
|
+
def dist_path()
|
|
146
|
+
File.join dist_dir, target
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# The packages of the app for the stores, as {store => path}, each of
|
|
150
|
+
# which --<store> packages, in the dist directory of the store.
|
|
151
|
+
#
|
|
152
|
+
def store_package_paths()
|
|
153
|
+
{}
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# The command to zip the packaged app into archive_path.
|
|
157
|
+
#
|
|
158
|
+
def archive_command()
|
|
159
|
+
raise "#{self.class} must implement #{__method__}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# The zip of the packaged app to hand out, in dist_dir.
|
|
163
|
+
#
|
|
164
|
+
def archive_path()
|
|
165
|
+
File.join dist_dir, "#{target}-#{config.version}-#{archive_platform}.zip"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# The platform and the architecture the packaged app runs on, as the
|
|
169
|
+
# name of its zip tells them.
|
|
170
|
+
#
|
|
171
|
+
def archive_platform()
|
|
172
|
+
platform_name
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Returns the app name without characters unsafe for target, scheme or
|
|
176
|
+
# file names.
|
|
177
|
+
#
|
|
178
|
+
def target()
|
|
179
|
+
config.name.gsub(/[^A-Za-z0-9_\-]+/, '').then {_1.empty? ? 'App' : _1}
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# The script the app starts with, in the app directory: the boot script
|
|
183
|
+
# of the profile, or the main script of the app.
|
|
184
|
+
#
|
|
185
|
+
def start_script()
|
|
186
|
+
profile.boot_main || config.main
|
|
187
|
+
end
|
|
188
|
+
|
|
41
189
|
private
|
|
42
190
|
|
|
43
|
-
|
|
191
|
+
# The name of the platform, for the build directory, the templates and
|
|
192
|
+
# the zip of the app.
|
|
193
|
+
#
|
|
194
|
+
def platform_name()
|
|
195
|
+
raise "#{self.class} must implement #{__method__}"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Left out when copying a library: a gem build leaves its binaries in
|
|
199
|
+
# lib/, while a package links the extensions in, and the Ruby of a
|
|
200
|
+
# windows package would even prefer a rays_ext.so on the load path to
|
|
201
|
+
# the one linked in.
|
|
202
|
+
#
|
|
203
|
+
BINARY_EXTS = %w[.so .dll .a .o .bundle]
|
|
204
|
+
|
|
205
|
+
# Copies the Ruby code of a library and what it reads at run time into
|
|
206
|
+
# +dest+: lib/ without the binaries, VERSION and res/.
|
|
207
|
+
#
|
|
208
|
+
def copy_library(root, dest)
|
|
209
|
+
copy_tree File.join(root, 'lib'), File.join(dest, 'lib')
|
|
210
|
+
%w[VERSION res].map {File.join root, _1}.select {File.exist? _1}.each do |path|
|
|
211
|
+
FileUtils.mkdir_p dest
|
|
212
|
+
FileUtils.cp_r path, dest
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def copy_tree(src, dest)
|
|
217
|
+
Dir.glob('**/*', base: src).each do |path|
|
|
218
|
+
from = File.join src, path
|
|
219
|
+
next if File.directory?(from) || BINARY_EXTS.include?(File.extname path)
|
|
220
|
+
to = File.join dest, path
|
|
221
|
+
FileUtils.mkdir_p File.dirname(to)
|
|
222
|
+
FileUtils.cp from, to
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# The files of the app, as they are, for any platform.
|
|
227
|
+
#
|
|
228
|
+
def app_dir()
|
|
229
|
+
File.join config.dir, '.build', 'app'
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Copies the files of the app to app_dir, and to +dir+ in the build
|
|
233
|
+
# directory, as they are, or packed with what reads its data file in
|
|
234
|
+
# the reflex library of the package, if the app is to be packed.
|
|
235
|
+
#
|
|
236
|
+
def copy_app_files(dir = 'app')
|
|
237
|
+
FileUtils.rm_rf app_dir
|
|
238
|
+
FileUtils.mkdir_p app_dir
|
|
239
|
+
config.app_files.each do |file|
|
|
240
|
+
dest = File.join app_dir, file
|
|
241
|
+
FileUtils.mkdir_p File.dirname(dest)
|
|
242
|
+
FileUtils.cp File.join(config.dir, file), dest
|
|
243
|
+
end
|
|
244
|
+
File.write File.join(app_dir, profile.boot_main), profile.boot if
|
|
245
|
+
profile.boot_main && profile.boot
|
|
246
|
+
|
|
247
|
+
dir = File.join build_dir, dir
|
|
248
|
+
FileUtils.rm_rf dir
|
|
249
|
+
FileUtils.mkdir_p dir
|
|
250
|
+
if pack?
|
|
251
|
+
raise Error, 'a packed app needs reflex in its libraries' unless
|
|
252
|
+
libraries.any? {_1.name == 'reflex'}
|
|
253
|
+
Platform.pack_app app_dir, dir, compile: compile_on_generate?
|
|
254
|
+
loader = File.join library_lib_dir('reflex'), 'reflex', 'packager'
|
|
255
|
+
FileUtils.mkdir_p loader
|
|
256
|
+
%w[data_file.rb data_loader.rb].each {FileUtils.cp File.join(__dir__, _1), loader}
|
|
257
|
+
else
|
|
258
|
+
FileUtils.cp_r File.join(app_dir, '.'), dir
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Whether the scripts of a packed app are compiled when generating, by
|
|
263
|
+
# the Ruby running this, which is the one the package runs them on.
|
|
264
|
+
#
|
|
265
|
+
def compile_on_generate?()
|
|
266
|
+
true
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def write(path, content)
|
|
270
|
+
path = File.expand_path path, build_dir
|
|
271
|
+
FileUtils.mkdir_p File.dirname(path)
|
|
272
|
+
File.write path, content
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Renders the template with the packager's methods, and +vars+ as
|
|
276
|
+
# local variables.
|
|
277
|
+
#
|
|
278
|
+
def render(template, **vars)
|
|
44
279
|
path = File.join TEMPLATES_DIR, platform_name, template
|
|
45
|
-
|
|
280
|
+
b = template_binding
|
|
281
|
+
vars.each {|name, value| b.local_variable_set name, value}
|
|
282
|
+
ERB.new(File.read(path), trim_mode: '-').result b
|
|
46
283
|
end
|
|
47
284
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
285
|
+
# A binding with the packager's methods and nothing else: a template
|
|
286
|
+
# sees every local variable of the method its binding is made in.
|
|
287
|
+
#
|
|
288
|
+
def template_binding()
|
|
289
|
+
binding
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Runs +cmd+, dropping what it prints if +quiet+ unless verbose, though
|
|
293
|
+
# not the errors it tells. The command is run as it is, not split by
|
|
294
|
+
# spaces even if it is only a path, as of the app named with spaces.
|
|
295
|
+
#
|
|
296
|
+
def run(*cmd, chdir: nil, env: {}, quiet: false)
|
|
297
|
+
puts "==> #{cmd.shelljoin}"
|
|
298
|
+
out = quiet && !verbose? ? {out: File::NULL} : {}
|
|
299
|
+
ok = with_dir(chdir) {system env, [cmd.first, cmd.first], *cmd.drop(1), chdir: _1, **out}
|
|
300
|
+
return if ok
|
|
301
|
+
raise Error, "command failed: #{cmd.shelljoin}"
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Runs +cmd+, returning what it prints and whether it succeeded, for
|
|
305
|
+
# the caller to tell what failed.
|
|
306
|
+
#
|
|
307
|
+
def capture(*cmd, chdir: nil)
|
|
308
|
+
out, status = with_dir(chdir) {Open3.capture2e(*cmd, chdir: _1)}
|
|
309
|
+
[out, status.success?]
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Calls the block with +dir+ for a command to run in, or with no +dir+,
|
|
313
|
+
# a temporary directory removed after, not for the command to depend
|
|
314
|
+
# on where the packager is run from.
|
|
315
|
+
#
|
|
316
|
+
def with_dir(dir, &block)
|
|
317
|
+
dir ? block.call(dir) : Dir.mktmpdir(&block)
|
|
52
318
|
end
|
|
53
319
|
|
|
54
320
|
def check_tools(tools)
|
|
@@ -60,8 +326,21 @@ module Reflex
|
|
|
60
326
|
end
|
|
61
327
|
|
|
62
328
|
def executable?(name)
|
|
63
|
-
|
|
64
|
-
|
|
329
|
+
!!find_executable(name)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Windows finds 'g++' as 'g++.exe' through PATHEXT, which is separated
|
|
333
|
+
# by ';' whatever the platform.
|
|
334
|
+
#
|
|
335
|
+
def find_executable(name)
|
|
336
|
+
exts = ['', *ENV['PATHEXT'].to_s.split(';')]
|
|
337
|
+
ENV['PATH'].to_s.split(File::PATH_SEPARATOR).each do |dir|
|
|
338
|
+
exts.each do |ext|
|
|
339
|
+
path = File.join dir, "#{name}#{ext}"
|
|
340
|
+
return path if File.file?(path) && File.executable?(path)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
nil
|
|
65
344
|
end
|
|
66
345
|
|
|
67
346
|
end# Platform
|
|
@@ -1,48 +1,64 @@
|
|
|
1
|
+
require 'reflex/packager/library'
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
module Reflex
|
|
2
5
|
|
|
3
6
|
|
|
4
7
|
module Packager
|
|
5
8
|
|
|
6
9
|
|
|
7
|
-
# Describes the runtime a packaged app embeds: the
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
+
# Describes the runtime a packaged app embeds: the gem it is made of, the
|
|
11
|
+
# libraries and extensions the gem depends on, and the scaffold for the
|
|
12
|
+
# 'new' command.
|
|
10
13
|
#
|
|
11
14
|
# The packager itself is runtime-agnostic; each gem (reflex, rubysketch,
|
|
12
15
|
# ...) supplies its own profile and reuses this packager as the engine.
|
|
13
16
|
#
|
|
14
17
|
class Profile
|
|
15
18
|
|
|
16
|
-
# @param [
|
|
17
|
-
# @param [String] git umbrella pod git repository
|
|
18
|
-
# @param [String] version umbrella pod version (for the tag)
|
|
19
|
-
# @param [Array<String>] libraries ruby lib bundles to add to load path
|
|
20
|
-
# @param [Array<String>] extensions native exts to register (Init_<name>)
|
|
19
|
+
# @param [Module] extension Extension of the gem (e.g. Reflex::Extension)
|
|
21
20
|
# @param [Array<String>] config_files config file names, preferred first
|
|
22
21
|
# @param [Hash{String=>String}] templates 'new' scaffold files ({filename => content})
|
|
23
|
-
# @param [String, nil] command CLI command name (default:
|
|
22
|
+
# @param [String, nil] command CLI command name (default: the name in lower case)
|
|
24
23
|
# @param [String, nil] boot boot script overrides config main (default: nil)
|
|
25
24
|
#
|
|
26
|
-
def initialize(
|
|
27
|
-
|
|
28
|
-
command: nil, boot: nil)
|
|
29
|
-
|
|
30
|
-
@pod = pod
|
|
31
|
-
@git = git
|
|
32
|
-
@version = version
|
|
33
|
-
@libraries = libraries
|
|
34
|
-
@extensions = extensions
|
|
25
|
+
def initialize(extension:, config_files:, templates:, command: nil, boot: nil)
|
|
26
|
+
@extension = extension
|
|
35
27
|
@config_files = config_files
|
|
36
28
|
@templates = templates
|
|
37
29
|
@command = command
|
|
38
30
|
@boot = boot
|
|
39
31
|
end
|
|
40
32
|
|
|
41
|
-
attr_reader :
|
|
42
|
-
|
|
33
|
+
attr_reader :extension, :config_files, :templates, :boot
|
|
34
|
+
|
|
35
|
+
# The name of the gem (e.g. 'Reflex').
|
|
36
|
+
#
|
|
37
|
+
def name()
|
|
38
|
+
extension.name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def version()
|
|
42
|
+
extension.version
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The libraries the gem depends on, and the gem itself, each after the
|
|
46
|
+
# ones it depends on.
|
|
47
|
+
#
|
|
48
|
+
# @return [Array<Library>] libraries
|
|
49
|
+
#
|
|
50
|
+
def libraries()
|
|
51
|
+
@libraries ||= Library.collect extension.root_dir
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Native extensions to register (Init_<name>).
|
|
55
|
+
#
|
|
56
|
+
def extensions()
|
|
57
|
+
libraries.filter_map(&:extension)
|
|
58
|
+
end
|
|
43
59
|
|
|
44
60
|
def command()
|
|
45
|
-
@command ||
|
|
61
|
+
@command || name.downcase
|
|
46
62
|
end
|
|
47
63
|
|
|
48
64
|
def main()
|
|
@@ -53,12 +69,8 @@ module Reflex
|
|
|
53
69
|
@boot ? '__reflex_main__.rb' : nil
|
|
54
70
|
end
|
|
55
71
|
|
|
56
|
-
def pod_key()
|
|
57
|
-
pod.downcase.to_sym
|
|
58
|
-
end
|
|
59
|
-
|
|
60
72
|
def bundle_id_prefix()
|
|
61
|
-
"org.xord.#{
|
|
73
|
+
"org.xord.#{name.downcase}"
|
|
62
74
|
end
|
|
63
75
|
|
|
64
76
|
end# Profile
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'reflex/extension'
|
|
2
|
+
require 'reflex/packager/profile'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
module Reflex
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module Packager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# The profile to package an app of Reflex with.
|
|
12
|
+
#
|
|
13
|
+
REFLEX = Profile.new(
|
|
14
|
+
extension: Reflex::Extension,
|
|
15
|
+
config_files: %w[reflex.yml reflex.yaml],
|
|
16
|
+
templates: {'main.rb': <<~MAIN, 'reflex.yml': <<~CONFIG})
|
|
17
|
+
require 'reflex'
|
|
18
|
+
|
|
19
|
+
view = Reflex::View.new
|
|
20
|
+
def view.on_draw(e)
|
|
21
|
+
e.painter.font nil, 48
|
|
22
|
+
e.painter.text 'hello, world!', 100, 100
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
win = Reflex::Window.new
|
|
26
|
+
win.title = '{{name}}'
|
|
27
|
+
win.frame = [100, 100, 400, 300]
|
|
28
|
+
win.add view
|
|
29
|
+
win.show
|
|
30
|
+
|
|
31
|
+
Reflex.start
|
|
32
|
+
MAIN
|
|
33
|
+
name: {{name}}
|
|
34
|
+
version: 1.0.0
|
|
35
|
+
#bundle_id: org.xord.reflex.example.{{name_id}}
|
|
36
|
+
#icon: icon.png
|
|
37
|
+
CONFIG
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
end# Packager
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
end# Reflex
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require 'rbconfig'
|
|
2
|
+
require 'xot/rake'
|
|
3
|
+
require 'reflex/packager'
|
|
4
|
+
require 'reflex/packager/profiles/reflex'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module Reflex
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
module Packager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
module Rake
|
|
14
|
+
|
|
15
|
+
def package_application()
|
|
16
|
+
config = Config.load REFLEX, '.'
|
|
17
|
+
platform = PLATFORMS[Packager.default_platform].new config, pack: true
|
|
18
|
+
app_path = platform.dist_path
|
|
19
|
+
archive_path = platform.archive_path
|
|
20
|
+
tag = "v#{config.version}"
|
|
21
|
+
srcs = [
|
|
22
|
+
*config.app_files,
|
|
23
|
+
*REFLEX.config_files,
|
|
24
|
+
'VERSION',
|
|
25
|
+
'Gemfile.lock'
|
|
26
|
+
].select {File.file? _1}
|
|
27
|
+
|
|
28
|
+
relative = -> path {path.delete_prefix "#{config.dir}/"}
|
|
29
|
+
reflex_package = -> *args do
|
|
30
|
+
reflex = File.expand_path '../../../bin/reflex', __dir__
|
|
31
|
+
sh RbConfig.ruby, reflex, 'package', '--pack', *args, '.'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "package the application into #{relative[app_path]}"
|
|
35
|
+
alias_task :package => app_path
|
|
36
|
+
|
|
37
|
+
desc "archive the application into #{relative[archive_path]}"
|
|
38
|
+
alias_task :archive => archive_path
|
|
39
|
+
|
|
40
|
+
namespace :package do
|
|
41
|
+
platform.store_package_paths.each do |store, path|
|
|
42
|
+
desc "package the application for #{store} into #{relative[path]}"
|
|
43
|
+
alias_task store => path
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
desc "release the application archived or packaged on GitHub, by the tag #{tag}"
|
|
48
|
+
task :release do
|
|
49
|
+
ref = ENV['GITHUB_REF_NAME']
|
|
50
|
+
raise "#{ref} is not the tag of the version, #{tag}" if ref && ref != tag
|
|
51
|
+
paths = [archive_path, *platform.store_package_paths.values].select {File.file? _1}
|
|
52
|
+
raise 'nothing to release, archive or package the application first' if paths.empty?
|
|
53
|
+
release_on_github(*paths, tag: tag, notes: release_notes(tag))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
namespace :upload do
|
|
57
|
+
desc "upload #{relative[archive_path]} to itch.io"
|
|
58
|
+
task :itchio => archive_path do
|
|
59
|
+
project = config.upload.itchio_project ||
|
|
60
|
+
raise("no upload.itchio.project in #{REFLEX.config_files.first}")
|
|
61
|
+
Stores::Itchio.new(project).upload archive_path,
|
|
62
|
+
channel: Packager.default_platform.to_s, version: config.version
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
platform.store_package_paths[:appstore]&.then do |pkg_path|
|
|
66
|
+
desc "upload #{relative[pkg_path]} to App Store Connect"
|
|
67
|
+
task :appstore => pkg_path do
|
|
68
|
+
Stores::AppStore.new.upload pkg_path, platform: Packager.default_platform.to_s
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
task :clean do
|
|
74
|
+
rm_rf File.dirname(platform.dist_dir)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
task :clobber => :clean do
|
|
78
|
+
rm_rf File.dirname(platform.platform_dir)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
file app_path => srcs do
|
|
82
|
+
reflex_package.call
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
file archive_path => app_path do
|
|
86
|
+
sh(*platform.archive_command)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
platform.store_package_paths.each do |store, path|
|
|
90
|
+
file path => srcs do
|
|
91
|
+
reflex_package.call "--#{store}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def release_notes(tag)
|
|
99
|
+
commit = git('-C', Reflex::Extension.root_dir, 'rev-parse', 'HEAD', err: File::NULL).strip
|
|
100
|
+
[
|
|
101
|
+
Xot::ChangeLog.new.changes(tag),
|
|
102
|
+
("Built with xord/all@#{commit}." unless commit.empty?)
|
|
103
|
+
].compact.join("\n\n")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end# Rake
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
end# Packager
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
end# Reflex
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
include Reflex::Packager::Rake
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'shellwords'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Reflex
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module Packager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# The stores to hand out the app at.
|
|
11
|
+
#
|
|
12
|
+
module Stores
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# The App Store, which the app is uploaded to by altool of Xcode, as an
|
|
16
|
+
# installer package, by the API key of App Store Connect in the
|
|
17
|
+
# environment variables, as notarizing on CI is.
|
|
18
|
+
#
|
|
19
|
+
class AppStore
|
|
20
|
+
|
|
21
|
+
def initialize(app_store_connect = AppStoreConnect.new)
|
|
22
|
+
@app_store_connect = app_store_connect
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Uploads +path+, the installer package of the app for +platform+, as
|
|
26
|
+
# macos, which App Store Connect tells the app and the version of by
|
|
27
|
+
# its Info.plist.
|
|
28
|
+
#
|
|
29
|
+
def upload(path, platform:)
|
|
30
|
+
raise Error, "nothing to upload: '#{path}'" unless File.file? path
|
|
31
|
+
|
|
32
|
+
asc = @app_store_connect
|
|
33
|
+
asc.with_key_path do |key_path|
|
|
34
|
+
cmd = [
|
|
35
|
+
'xcrun', 'altool', '--upload-package', path,
|
|
36
|
+
'--platform', platform,
|
|
37
|
+
'--api-key', asc.credentials[:key_id],
|
|
38
|
+
'--api-issuer', asc.credentials[:issuer_id],
|
|
39
|
+
'--p8-file-path', key_path
|
|
40
|
+
]
|
|
41
|
+
puts "==> #{cmd.shelljoin}"
|
|
42
|
+
case system(*cmd)
|
|
43
|
+
when nil
|
|
44
|
+
raise Error, 'xcrun not found, install Xcode'
|
|
45
|
+
when false
|
|
46
|
+
raise Error, "failed to upload: #{cmd.shelljoin}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end# AppStore
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
end# Stores
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
end# Packager
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
end# Reflex
|