reflex-packager 0.1.5 → 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 -40
- data/.github/workflows/tag.yml +11 -28
- data/.github/workflows/test.yml +16 -28
- data/.gitignore +2 -0
- data/ChangeLog.md +35 -0
- data/Gemfile.lock +26 -0
- data/README.md +232 -27
- data/Rakefile +21 -0
- data/VERSION +1 -1
- data/bin/reflex +2 -17
- 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 +66 -26
- data/lib/reflex/packager/config.rb +239 -38
- 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 -92
- data/lib/reflex/packager/platform.rb +289 -10
- data/lib/reflex/packager/profile.rb +54 -17
- 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 -17
- 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 +54 -7
- data/test/test_packager_app_store_connect.rb +108 -0
- data/test/test_packager_cli.rb +40 -5
- 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 -87
- 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
data/lib/reflex/packager/cli.rb
CHANGED
|
@@ -14,6 +14,16 @@ module Reflex
|
|
|
14
14
|
#
|
|
15
15
|
class CLI
|
|
16
16
|
|
|
17
|
+
# The options to notarize the app by, and the steps of the notarization
|
|
18
|
+
# they take.
|
|
19
|
+
#
|
|
20
|
+
NOTARIZE_OPTIONS = {
|
|
21
|
+
notarize: %i[submit export],
|
|
22
|
+
'notarize-submit': %i[submit],
|
|
23
|
+
'notarize-wait': %i[wait],
|
|
24
|
+
'notarize-export': %i[export]
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
def initialize(profile)
|
|
18
28
|
@profile = profile
|
|
19
29
|
end
|
|
@@ -40,51 +50,71 @@ module Reflex
|
|
|
40
50
|
end
|
|
41
51
|
|
|
42
52
|
def create(argv)
|
|
43
|
-
argv, = parse argv, "Usage: #{@profile.
|
|
53
|
+
argv, = parse argv, "Usage: #{@profile.command} new NAME"
|
|
44
54
|
|
|
45
55
|
name = argv.shift
|
|
46
56
|
raise Error, 'project name required' unless name
|
|
47
57
|
raise Error, "'#{name}' already exists" if File.exist? name
|
|
48
58
|
|
|
49
59
|
FileUtils.mkdir_p name
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
#bundle_id: com.example.#{name.downcase.gsub(/[^a-z0-9]+/, '')}
|
|
55
|
-
#version: 1.0.0
|
|
56
|
-
#icon: icon.png
|
|
57
|
-
END
|
|
60
|
+
@profile.templates.each do |path, content|
|
|
61
|
+
File.write File.join(name, path.to_s), gsub_template(content, name)
|
|
62
|
+
end
|
|
63
|
+
|
|
58
64
|
puts "Created #{name}/"
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
hints = [
|
|
66
|
+
["cd #{name} && ruby #{@profile.templates.keys.first}", "run the application"],
|
|
67
|
+
["cd #{name} && #{@profile.command} package .", "package as an application"]
|
|
68
|
+
]
|
|
69
|
+
max = hints.map {|cmd,| cmd.length}.max
|
|
70
|
+
hints.each {|cmd, desc| puts " #{cmd.ljust max} # #{desc}"}
|
|
61
71
|
end
|
|
62
72
|
|
|
63
73
|
def package(argv)
|
|
64
|
-
profile = @profile
|
|
65
|
-
argv, params
|
|
66
|
-
on '--platform PLATFORM',
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
on '--generate-only',
|
|
70
|
-
on '--
|
|
74
|
+
profile, platform = @profile, Packager.default_platform
|
|
75
|
+
argv, params = parse argv, "Usage: #{profile.command} package [options] [DIR]" do
|
|
76
|
+
on '--platform PLATFORM',
|
|
77
|
+
"target platform: #{PLATFORMS.keys.join ' or '} (default: #{platform})"
|
|
78
|
+
on '--config PATH', "config file path (default: DIR/#{profile.config_files.first})"
|
|
79
|
+
on '--generate-only', 'generate project files but do not build'
|
|
80
|
+
on '--pack', 'put the files of the app together in a data file, for a release'
|
|
81
|
+
on '--generic', 'package the app signed with Developer ID, also with --appstore'
|
|
82
|
+
on '--appstore', 'package the app for the Mac App Store, only it unless --generic'
|
|
83
|
+
on '--notarize', 'have Apple check the app with Developer ID, on macos'
|
|
84
|
+
on '--notarize-submit', 'only submit the app to be notarized, not waiting'
|
|
85
|
+
on '--notarize-wait ID', 'only wait for the notarization ID, by the API key'
|
|
86
|
+
on '--notarize-export', 'only wait for the notarization submitted and export the app'
|
|
87
|
+
on '--verbose', 'verbose output'
|
|
71
88
|
end
|
|
72
89
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
raise Error, "unknown platform: '#{platform}'" unless klass
|
|
90
|
+
notarize = NOTARIZE_OPTIONS.filter_map {|key, value| value if params[key]}
|
|
91
|
+
raise Error, "only one of #{NOTARIZE_OPTIONS.keys.map {"--#{_1}"}.join ', '}" if
|
|
92
|
+
notarize.size > 1
|
|
77
93
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
id = params[:'notarize-wait']
|
|
95
|
+
raise Error, '--notarize-wait packages nothing, not with --appstore' if
|
|
96
|
+
id && params[:appstore]
|
|
97
|
+
return AppStoreConnect.new.wait_until_notarized id if id
|
|
98
|
+
|
|
99
|
+
dir = argv.shift || '.'
|
|
100
|
+
platform = (params[:platform] || platform).to_sym
|
|
101
|
+
klass = PLATFORMS[platform] || raise(Error, "unknown platform: '#{platform}'")
|
|
102
|
+
config = Config.load profile, dir, params[:config]
|
|
103
|
+
klass.new(
|
|
104
|
+
config,
|
|
105
|
+
verbose: params[:verbose],
|
|
106
|
+
pack: params[:pack],
|
|
107
|
+
notarize: notarize.first,
|
|
108
|
+
generic: params[:generic],
|
|
109
|
+
appstore: params[:appstore]
|
|
110
|
+
).package generate_only: params[:'generate-only']
|
|
81
111
|
end
|
|
82
112
|
|
|
83
113
|
private
|
|
84
114
|
|
|
85
115
|
def usage()
|
|
86
116
|
<<~END
|
|
87
|
-
Usage: #{@profile.
|
|
117
|
+
Usage: #{@profile.command} <command> [options]
|
|
88
118
|
|
|
89
119
|
Commands:
|
|
90
120
|
new NAME create a new application project
|
|
@@ -105,6 +135,16 @@ module Reflex
|
|
|
105
135
|
return argv, params
|
|
106
136
|
end
|
|
107
137
|
|
|
138
|
+
def gsub_template(str, name)
|
|
139
|
+
{
|
|
140
|
+
name: name,
|
|
141
|
+
name_id: Config.name2id(name)
|
|
142
|
+
}.each do |from, to|
|
|
143
|
+
str = str.gsub "{{#{from}}}", to
|
|
144
|
+
end
|
|
145
|
+
str
|
|
146
|
+
end
|
|
147
|
+
|
|
108
148
|
end# CLI
|
|
109
149
|
|
|
110
150
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'pathname'
|
|
1
2
|
require 'yaml'
|
|
2
3
|
|
|
3
4
|
|
|
@@ -20,15 +21,15 @@ module Reflex
|
|
|
20
21
|
{
|
|
21
22
|
name: name,
|
|
22
23
|
bundle_id: hash[:bundle_id] || default_bundle_id(profile, name),
|
|
23
|
-
version: '0.1.0',
|
|
24
|
-
|
|
24
|
+
version: (hash[:version].nil? && version_file(dir)) || '0.1.0',
|
|
25
|
+
copyright: nil,
|
|
26
|
+
main: profile.main,
|
|
25
27
|
icon: nil,
|
|
26
28
|
files: nil,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
macos: MacOSConfig.defaults
|
|
29
|
+
excludes: nil,
|
|
30
|
+
macos: MacOSConfig.defaults,
|
|
31
|
+
windows: WindowsConfig.defaults,
|
|
32
|
+
upload: UploadConfig.defaults
|
|
32
33
|
}
|
|
33
34
|
end
|
|
34
35
|
|
|
@@ -51,45 +52,77 @@ module Reflex
|
|
|
51
52
|
def initialize(profile, dir, hash = nil, stderr: $stderr)
|
|
52
53
|
raise Error, "no such directory: '#{dir}'" unless File.directory? dir
|
|
53
54
|
|
|
54
|
-
@profile
|
|
55
|
-
@dir
|
|
56
|
-
hash
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
@profile = profile
|
|
56
|
+
@dir = File.expand_path dir
|
|
57
|
+
hash = symbolize_keys hash || {}
|
|
58
|
+
localizations = hash.delete :localizations
|
|
59
|
+
hash = validate_input hash, Config.defaults(profile, @dir, hash), stderr: stderr
|
|
60
|
+
|
|
61
|
+
# YAML reads 1.10 as a number, the same as 1.1
|
|
62
|
+
raise Error, "version #{hash[:version]} is read as a number, quote it" if
|
|
63
|
+
hash[:version].is_a? Float
|
|
64
|
+
|
|
65
|
+
@name = hash[:name] .to_s
|
|
66
|
+
@bundle_id = hash[:bundle_id] .to_s
|
|
67
|
+
@version = hash[:version] .to_s
|
|
68
|
+
@copyright = hash[:copyright]&.to_s
|
|
69
|
+
@main = hash[:main] .to_s
|
|
70
|
+
@icon = hash[:icon] &.to_s
|
|
64
71
|
@files = hash[:files]&.then {Array(_1).map(&:to_s)}
|
|
65
|
-
@
|
|
66
|
-
@macos = MacOSConfig.new
|
|
72
|
+
@excludes = glob_patterns hash[:excludes], :excludes
|
|
73
|
+
@macos = MacOSConfig.new hash[:macos]
|
|
74
|
+
@windows = WindowsConfig.new hash[:windows]
|
|
75
|
+
@upload = UploadConfig.new hash[:upload]
|
|
76
|
+
|
|
77
|
+
@localizations = localize localizations, stderr: stderr
|
|
67
78
|
validate
|
|
68
79
|
end
|
|
69
80
|
|
|
70
|
-
attr_reader :profile, :dir, :name, :bundle_id, :version, :
|
|
71
|
-
:files, :macos, :
|
|
81
|
+
attr_reader :profile, :dir, :name, :bundle_id, :version, :copyright,
|
|
82
|
+
:main, :icon, :files, :excludes, :macos, :windows, :upload
|
|
83
|
+
|
|
84
|
+
# The values that differ by language, in English, the language of the
|
|
85
|
+
# ones out of localizations, and in each language of the localizations
|
|
86
|
+
# with the English ones for what they leave out.
|
|
87
|
+
#
|
|
88
|
+
# @return [Hash<String, Hash>] {language => {name:, copyright:}}
|
|
89
|
+
#
|
|
90
|
+
attr_reader :localizations
|
|
91
|
+
|
|
92
|
+
# The version to show: the numbers of the version, up to three.
|
|
93
|
+
#
|
|
94
|
+
def display_version()
|
|
95
|
+
@version.split('.').first(3).join('.')
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The version to tell builds apart with: the version, a fourth number
|
|
99
|
+
# of which tells a build apart from another of the same version.
|
|
100
|
+
#
|
|
101
|
+
def build_version()
|
|
102
|
+
@version
|
|
103
|
+
end
|
|
72
104
|
|
|
73
105
|
# Returns paths to be bundled into the application, relative to the
|
|
74
|
-
# project directory
|
|
106
|
+
# project directory: the files only, with the ones in the directories
|
|
107
|
+
# matched, for excludes to take out a file in them.
|
|
75
108
|
#
|
|
76
109
|
# @return [Array<String>] relative paths
|
|
77
110
|
#
|
|
78
111
|
def app_files()
|
|
79
|
-
|
|
80
|
-
[@main, *@files]
|
|
81
|
-
.flat_map {Dir.glob _1, base: @dir}
|
|
82
|
-
.reject {_1.start_with?('.') || excludes.include?(_1)}
|
|
83
|
-
.uniq
|
|
84
|
-
.sort
|
|
112
|
+
excluded = glob_files [*EXCLUDES, *@profile.config_files, *@excludes]
|
|
113
|
+
(glob_files([@main, *@files]) - excluded).reject {_1.start_with? '.'}.sort
|
|
85
114
|
end
|
|
86
115
|
|
|
87
|
-
EXCLUDES = %w[
|
|
116
|
+
EXCLUDES = %w[dist]
|
|
88
117
|
|
|
89
118
|
private
|
|
90
119
|
|
|
120
|
+
def self.name2id(name)
|
|
121
|
+
name.downcase.gsub(/[^a-z0-9\-]+/, '')
|
|
122
|
+
end
|
|
123
|
+
|
|
91
124
|
def self.default_bundle_id(profile, name)
|
|
92
|
-
id = name
|
|
125
|
+
id = name2id name
|
|
93
126
|
if id.empty?
|
|
94
127
|
raise Error, "cannot derive a bundle_id from name '#{name}', " +
|
|
95
128
|
"set 'bundle_id' in #{profile.config_files.first}"
|
|
@@ -97,6 +130,19 @@ module Reflex
|
|
|
97
130
|
"#{profile.bundle_id_prefix}.#{id}"
|
|
98
131
|
end
|
|
99
132
|
|
|
133
|
+
# The version in the VERSION file of the app, which the config may leave
|
|
134
|
+
# it to, as the gems have theirs in one.
|
|
135
|
+
#
|
|
136
|
+
def self.version_file(dir)
|
|
137
|
+
path = File.join dir, 'VERSION'
|
|
138
|
+
return nil unless File.file? path
|
|
139
|
+
# without the BOM an editor on Windows may write
|
|
140
|
+
version = File.read(path, encoding: 'bom|utf-8').strip
|
|
141
|
+
raise Error, "invalid version in the VERSION file: '#{version}'" if
|
|
142
|
+
version !~ VERSION_FORMAT
|
|
143
|
+
version
|
|
144
|
+
end
|
|
145
|
+
|
|
100
146
|
def symbolize_keys(hash)
|
|
101
147
|
hash.map {|k, v|
|
|
102
148
|
[
|
|
@@ -106,6 +152,26 @@ module Reflex
|
|
|
106
152
|
}.to_h
|
|
107
153
|
end
|
|
108
154
|
|
|
155
|
+
# A language tag, as ja or zh-Hans.
|
|
156
|
+
#
|
|
157
|
+
LANGUAGE_FORMAT = /\A[a-z]{2,3}(-[A-Za-z0-9]{2,8})*\z/
|
|
158
|
+
|
|
159
|
+
def localize(localizations, stderr: nil)
|
|
160
|
+
english = {name: @name, copyright: @copyright}
|
|
161
|
+
raise Error, "not a Hash: '/localizations'" unless
|
|
162
|
+
localizations.nil? || localizations.is_a?(Hash)
|
|
163
|
+
|
|
164
|
+
(localizations || {}).each.with_object({'en' => english}) do |(lang, hash), result|
|
|
165
|
+
lang = lang.to_s
|
|
166
|
+
raise Error, "invalid language: '#{lang}'" if lang !~ LANGUAGE_FORMAT
|
|
167
|
+
raise Error, "'en' is the language of the values out of localizations" if
|
|
168
|
+
lang == 'en'
|
|
169
|
+
result[lang] = validate_input(
|
|
170
|
+
hash, english, parent: "/localizations/#{lang}", stderr: stderr
|
|
171
|
+
).transform_values {_1&.to_s}
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
109
175
|
def validate_input(hash, defaults, parent: '', stderr: nil)
|
|
110
176
|
return defaults.dup if hash.nil?
|
|
111
177
|
raise Error, "not a Hash: '#{parent}'" unless hash.is_a? Hash
|
|
@@ -123,16 +189,50 @@ module Reflex
|
|
|
123
189
|
end
|
|
124
190
|
end
|
|
125
191
|
|
|
192
|
+
def glob_patterns(patterns, key)
|
|
193
|
+
return nil if patterns.nil?
|
|
194
|
+
Array(patterns).map do |pattern|
|
|
195
|
+
raise Error, "not a glob pattern in '/#{key}': #{pattern.inspect}" if
|
|
196
|
+
pattern.is_a?(Hash) || pattern.is_a?(Array) || pattern.to_s.strip.empty?
|
|
197
|
+
pattern.to_s
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# The files matched by +patterns+ in the project directory, and the
|
|
202
|
+
# ones in the directories matched but the dotfiles, as lib/a.rb for
|
|
203
|
+
# ./lib/a.rb too, for the excludes to be compared with.
|
|
204
|
+
#
|
|
205
|
+
def glob_files(patterns)
|
|
206
|
+
patterns
|
|
207
|
+
.flat_map {Dir.glob _1, base: @dir}
|
|
208
|
+
.map {Pathname(_1).cleanpath.to_s}
|
|
209
|
+
.flat_map {|path|
|
|
210
|
+
# by base:, not to take the name of the directory as a pattern
|
|
211
|
+
[path, *Dir.glob('**/*', base: File.join(@dir, path)).map {File.join path, _1}]
|
|
212
|
+
}
|
|
213
|
+
.reject {File.directory? File.join(@dir, _1)}
|
|
214
|
+
.uniq
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Numbers only, which macOS and Windows compare versions by.
|
|
218
|
+
#
|
|
219
|
+
VERSION_FORMAT = /\A\d+(\.\d+)*\z/
|
|
220
|
+
|
|
126
221
|
def validate()
|
|
222
|
+
# the name of the .app on macos, which can not have a path separator
|
|
223
|
+
raise Error, "invalid name: '#{@name}'" if @name.strip.empty? || @name =~ %r{[/:]}
|
|
224
|
+
|
|
127
225
|
raise Error, "invalid bundle_id: '#{@bundle_id}'" if
|
|
128
226
|
@bundle_id !~ /\A[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+\z/
|
|
129
227
|
|
|
130
|
-
raise Error, "invalid version: '#{@version}'" if
|
|
131
|
-
@version !~ /\A\d+(\.\d+)*\z/
|
|
228
|
+
raise Error, "invalid version: '#{@version}'" if @version !~ VERSION_FORMAT
|
|
132
229
|
|
|
133
230
|
raise Error, "main script not found: '#{@main}'" if
|
|
134
231
|
!File.file?(File.join @dir, @main)
|
|
135
232
|
|
|
233
|
+
raise Error, "main script excluded: '#{@main}'" if
|
|
234
|
+
@excludes && (glob_files([@main]) - glob_files(@excludes)).empty?
|
|
235
|
+
|
|
136
236
|
raise Error, "icon not found: '#{@icon}'" if
|
|
137
237
|
@icon && !File.file?(File.join @dir, @icon)
|
|
138
238
|
end
|
|
@@ -144,10 +244,20 @@ module Reflex
|
|
|
144
244
|
#
|
|
145
245
|
class MacOSConfig
|
|
146
246
|
|
|
247
|
+
# The keys of codesign in the config, and the environment variables
|
|
248
|
+
# that override them.
|
|
249
|
+
#
|
|
250
|
+
CODESIGN_ENV_KEYS = {
|
|
251
|
+
identity: 'APPLE_CODESIGN_IDENTITY',
|
|
252
|
+
team_id: 'APPLE_TEAM_ID'
|
|
253
|
+
}
|
|
254
|
+
|
|
147
255
|
def self.defaults()
|
|
148
256
|
{
|
|
149
257
|
deployment_target: '11.0',
|
|
150
|
-
archs:
|
|
258
|
+
archs: %w[arm64 x86_64],
|
|
259
|
+
cruby: nil,
|
|
260
|
+
category: nil,
|
|
151
261
|
codesign: {
|
|
152
262
|
identity: '-',
|
|
153
263
|
team_id: nil
|
|
@@ -156,22 +266,113 @@ module Reflex
|
|
|
156
266
|
end
|
|
157
267
|
|
|
158
268
|
def initialize(hash)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
@
|
|
162
|
-
@
|
|
269
|
+
codesign = hash[:codesign].merge CODESIGN_ENV_KEYS.transform_values {env _1}.compact
|
|
270
|
+
|
|
271
|
+
@deployment_target = hash[:deployment_target] .to_s
|
|
272
|
+
@archs = Array(hash[:archs]).map &:to_s
|
|
273
|
+
@cruby = hash[:cruby] &.to_s
|
|
274
|
+
@category = hash[:category] &.to_s
|
|
275
|
+
@codesign_identity = codesign[:identity] .to_s
|
|
276
|
+
@codesign_team_id = codesign[:team_id] &.to_s
|
|
163
277
|
validate
|
|
164
278
|
end
|
|
165
279
|
|
|
166
|
-
|
|
280
|
+
# The version of CRuby, or the path of a checkout of the cruby
|
|
281
|
+
# repository, to embed.
|
|
282
|
+
#
|
|
283
|
+
attr_reader :cruby
|
|
284
|
+
|
|
285
|
+
attr_reader :deployment_target, :archs
|
|
286
|
+
|
|
287
|
+
# The category of the app in its Info.plist, as public.app-category.games,
|
|
288
|
+
# which Finder groups apps by and the App Store requires.
|
|
289
|
+
#
|
|
290
|
+
attr_reader :category
|
|
291
|
+
|
|
292
|
+
# The identity and the team to sign the app with, which
|
|
293
|
+
# APPLE_CODESIGN_IDENTITY and APPLE_TEAM_ID override, for the name of
|
|
294
|
+
# the developer in the identity not to be in the config.
|
|
295
|
+
#
|
|
296
|
+
attr_reader :codesign_identity, :codesign_team_id
|
|
167
297
|
|
|
168
298
|
def validate()
|
|
169
299
|
raise Error, "invalid archs: '#{@archs}'" if @archs.empty?
|
|
300
|
+
raise Error, "invalid category: '#{@category}'" if
|
|
301
|
+
@category && @category !~ /\Apublic\.app-category\.[a-z\-]+\z/
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
private
|
|
305
|
+
|
|
306
|
+
# The environment variable +name+, or nil if empty, as a secret of
|
|
307
|
+
# GitHub Actions not set is.
|
|
308
|
+
#
|
|
309
|
+
def env(name)
|
|
310
|
+
ENV[name].then {_1 unless _1.to_s.empty?}
|
|
170
311
|
end
|
|
171
312
|
|
|
172
313
|
end# MacOSConfig
|
|
173
314
|
|
|
174
315
|
|
|
316
|
+
# Windows specific configuration.
|
|
317
|
+
#
|
|
318
|
+
class WindowsConfig
|
|
319
|
+
|
|
320
|
+
def self.defaults()
|
|
321
|
+
{
|
|
322
|
+
console: false
|
|
323
|
+
}
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def initialize(hash)
|
|
327
|
+
@console = hash[:console]
|
|
328
|
+
validate
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Whether the executable keeps a console window, which shows what an app
|
|
332
|
+
# prints and the errors it dies of while it is being worked on.
|
|
333
|
+
#
|
|
334
|
+
def console?()
|
|
335
|
+
@console
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def validate()
|
|
339
|
+
raise Error, "invalid console: '#{@console}'" unless
|
|
340
|
+
[true, false].include? @console
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
end# WindowsConfig
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# Configuration of the stores to upload the app to.
|
|
347
|
+
#
|
|
348
|
+
class UploadConfig
|
|
349
|
+
|
|
350
|
+
def self.defaults()
|
|
351
|
+
{
|
|
352
|
+
itchio: {
|
|
353
|
+
project: nil
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def initialize(hash)
|
|
359
|
+
@itchio_project = hash[:itchio][:project]&.to_s
|
|
360
|
+
validate
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# The project of itch.io to upload the app to, as user/game of
|
|
364
|
+
# https://user.itch.io/game, which butler takes.
|
|
365
|
+
#
|
|
366
|
+
attr_reader :itchio_project
|
|
367
|
+
|
|
368
|
+
def validate()
|
|
369
|
+
raise Error, "invalid upload.itchio.project: '#{@itchio_project}'" if
|
|
370
|
+
@itchio_project && @itchio_project !~ %r{\A[a-z0-9_\-]+/[a-z0-9_\-]+\z}
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
end# UploadConfig
|
|
374
|
+
|
|
375
|
+
|
|
175
376
|
# Raised on invalid configuration or packaging failure.
|
|
176
377
|
#
|
|
177
378
|
class Error < StandardError; end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require_relative 'data_file'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Reflex
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module Packager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Compiles the Ruby scripts of an app into instruction sequences for its
|
|
11
|
+
# data file, which DataLoader reads them from, named as the scripts but
|
|
12
|
+
# with .rbc.
|
|
13
|
+
#
|
|
14
|
+
# They are compiled by the Ruby running this, which has to be the one a
|
|
15
|
+
# package runs them on, with their paths relative to the app directory,
|
|
16
|
+
# as the one the package runs in has them.
|
|
17
|
+
#
|
|
18
|
+
# A package carries none of this, which requires nothing but DataFile,
|
|
19
|
+
# for an app to compile its scripts with the Ruby in it.
|
|
20
|
+
#
|
|
21
|
+
module DataCompiler
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
|
|
25
|
+
# The names of the Ruby scripts of the app in +dir+.
|
|
26
|
+
#
|
|
27
|
+
def scripts(dir)
|
|
28
|
+
Dir.glob('**/*.rb', base: dir).select {File.file? File.join(dir, _1)}.sort
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Compiles the Ruby scripts of the app in +dir+ into the files of its
|
|
32
|
+
# data file, with the version of the Ruby.
|
|
33
|
+
#
|
|
34
|
+
def compile(dir)
|
|
35
|
+
data = scripts(dir).to_h do |name|
|
|
36
|
+
source = File.read File.join(dir, name), encoding: Encoding::UTF_8
|
|
37
|
+
iseq = RubyVM::InstructionSequence.compile source, name, name
|
|
38
|
+
[name.sub(/\.rb\z/, '.rbc'), iseq.to_binary]
|
|
39
|
+
end
|
|
40
|
+
data['.ruby-version'] = RUBY_VERSION
|
|
41
|
+
data
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Writes the data file at +path+ with the Ruby scripts of the app in
|
|
45
|
+
# +dir+ compiled, all of them before writing it.
|
|
46
|
+
#
|
|
47
|
+
def write(dir, path)
|
|
48
|
+
DataFile.write path, compile(dir)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end# self
|
|
52
|
+
|
|
53
|
+
end# DataCompiler
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
end# Packager
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
end# Reflex
|