reflex-packager 0.1.2
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 +7 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +12 -0
- data/.github/workflows/release-gem.yml +51 -0
- data/.github/workflows/tag.yml +35 -0
- data/.github/workflows/test.yml +37 -0
- data/.github/workflows/utils.rb +127 -0
- data/.gitignore +6 -0
- data/CONTRIBUTING.md +7 -0
- data/ChangeLog.md +16 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +166 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/bin/reflex +22 -0
- data/lib/reflex/packager/cli.rb +114 -0
- data/lib/reflex/packager/config.rb +183 -0
- data/lib/reflex/packager/extension.rb +32 -0
- data/lib/reflex/packager/macos.rb +190 -0
- data/lib/reflex/packager/platform.rb +73 -0
- data/lib/reflex/packager/profile.rb +45 -0
- data/lib/reflex/packager/templates/macos/Podfile.erb +30 -0
- data/lib/reflex/packager/templates/macos/main.mm.erb +43 -0
- data/lib/reflex/packager/templates/macos/project.yml.erb +40 -0
- data/lib/reflex/packager.rb +12 -0
- data/reflex-packager.gemspec +37 -0
- data/test/helper.rb +28 -0
- data/test/test_packager_cli.rb +80 -0
- data/test/test_packager_config.rb +240 -0
- data/test/test_packager_macos.rb +225 -0
- metadata +132 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Reflex
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Tools to package a Reflex application as a distributable bundle.
|
|
8
|
+
# Files under this directory must not require 'reflex' because it has
|
|
9
|
+
# side effects such as requiring native extensions.
|
|
10
|
+
#
|
|
11
|
+
module Packager
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Application project configuration loaded from 'reflex.yml'.
|
|
15
|
+
#
|
|
16
|
+
class Config
|
|
17
|
+
|
|
18
|
+
def self.defaults(profile, dir, hash)
|
|
19
|
+
name = hash[:name]&.to_s || File.basename(dir)
|
|
20
|
+
{
|
|
21
|
+
name: name,
|
|
22
|
+
bundle_id: hash[:bundle_id] || default_bundle_id(profile, name),
|
|
23
|
+
version: '0.1.0',
|
|
24
|
+
main: 'main.rb',
|
|
25
|
+
icon: nil,
|
|
26
|
+
files: nil,
|
|
27
|
+
pods: {
|
|
28
|
+
:cruby => {tag: nil, branch: nil, git: nil, path: nil},
|
|
29
|
+
profile.pod_key => {tag: nil, branch: nil, git: nil, path: nil}
|
|
30
|
+
},
|
|
31
|
+
macos: MacOSConfig.defaults
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Load the config file in the project directory.
|
|
36
|
+
#
|
|
37
|
+
# @param [Profile] profile runtime profile to package for
|
|
38
|
+
# @param [String] dir project directory
|
|
39
|
+
# @param [String] path config file path to use instead of the default
|
|
40
|
+
#
|
|
41
|
+
# @return [Config] config object
|
|
42
|
+
#
|
|
43
|
+
def self.load(profile, dir, path = nil)
|
|
44
|
+
raise Error, "config file not found: '#{path}'" if path && !File.file?(path)
|
|
45
|
+
path ||= profile.config_files.map {File.join dir, _1}.find {File.file? _1}
|
|
46
|
+
new profile, dir, (path ? YAML.safe_load(File.read(path), aliases: true) : nil)
|
|
47
|
+
rescue Psych::SyntaxError => e
|
|
48
|
+
raise Error, "failed to parse '#{path}': #{e.message}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def initialize(profile, dir, hash = nil, stderr: $stderr)
|
|
52
|
+
raise Error, "no such directory: '#{dir}'" unless File.directory? dir
|
|
53
|
+
|
|
54
|
+
@profile = profile
|
|
55
|
+
@dir = File.expand_path dir
|
|
56
|
+
hash = symbolize_keys hash || {}
|
|
57
|
+
hash = validate_input hash, Config.defaults(profile, @dir, hash), stderr: stderr
|
|
58
|
+
|
|
59
|
+
@name = hash[:name] .to_s
|
|
60
|
+
@bundle_id = hash[:bundle_id].to_s
|
|
61
|
+
@version = hash[:version] .to_s
|
|
62
|
+
@main = hash[:main] .to_s
|
|
63
|
+
@icon = hash[:icon] &.to_s
|
|
64
|
+
@files = hash[:files]&.then {Array(_1).map(&:to_s)}
|
|
65
|
+
@pods = hash[:pods].transform_values &:compact
|
|
66
|
+
@macos = MacOSConfig.new hash[:macos]
|
|
67
|
+
validate
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
attr_reader :profile, :dir, :name, :bundle_id, :version, :main, :icon,
|
|
71
|
+
:files, :macos, :pods
|
|
72
|
+
|
|
73
|
+
# Returns paths to be bundled into the application, relative to the
|
|
74
|
+
# project directory.
|
|
75
|
+
#
|
|
76
|
+
# @return [Array<String>] relative paths
|
|
77
|
+
#
|
|
78
|
+
def app_files()
|
|
79
|
+
excludes = EXCLUDES + @profile.config_files
|
|
80
|
+
[@main, *@files]
|
|
81
|
+
.flat_map {Dir.glob _1, base: @dir}
|
|
82
|
+
.reject {_1.start_with?('.') || excludes.include?(_1)}
|
|
83
|
+
.uniq
|
|
84
|
+
.sort
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
EXCLUDES = %w[build dist]
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def self.default_bundle_id(profile, name)
|
|
92
|
+
id = name.downcase.gsub(/[^a-z0-9\-]+/, '')
|
|
93
|
+
if id.empty?
|
|
94
|
+
raise Error, "cannot derive a bundle_id from name '#{name}', " +
|
|
95
|
+
"set 'bundle_id' in #{profile.config_files.first}"
|
|
96
|
+
end
|
|
97
|
+
"#{profile.bundle_id_prefix}.#{id}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def symbolize_keys(hash)
|
|
101
|
+
hash.map {|k, v|
|
|
102
|
+
[
|
|
103
|
+
k.to_sym,
|
|
104
|
+
v.is_a?(Hash) ? symbolize_keys(v) : v
|
|
105
|
+
]
|
|
106
|
+
}.to_h
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def validate_input(hash, defaults, parent: '', stderr: nil)
|
|
110
|
+
return defaults.dup if hash.nil?
|
|
111
|
+
raise Error, "not a Hash: '#{parent}'" unless hash.is_a? Hash
|
|
112
|
+
hash.each_key {stderr&.puts "unknown key '#{parent}/#{_1}'" unless defaults.key? _1}
|
|
113
|
+
|
|
114
|
+
defaults.each.with_object({}) do |(key, defval), result|
|
|
115
|
+
value = hash[key]
|
|
116
|
+
result[key] =
|
|
117
|
+
if defval.is_a? Hash
|
|
118
|
+
validate_input value, defval, parent: "#{parent}/#{key}", stderr: stderr
|
|
119
|
+
else
|
|
120
|
+
raise Error, "unexpected Hash: '#{parent}/#{key}'" if value.is_a? Hash
|
|
121
|
+
value.nil? ? defval : value
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def validate()
|
|
127
|
+
raise Error, "invalid bundle_id: '#{@bundle_id}'" if
|
|
128
|
+
@bundle_id !~ /\A[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+\z/
|
|
129
|
+
|
|
130
|
+
raise Error, "invalid version: '#{@version}'" if
|
|
131
|
+
@version !~ /\A\d+(\.\d+)*\z/
|
|
132
|
+
|
|
133
|
+
raise Error, "main script not found: '#{@main}'" if
|
|
134
|
+
!File.file?(File.join @dir, @main)
|
|
135
|
+
|
|
136
|
+
raise Error, "icon not found: '#{@icon}'" if
|
|
137
|
+
@icon && !File.file?(File.join @dir, @icon)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end# Config
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# macOS specific configuration.
|
|
144
|
+
#
|
|
145
|
+
class MacOSConfig
|
|
146
|
+
|
|
147
|
+
def self.defaults()
|
|
148
|
+
{
|
|
149
|
+
deployment_target: '11.0',
|
|
150
|
+
archs: 'arm64',
|
|
151
|
+
codesign: {
|
|
152
|
+
identity: '-',
|
|
153
|
+
team_id: nil
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def initialize(hash)
|
|
159
|
+
@deployment_target = hash[:deployment_target] .to_s
|
|
160
|
+
@archs = Array(hash[:archs]).map &:to_s
|
|
161
|
+
@codesign_identity = hash[:codesign][:identity].to_s
|
|
162
|
+
@codesign_team_id = hash[:codesign][:team_id]&.to_s
|
|
163
|
+
validate
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
attr_reader :deployment_target, :archs, :codesign_identity, :codesign_team_id
|
|
167
|
+
|
|
168
|
+
def validate()
|
|
169
|
+
raise Error, "invalid archs: '#{@archs}'" if @archs.empty?
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
end# MacOSConfig
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# Raised on invalid configuration or packaging failure.
|
|
176
|
+
#
|
|
177
|
+
class Error < StandardError; end
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
end# Packager
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
end# Reflex
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Reflex
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Packager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# @private
|
|
8
|
+
module Extension
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def name(downcase = false)
|
|
13
|
+
super().split('::')[..-2].join.then {|s|
|
|
14
|
+
downcase ? s.gsub(/([a-z])([A-Z])/) {"#{$1}-#{$2}"}.downcase : s
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def version()
|
|
19
|
+
File.read(root_dir 'VERSION')[/[\d\.]+/]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def root_dir(path = '')
|
|
23
|
+
File.expand_path "../../../#{path}", __dir__
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end# Extension
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
end# Packager
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
end# Reflex
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
require 'reflex/packager/platform'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Reflex
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module Packager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Packages a Reflex application as a macOS application bundle.
|
|
11
|
+
#
|
|
12
|
+
class MacOS < Platform
|
|
13
|
+
|
|
14
|
+
GIT_CRUBY = 'https://github.com/xord/cruby'
|
|
15
|
+
|
|
16
|
+
TOOLS = {
|
|
17
|
+
xcodegen: 'install with: brew install xcodegen',
|
|
18
|
+
pod: 'install with: brew install cocoapods (or gem install cocoapods)',
|
|
19
|
+
xcodebuild: 'install Xcode and run: sudo xcode-select --switch /Applications/Xcode.app'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def generate()
|
|
23
|
+
copy_app_files
|
|
24
|
+
generate_icon if config.icon
|
|
25
|
+
write 'project.yml', render('project.yml.erb')
|
|
26
|
+
write 'Podfile', render('Podfile.erb')
|
|
27
|
+
write 'src/main.mm', render('main.mm.erb')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def build()
|
|
31
|
+
check_tools TOOLS
|
|
32
|
+
check_dev_pods
|
|
33
|
+
run 'xcodegen', 'generate', chdir: build_dir
|
|
34
|
+
run 'pod', 'install', *('--verbose' if verbose?), chdir: build_dir,
|
|
35
|
+
env: {'os' => 'macos'}
|
|
36
|
+
run 'xcodebuild',
|
|
37
|
+
'-workspace', "#{target}.xcworkspace",
|
|
38
|
+
'-scheme', target,
|
|
39
|
+
'-configuration', 'Release',
|
|
40
|
+
'-destination', 'platform=macOS',
|
|
41
|
+
'-derivedDataPath', 'DerivedData',
|
|
42
|
+
'build',
|
|
43
|
+
chdir: build_dir
|
|
44
|
+
copy_app
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the Xcode target name: the app name without characters
|
|
48
|
+
# unsafe for target/scheme/file names.
|
|
49
|
+
#
|
|
50
|
+
def target()
|
|
51
|
+
config.name.gsub(/[^A-Za-z0-9_\-]+/, '').then {_1.empty? ? 'App' : _1}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def build_dir()
|
|
55
|
+
File.join config.dir, 'build', platform_name
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def dist_dir()
|
|
59
|
+
File.join config.dir, 'dist'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Native extensions registered with CRuby (Init_<name> symbols).
|
|
63
|
+
#
|
|
64
|
+
def extensions()
|
|
65
|
+
profile.extensions
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Ruby library bundles added to the load path.
|
|
69
|
+
#
|
|
70
|
+
def libraries()
|
|
71
|
+
profile.libraries
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns {'CRuby' => {...}, '<pod>' => {...}} resolved from the config,
|
|
75
|
+
# the <POD>_PODS_PATH env var, or the defaults.
|
|
76
|
+
#
|
|
77
|
+
def pod_refs()
|
|
78
|
+
p = profile
|
|
79
|
+
{
|
|
80
|
+
'CRuby' => pod_ref(:cruby, {git: GIT_CRUBY}),
|
|
81
|
+
p.pod => pod_ref(p.pod_key, {git: p.git, tag: "v#{p.version}"})
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns local directories of pods referenced by path, which need
|
|
86
|
+
# special handling because CocoaPods does not place development
|
|
87
|
+
# pods under PODS_ROOT and does not run their prepare_command.
|
|
88
|
+
#
|
|
89
|
+
def dev_pod_paths()
|
|
90
|
+
pod_refs
|
|
91
|
+
.filter_map {|name, ref| [name, ref[:path]] if ref[:path]}
|
|
92
|
+
.to_h
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns sips command lines to resize the icon into an iconset.
|
|
96
|
+
#
|
|
97
|
+
def icon_commands(src, iconset_dir)
|
|
98
|
+
[16, 32, 128, 256, 512].flat_map {|size|
|
|
99
|
+
[[size, "icon_#{size}x#{size}.png"], [size * 2, "icon_#{size}x#{size}@2x.png"]]
|
|
100
|
+
}.map {|px, file|
|
|
101
|
+
['sips', '-z', px.to_s, px.to_s, src, '--out', File.join(iconset_dir, file)]
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def platform_name()
|
|
108
|
+
'macos'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def pod_ref(name, default)
|
|
112
|
+
ref = config.pods[name]
|
|
113
|
+
return ref unless ref.nil? || ref.empty?
|
|
114
|
+
|
|
115
|
+
root = ENV["#{profile.pod_key.to_s.upcase}_PODS_PATH"]
|
|
116
|
+
root ? {path: File.expand_path(name.to_s, root)} : default
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def pod_line(name, ref)
|
|
120
|
+
args = ref.map {|key, value| "#{key}: '#{value}'"}.join ', '
|
|
121
|
+
"pod '#{name}', #{args}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def copy_app_files()
|
|
125
|
+
dir = File.join build_dir, 'app'
|
|
126
|
+
FileUtils.rm_rf dir
|
|
127
|
+
FileUtils.mkdir_p dir
|
|
128
|
+
config.app_files.each do |file|
|
|
129
|
+
dest = File.join dir, file
|
|
130
|
+
FileUtils.mkdir_p File.dirname(dest)
|
|
131
|
+
FileUtils.cp_r File.join(config.dir, file), dest
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def generate_icon()
|
|
136
|
+
iconset = File.join build_dir, 'AppIcon.iconset'
|
|
137
|
+
FileUtils.rm_rf iconset
|
|
138
|
+
FileUtils.mkdir_p iconset
|
|
139
|
+
icon_commands(File.join(config.dir, config.icon), iconset)
|
|
140
|
+
.each {|cmd| run(*cmd, chdir: build_dir)}
|
|
141
|
+
run 'iconutil', '-c', 'icns', 'AppIcon.iconset', '-o', 'AppIcon.icns',
|
|
142
|
+
chdir: build_dir
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Development pods skip prepare_command on pod install, so ensure
|
|
146
|
+
# that the manual setups have been done.
|
|
147
|
+
#
|
|
148
|
+
def check_dev_pods()
|
|
149
|
+
dev_pod_paths.each do |name, path|
|
|
150
|
+
raise Error, "pod directory not found: '#{path}'" unless File.directory? path
|
|
151
|
+
if name == 'CRuby'
|
|
152
|
+
unless File.directory? File.join(path, 'CRuby', 'include')
|
|
153
|
+
raise Error,
|
|
154
|
+
"'#{path}' has no CRuby binary, " +
|
|
155
|
+
"run: cd #{path} && rake download_or_build os=macos"
|
|
156
|
+
end
|
|
157
|
+
else # the umbrella pod (Reflex / RubySketch / ...)
|
|
158
|
+
unless File.directory? File.join(path, 'all')
|
|
159
|
+
raise Error,
|
|
160
|
+
"'#{path}' is not set up for CocoaPods, " +
|
|
161
|
+
"run: cd #{path} && rake -f pod.rake setup"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def copy_app()
|
|
168
|
+
app = File.join build_dir, 'DerivedData', 'Build', 'Products', 'Release', "#{target}.app"
|
|
169
|
+
raise Error, "application not found: '#{app}'" unless File.directory? app
|
|
170
|
+
|
|
171
|
+
dist = File.join dist_dir, "#{target}.app"
|
|
172
|
+
FileUtils.rm_rf dist
|
|
173
|
+
FileUtils.mkdir_p dist_dir
|
|
174
|
+
FileUtils.cp_r app, dist
|
|
175
|
+
puts "Created #{dist}"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def write(path, content)
|
|
179
|
+
path = File.join build_dir, path
|
|
180
|
+
FileUtils.mkdir_p File.dirname(path)
|
|
181
|
+
File.write path, content
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
end# MacOS
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
end# Packager
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
end# Reflex
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
module Reflex
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module Packager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
TEMPLATES_DIR = File.expand_path 'templates', __dir__
|
|
12
|
+
|
|
13
|
+
# Base class for platform specific packagers.
|
|
14
|
+
#
|
|
15
|
+
class Platform
|
|
16
|
+
|
|
17
|
+
def initialize(config, verbose: false)
|
|
18
|
+
@config, @verbose = config, verbose
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attr_reader :config
|
|
22
|
+
|
|
23
|
+
def profile
|
|
24
|
+
config.profile
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def verbose?()
|
|
28
|
+
@verbose
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Package the application as a distributable bundle.
|
|
32
|
+
#
|
|
33
|
+
# @param [Boolean] generate_only generate the project files but do
|
|
34
|
+
# not build them
|
|
35
|
+
#
|
|
36
|
+
def package(generate_only: false)
|
|
37
|
+
generate
|
|
38
|
+
build unless generate_only
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def render(template)
|
|
44
|
+
path = File.join TEMPLATES_DIR, platform_name, template
|
|
45
|
+
ERB.new(File.read(path), trim_mode: '-').result binding
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def run(*cmd, chdir:, env: {})
|
|
49
|
+
puts "==> #{cmd.join ' '}"
|
|
50
|
+
return if system env, *cmd, chdir: chdir
|
|
51
|
+
raise Error, "command failed: #{cmd.join ' '}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def check_tools(tools)
|
|
55
|
+
missing = tools.reject {|name, _| executable? name}
|
|
56
|
+
return if missing.empty?
|
|
57
|
+
|
|
58
|
+
list = missing.map {|name, hint| ' %-10s -- %s' % [name, hint]}
|
|
59
|
+
raise Error, "required tools not found:\n#{list.join "\n"}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def executable?(name)
|
|
63
|
+
ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
|
|
64
|
+
.any? {|dir| File.executable? File.join(dir, name.to_s)}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end# Platform
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
end# Packager
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
end# Reflex
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Reflex
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Packager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Describes the runtime a packaged app embeds: the umbrella pod that
|
|
8
|
+
# provides the native build, the extensions and libraries registered with
|
|
9
|
+
# CRuby, and the scaffold for the 'new' command.
|
|
10
|
+
#
|
|
11
|
+
# The packager itself is runtime-agnostic; each gem (reflex, rubysketch,
|
|
12
|
+
# ...) supplies its own profile and reuses this packager as the engine.
|
|
13
|
+
#
|
|
14
|
+
class Profile
|
|
15
|
+
|
|
16
|
+
# @param [String] pod umbrella pod name (e.g. 'Reflex')
|
|
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>)
|
|
21
|
+
# @param [Array<String>] config_files config file names, preferred first
|
|
22
|
+
# @param [String] template 'new' main script template ({{name}})
|
|
23
|
+
#
|
|
24
|
+
def initialize(pod:, git:, version:, libraries:, extensions:, config_files:, template:)
|
|
25
|
+
@pod, @git, @version, @libraries, @extensions, @config_files, @template =
|
|
26
|
+
pod, git, version, libraries, extensions, config_files, template
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attr_reader :pod, :git, :version, :libraries, :extensions, :config_files, :template
|
|
30
|
+
|
|
31
|
+
def pod_key()
|
|
32
|
+
pod.downcase.to_sym
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def bundle_id_prefix()
|
|
36
|
+
"org.xord.#{pod_key}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end# Profile
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
end# Packager
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
end# Reflex
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- mode: ruby -*-
|
|
2
|
+
|
|
3
|
+
platform :macos, '<%= config.macos.deployment_target %>'
|
|
4
|
+
|
|
5
|
+
target '<%= target %>' do
|
|
6
|
+
<%- pod_refs.each do |name, ref| -%>
|
|
7
|
+
<%= pod_line name, ref %>
|
|
8
|
+
<%- end -%>
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
post_install do |installer|
|
|
12
|
+
installer.pods_project.targets.each do |target|
|
|
13
|
+
target.build_configurations.each do |c|
|
|
14
|
+
c.build_settings['ARCHS'] = '<%= config.macos.archs.join ' ' %>'
|
|
15
|
+
c.build_settings['VALID_ARCHS'] = '<%= config.macos.archs.join ' ' %>'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
<%- unless dev_pod_paths.empty? -%>
|
|
19
|
+
|
|
20
|
+
# development pods are not placed under PODS_ROOT, so resolve
|
|
21
|
+
# '${PODS_ROOT}/<name>' to each local directory in the xcconfigs
|
|
22
|
+
Dir.glob('Pods/Target Support Files/**/*.xcconfig').each do |path|
|
|
23
|
+
s = File.read path
|
|
24
|
+
<%- dev_pod_paths.each do |name, dir| -%>
|
|
25
|
+
s.gsub! '${PODS_ROOT}/<%= name %>', '<%= dir %>'
|
|
26
|
+
<%- end -%>
|
|
27
|
+
File.write path, s
|
|
28
|
+
end
|
|
29
|
+
<%- end -%>
|
|
30
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// -*- mode: objc -*-
|
|
2
|
+
#import <Cocoa/Cocoa.h>
|
|
3
|
+
#import <CRuby.h>
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
extern "C"
|
|
7
|
+
{
|
|
8
|
+
<%- extensions.each do |ext| -%>
|
|
9
|
+
void Init_<%= ext %> ();
|
|
10
|
+
<%- end -%>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
int
|
|
15
|
+
main (int argc, const char* argv[])
|
|
16
|
+
{
|
|
17
|
+
@autoreleasepool
|
|
18
|
+
{
|
|
19
|
+
NSString* appDir =
|
|
20
|
+
[NSBundle.mainBundle.resourcePath stringByAppendingPathComponent:@"app"];
|
|
21
|
+
|
|
22
|
+
// the application loads images by paths relative to the script
|
|
23
|
+
[NSFileManager.defaultManager changeCurrentDirectoryPath:appDir];
|
|
24
|
+
|
|
25
|
+
<%- extensions.each do |ext| -%>
|
|
26
|
+
[CRuby addExtension:@"<%= ext %>" init:^{Init_<%= ext %>();}];
|
|
27
|
+
<%- end -%>
|
|
28
|
+
|
|
29
|
+
for (NSString* lib in @[<%= libraries.map {|l| "@\"#{l}\""}.join ', ' %>])
|
|
30
|
+
[CRuby addLibrary:lib bundle:NSBundle.mainBundle];
|
|
31
|
+
|
|
32
|
+
NSString* main = [appDir stringByAppendingPathComponent:@"<%= config.main %>"];
|
|
33
|
+
|
|
34
|
+
// add the script directory to the load path so that apps split
|
|
35
|
+
// into multiple files can require each other
|
|
36
|
+
[CRuby evaluate:[NSString stringWithFormat:@"$LOAD_PATH.unshift '%@'", appDir]];
|
|
37
|
+
|
|
38
|
+
// the script calls Reflex.start, which runs [NSApp run] and blocks
|
|
39
|
+
// here until the application quits
|
|
40
|
+
[CRuby start:main];
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: <%= target %>
|
|
2
|
+
|
|
3
|
+
options:
|
|
4
|
+
deploymentTarget:
|
|
5
|
+
macOS: "<%= config.macos.deployment_target %>"
|
|
6
|
+
|
|
7
|
+
settings:
|
|
8
|
+
base:
|
|
9
|
+
PRODUCT_BUNDLE_IDENTIFIER: <%= config.bundle_id %>
|
|
10
|
+
MARKETING_VERSION: "<%= config.version %>"
|
|
11
|
+
ARCHS: <%= config.macos.archs.join ' ' %>
|
|
12
|
+
ONLY_ACTIVE_ARCH: NO
|
|
13
|
+
CODE_SIGN_IDENTITY: <%= config.macos.codesign_identity.inspect %>
|
|
14
|
+
<%- if config.macos.codesign_team_id -%>
|
|
15
|
+
DEVELOPMENT_TEAM: <%= config.macos.codesign_team_id %>
|
|
16
|
+
<%- end -%>
|
|
17
|
+
|
|
18
|
+
targets:
|
|
19
|
+
<%= target %>:
|
|
20
|
+
type: application
|
|
21
|
+
platform: macOS
|
|
22
|
+
sources:
|
|
23
|
+
- path: src
|
|
24
|
+
- path: app
|
|
25
|
+
type: folder
|
|
26
|
+
buildPhase: resources
|
|
27
|
+
<%- if config.icon -%>
|
|
28
|
+
- path: AppIcon.icns
|
|
29
|
+
buildPhase: resources
|
|
30
|
+
<%- end -%>
|
|
31
|
+
info:
|
|
32
|
+
path: src/Info.plist
|
|
33
|
+
properties:
|
|
34
|
+
CFBundleName: <%= config.name.inspect %>
|
|
35
|
+
CFBundleDisplayName: <%= config.name.inspect %>
|
|
36
|
+
CFBundleShortVersionString: "<%= config.version %>"
|
|
37
|
+
NSHighResolutionCapable: true
|
|
38
|
+
<%- if config.icon -%>
|
|
39
|
+
CFBundleIconFile: AppIcon
|
|
40
|
+
<%- end -%>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'reflex/packager/extension'
|
|
2
|
+
|
|
3
|
+
require 'reflex/packager/profile'
|
|
4
|
+
require 'reflex/packager/config'
|
|
5
|
+
require 'reflex/packager/platform'
|
|
6
|
+
require 'reflex/packager/macos'
|
|
7
|
+
require 'reflex/packager/cli'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
module Reflex::Packager
|
|
11
|
+
PLATFORMS = {macos: MacOS}
|
|
12
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# -*- mode: ruby -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
require_relative 'lib/reflex/packager/extension'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |s|
|
|
8
|
+
glob = -> *patterns do
|
|
9
|
+
patterns.map {|pat| Dir.glob(pat).to_a}.flatten
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
ext = Reflex::Packager::Extension
|
|
13
|
+
name = ext.name true
|
|
14
|
+
rdocs = glob.call *%w[README]
|
|
15
|
+
|
|
16
|
+
s.name = name
|
|
17
|
+
s.version = ext.version
|
|
18
|
+
s.license = 'MIT'
|
|
19
|
+
s.summary = 'Package Reflex applications as native app bundles.'
|
|
20
|
+
s.description = 'CLI tool to package Reflex applications as native macOS application bundles.'
|
|
21
|
+
s.authors = %w[xordog]
|
|
22
|
+
s.email = 'xordog@gmail.com'
|
|
23
|
+
s.homepage = "https://github.com/xord/reflex-packager"
|
|
24
|
+
|
|
25
|
+
s.platform = Gem::Platform::RUBY
|
|
26
|
+
s.required_ruby_version = '>= 3.0.0'
|
|
27
|
+
|
|
28
|
+
s.add_dependency 'xot', '~> 0.3.15'
|
|
29
|
+
s.add_dependency 'rucy', '~> 0.3.15'
|
|
30
|
+
s.add_dependency 'rays', '~> 0.3.16'
|
|
31
|
+
s.add_dependency 'reflexion', '~> 0.5.0'
|
|
32
|
+
|
|
33
|
+
s.files = `git ls-files`.split $/
|
|
34
|
+
s.executables = s.files.grep(%r{^bin/}) {|f| File.basename f}
|
|
35
|
+
s.test_files = s.files.grep %r{^(test|spec|features)/}
|
|
36
|
+
s.extra_rdoc_files = rdocs.to_a
|
|
37
|
+
end
|