motion-sparkle-sandbox 0.8.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.rubocop.yml +7 -0
- data/.rubocop_todo.yml +183 -0
- data/.ruby-version +1 -0
- data/.travis.yml +37 -0
- data/Gemfile +13 -3
- data/LICENSE +1 -0
- data/README.md +72 -72
- data/Rakefile +2 -7
- data/bin/bundle +105 -0
- data/bin/byebug +29 -0
- data/bin/coderay +29 -0
- data/bin/fuzzy_match +29 -0
- data/bin/htmldiff +29 -0
- data/bin/httpclient +29 -0
- data/bin/ldiff +29 -0
- data/bin/pod +29 -0
- data/bin/pry +29 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/ruby-parse +29 -0
- data/bin/ruby-rewrite +29 -0
- data/bin/sandbox-pod +29 -0
- data/bin/xcodeproj +29 -0
- data/lib/motion/project/appcast.rb +177 -104
- data/lib/motion/project/indent_string.rb +18 -0
- data/lib/motion/project/install.rb +22 -66
- data/lib/motion/project/package.rb +52 -36
- data/lib/motion/project/project.rb +41 -51
- data/lib/motion/project/rake_tasks.rb +28 -32
- data/lib/motion/project/setup.rb +67 -73
- data/lib/motion/project/sparkle.rb +244 -148
- data/lib/motion/project/templates.rb +24 -22
- data/lib/motion-sparkle-sandbox/version.rb +5 -0
- data/lib/motion-sparkle-sandbox.rb +20 -3
- data/motion-sparkle-sandbox.gemspec +26 -16
- data/sample-app/.gitignore +9 -0
- data/sample-app/Gemfile +8 -0
- data/sample-app/README.md +87 -0
- data/sample-app/Rakefile +44 -0
- data/sample-app/app/app_delegate.rb +15 -0
- data/sample-app/app/menu.rb +155 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/1024x1024.png +0 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png +0 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png +0 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png +0 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png +0 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png +0 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png +0 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png +0 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png +0 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png +0 -0
- data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png +0 -0
- data/sample-app/resources/Assets.xcassets/Contents.json +6 -0
- data/sample-app/resources/Credits.rtf +29 -0
- data/sample-app/spec/main_spec.rb +11 -0
- data/spec/setup_spec.rb +42 -0
- data/spec/sparkle_spec.rb +92 -73
- data/spec/spec_helper.rb +100 -27
- data/spec/spec_utils.rb +68 -0
- data/vendor/.git_keep +0 -0
- metadata +78 -15
- data/vendor/README.md +0 -24
- data/vendor/Sparkle.zip +0 -0
- data/vendor/codesign_xpc +0 -46
- data/vendor/generate_appcast +0 -0
@@ -1,127 +1,200 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motion
|
4
|
+
module Project
|
5
|
+
class Sparkle
|
6
|
+
# Generate the appcast.
|
7
|
+
# Note: We do not support the old DSA keys, only the newer EdDSA keys.
|
8
|
+
# See https://sparkle-project.org/documentation/eddsa-migration
|
9
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
10
|
+
def generate_appcast
|
11
|
+
generate_appcast_app = "#{vendored_sparkle_path}/bin/generate_appcast"
|
12
|
+
path = (project_path + archive_folder).realpath
|
13
|
+
appcast_filename = (path + appcast.feed_filename)
|
14
|
+
|
15
|
+
args = []
|
16
|
+
|
17
|
+
FileUtils.mkdir_p(path) unless File.exist?(path)
|
18
|
+
|
19
|
+
App.info('Sparkle', "Generating appcast using `#{generate_appcast_app}`")
|
20
|
+
puts "from files in `#{path}`...".indent(11)
|
21
|
+
|
22
|
+
if appcast.use_exported_private_key && File.exist?(private_key_path)
|
23
|
+
# -s <private-EdDSA-key> The private EdDSA string (128 characters). If not
|
24
|
+
# specified, the private EdDSA key will be read from
|
25
|
+
# the Keychain instead.
|
26
|
+
private_key = File.read(private_key_path)
|
27
|
+
args << "-s=#{private_key}"
|
28
|
+
end
|
3
29
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
30
|
+
# --download-url-prefix <url> A URL that will be used as prefix for the URL from
|
31
|
+
# where updates will be downloaded.
|
32
|
+
args << "--download-url-prefix=#{appcast.package_base_url}" if appcast.package_base_url.present?
|
33
|
+
|
34
|
+
# --release-notes-url-prefix <url> A URL that will be used as prefix for constructing
|
35
|
+
# URLs for release notes.
|
36
|
+
args << "--release-notes-url-prefix=#{appcast.notes_base_url}" if appcast.notes_base_url.present?
|
37
|
+
|
38
|
+
# --link <link> A URL to the application's website which Sparkle may
|
39
|
+
# use for directing users to if they cannot download a
|
40
|
+
# new update from within the application. This will be
|
41
|
+
# used for new generated update items. By default, no
|
42
|
+
# product link is used.
|
43
|
+
|
44
|
+
# --versions <versions> An optional comma delimited list of application
|
45
|
+
# versions (specified by CFBundleVersion) to generate
|
46
|
+
# new update items for. By default, new update items
|
47
|
+
# are inferred from the available archives and are only
|
48
|
+
# generated if they are in the latest 5 updates in the
|
49
|
+
# appcast.
|
50
|
+
|
51
|
+
# --maximum-deltas <maximum-deltas>
|
52
|
+
# The maximum number of delta items to create for the
|
53
|
+
# latest update for each minimum required operating
|
54
|
+
# system. (default: 5)
|
55
|
+
|
56
|
+
# --channel <channel-name>
|
57
|
+
# The Sparkle channel name that will be used for
|
58
|
+
# generating new updates. By default, no channel is
|
59
|
+
# used. Old applications need to be using Sparkle 2 to
|
60
|
+
# use this feature.
|
61
|
+
|
62
|
+
# --major-version <major-version>
|
63
|
+
# The last major or minimum autoupdate sparkle:version
|
64
|
+
# that will be used for generating new updates. By
|
65
|
+
# default, no last major version is used.
|
66
|
+
|
67
|
+
# --phased-rollout-interval <phased-rollout-interval>
|
68
|
+
# The phased rollout interval in seconds that will be
|
69
|
+
# used for generating new updates. By default, no
|
70
|
+
# phased rollout interval is used.
|
71
|
+
|
72
|
+
# --critical-update-version <critical-update-version>
|
73
|
+
# The last critical update sparkle:version that will be
|
74
|
+
# used for generating new updates. An empty string
|
75
|
+
# argument will treat this update as critical coming
|
76
|
+
# from any application version. By default, no last
|
77
|
+
# critical update version is used. Old applications
|
78
|
+
# need to be using Sparkle 2 to use this feature.
|
79
|
+
|
80
|
+
# --informational-update-versions <informational-update-versions>
|
81
|
+
# A comma delimited list of application
|
82
|
+
# sparkle:version's that will see newly generated
|
83
|
+
# updates as being informational only. An empty string
|
84
|
+
# argument will treat this update as informational
|
85
|
+
# coming from any application version. By default,
|
86
|
+
# updates are not informational only. --link must also
|
87
|
+
# be provided. Old applications need to be using
|
88
|
+
# Sparkle 2 to use this feature.
|
89
|
+
|
90
|
+
# -o <output-path> Path to filename for the generated appcast (allowed
|
91
|
+
# when only one will be created).
|
92
|
+
|
93
|
+
# -f <private-dsa-key-file> Path to the private DSA key file. Only use this
|
94
|
+
# option for transitioning to EdDSA from older updates.
|
95
|
+
# Note: only for supporting a legacy app that used DSA keys. Check if the
|
96
|
+
# default DSA key exists in `sparkle/config/dsa_priv.pem` and if it does,
|
97
|
+
# add it to the command.
|
98
|
+
if File.exist?(legacy_private_key_path)
|
99
|
+
App.info 'Sparkle', "Also signing with legacy DSA key at #{legacy_private_key_path}"
|
100
|
+
args << "-f=#{legacy_private_key_path}"
|
9
101
|
end
|
10
|
-
App.info 'Create', "./#{release_notes_path}"
|
11
|
-
else
|
12
|
-
App.fail "Release notes template not found as expected at ./#{release_notes_template_path}"
|
13
|
-
end
|
14
|
-
end
|
15
102
|
|
16
|
-
|
17
|
-
create_release_folder
|
18
|
-
appcast_file = File.open("#{sparkle_release_path}/#{appcast.feed_filename}", 'w') do |f|
|
19
|
-
xml_string = ''
|
20
|
-
doc = REXML::Formatters::Pretty.new
|
21
|
-
doc.write(appcast_xml, xml_string)
|
22
|
-
f << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
23
|
-
f << xml_string
|
24
|
-
f << "\n"
|
25
|
-
end
|
26
|
-
if appcast_file
|
27
|
-
App.info "Create", "./#{sparkle_release_path}/#{appcast.feed_filename}"
|
28
|
-
else
|
29
|
-
App.info "Fail", "./#{sparkle_release_path}/#{appcast.feed_filename} not created"
|
30
|
-
end
|
31
|
-
end
|
103
|
+
args << "-o=#{appcast_filename}" if appcast_filename.present?
|
32
104
|
|
33
|
-
|
34
|
-
rss = REXML::Element.new 'rss'
|
35
|
-
rss.attributes['xmlns:atom'] = "http://www.w3.org/2005/Atom"
|
36
|
-
rss.attributes['xmlns:sparkle'] = "http://www.andymatuschak.org/xml-namespaces/sparkle"
|
37
|
-
rss.attributes['xmlns:version'] = "2.0"
|
38
|
-
rss.attributes['xmlns:dc'] = "http://purl.org/dc/elements/1.1/"
|
39
|
-
channel = rss.add_element 'channel'
|
40
|
-
channel.add_element('title').text = @config.name
|
41
|
-
channel.add_element('description').text = "#{@config.name} updates"
|
42
|
-
channel.add_element('link').text = @config.info_plist["SUFeedURL"]
|
43
|
-
channel.add_element('language').text = 'en'
|
44
|
-
channel.add_element('pubDate').text = Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")
|
45
|
-
atom_link = channel.add_element('atom:link')
|
46
|
-
atom_link.attributes['href'] = @config.info_plist["SUFeedURL"]
|
47
|
-
atom_link.attributes['rel'] = 'self'
|
48
|
-
atom_link.attributes['type'] = "application/rss+xml"
|
49
|
-
item = channel.add_element 'item'
|
50
|
-
item.add_element('title').text = "#{@config.name} #{@config.short_version}"
|
51
|
-
item.add_element('pubDate').text = Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")
|
52
|
-
guid = item.add_element('guid')
|
53
|
-
guid.text = "#{@config.name}-#{@config.short_version}"
|
54
|
-
guid.attributes['isPermaLink'] = false
|
55
|
-
item.add_element('sparkle:releaseNotesLink').text = "#{appcast.notes_url}"
|
56
|
-
enclosure = item.add_element('enclosure')
|
57
|
-
enclosure.attributes['url'] = "#{appcast.package_url}"
|
58
|
-
enclosure.attributes['length'] = "#{@package_size}"
|
59
|
-
enclosure.attributes['type'] = "application/octet-stream"
|
60
|
-
enclosure.attributes['sparkle:version'] = @config.version
|
61
|
-
enclosure.attributes['sparkle:shortVersionString'] = @config.short_version
|
62
|
-
enclosure.attributes['sparkle:dsaSignature'] = @package_signature
|
63
|
-
rss
|
64
|
-
end
|
105
|
+
App.info 'Executing', [generate_appcast_app, *args, path.to_s].join(' ')
|
65
106
|
|
66
|
-
|
67
|
-
sparkle_config_path + "release_notes.template.erb"
|
68
|
-
end
|
107
|
+
results, status = Open3.capture2e(generate_appcast_app, *args, path.to_s)
|
69
108
|
|
70
|
-
|
71
|
-
|
72
|
-
end
|
109
|
+
App.info('Sparkle', "Saved appcast to `#{appcast_filename}`") if status.success?
|
110
|
+
puts results.indent(11)
|
73
111
|
|
74
|
-
|
75
|
-
sparkle_release_path + appcast.notes_filename.to_s
|
76
|
-
end
|
112
|
+
return unless status.success?
|
77
113
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
else
|
82
|
-
App.fail "Missing #{release_notes_content_path}"
|
114
|
+
puts
|
115
|
+
puts "SUFeedURL : #{feed_url}".indent(11)
|
116
|
+
puts "SUPublicEDKey : #{public_EdDSA_key}".indent(11)
|
83
117
|
end
|
84
|
-
|
118
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
85
119
|
|
86
|
-
|
87
|
-
|
88
|
-
|
120
|
+
def generate_appcast_help
|
121
|
+
generate_appcast_app = "#{vendored_sparkle_path}/bin/generate_appcast"
|
122
|
+
results, _status = Open3.capture2e(generate_appcast_app, '--help')
|
123
|
+
puts results
|
124
|
+
end
|
125
|
+
|
126
|
+
def create_release_notes
|
127
|
+
App.fail "Release notes template not found as expected at ./#{release_notes_template_path}" unless File.exist?(release_notes_template_path)
|
128
|
+
|
129
|
+
create_release_folder
|
89
130
|
|
131
|
+
File.open(release_notes_path.to_s, 'w') do |f|
|
132
|
+
template = File.read(release_notes_template_path)
|
133
|
+
f << ERB.new(template).result(binding)
|
134
|
+
end
|
90
135
|
|
91
|
-
|
92
|
-
attr_accessor :base_url,
|
93
|
-
:feed_base_url,
|
94
|
-
:feed_filename,
|
95
|
-
:notes_base_url,
|
96
|
-
:notes_filename,
|
97
|
-
:package_base_url,
|
98
|
-
:package_filename,
|
99
|
-
:archive_folder
|
100
|
-
|
101
|
-
def initialize
|
102
|
-
@feed_base_url = nil
|
103
|
-
@feed_filename = 'releases.xml'
|
104
|
-
@notes_base_url = nil
|
105
|
-
@notes_filename = 'release_notes.html'
|
106
|
-
@package_base_url = nil
|
107
|
-
@package_filename = nil
|
108
|
-
@base_url = nil
|
109
|
-
@archive_folder = nil
|
136
|
+
App.info 'Create', "./#{release_notes_path}"
|
110
137
|
end
|
111
138
|
|
112
|
-
def
|
113
|
-
|
139
|
+
def release_notes_template_path
|
140
|
+
sparkle_config_path.join('release_notes.template.erb')
|
114
141
|
end
|
115
142
|
|
116
|
-
def
|
117
|
-
|
143
|
+
def release_notes_content_path
|
144
|
+
sparkle_config_path.join('release_notes.content.html')
|
118
145
|
end
|
119
146
|
|
120
|
-
def
|
121
|
-
"#{
|
147
|
+
def release_notes_path
|
148
|
+
sparkle_release_path + (appcast.notes_filename || "#{app_name}.#{@config.short_version}.html")
|
122
149
|
end
|
123
150
|
|
124
|
-
|
151
|
+
def release_notes_content
|
152
|
+
if File.exist?(release_notes_content_path)
|
153
|
+
File.read(release_notes_content_path)
|
154
|
+
else
|
155
|
+
App.fail "Missing #{release_notes_content_path}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def release_notes_html
|
160
|
+
release_notes_content
|
161
|
+
end
|
125
162
|
|
163
|
+
class Appcast
|
164
|
+
attr_accessor :base_url,
|
165
|
+
:feed_base_url,
|
166
|
+
:feed_filename,
|
167
|
+
:notes_filename,
|
168
|
+
:package_filename,
|
169
|
+
:archive_folder,
|
170
|
+
:use_exported_private_key
|
171
|
+
attr_writer :notes_base_url,
|
172
|
+
:package_base_url
|
173
|
+
|
174
|
+
def initialize
|
175
|
+
@feed_base_url = nil
|
176
|
+
@feed_filename = 'releases.xml'
|
177
|
+
@notes_base_url = nil
|
178
|
+
@notes_filename = nil
|
179
|
+
@package_base_url = nil
|
180
|
+
@package_filename = nil
|
181
|
+
@base_url = nil
|
182
|
+
@archive_folder = nil
|
183
|
+
@use_exported_private_key = false
|
184
|
+
end
|
185
|
+
|
186
|
+
def feed_url
|
187
|
+
"#{feed_base_url || base_url}#{feed_filename}"
|
188
|
+
end
|
189
|
+
|
190
|
+
def notes_base_url
|
191
|
+
@notes_base_url || base_url
|
192
|
+
end
|
193
|
+
|
194
|
+
def package_base_url
|
195
|
+
@package_base_url || base_url
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
126
199
|
end
|
127
200
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# https://makandracards.com/makandra/6087-ruby-indent-a-string
|
4
|
+
String.class_eval do
|
5
|
+
def indent(count, char = ' ', skip_first_line: false)
|
6
|
+
gsub(/([^\n]*)(\n|$)/) do |_match|
|
7
|
+
last_iteration = (Regexp.last_match(1) == '' && Regexp.last_match(2) == '')
|
8
|
+
line = String.new
|
9
|
+
line << (char * count) unless last_iteration || skip_first_line
|
10
|
+
line << Regexp.last_match(1)
|
11
|
+
line << Regexp.last_match(2)
|
12
|
+
|
13
|
+
skip_first_line = false
|
14
|
+
|
15
|
+
line
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,75 +1,31 @@
|
|
1
|
-
|
2
|
-
class Sparkle
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
Pathname.new(sparkle_vendor_path + SPARKLE_ZIP_FILE)
|
11
|
-
end
|
12
|
-
|
13
|
-
def sparkle_vendor_path
|
14
|
-
file_path = Pathname.new File.dirname(__FILE__)
|
15
|
-
(file_path.parent.parent.parent + 'vendor/').to_s
|
16
|
-
end
|
17
|
-
|
18
|
-
def sparkle_path
|
19
|
-
Pathname.new(vendor_path + 'Sparkle')
|
20
|
-
end
|
21
|
-
|
22
|
-
def sparkle_framework_path
|
23
|
-
Pathname.new(vendor_path + 'Sparkle/Sparkle.framework')
|
24
|
-
end
|
25
|
-
|
26
|
-
def sparkle_xpc_path
|
27
|
-
Pathname.new(vendor_path + 'Sparkle/XPCServices')
|
28
|
-
end
|
29
|
-
|
30
|
-
def sparkle_zipball
|
31
|
-
Pathname.new(vendor_path + SPARKLE_ZIP_FILE)
|
32
|
-
end
|
33
|
-
|
34
|
-
def copy_zipball
|
35
|
-
`cp #{sparkle_distrib} #{sparkle_zipball}`
|
36
|
-
end
|
37
|
-
|
38
|
-
def unzip
|
39
|
-
`unzip #{sparkle_zipball.to_s} -d #{vendor_path.to_s}`
|
40
|
-
`rm #{sparkle_zipball}`
|
41
|
-
end
|
42
|
-
|
43
|
-
def installed?
|
44
|
-
File.directory?(sparkle_framework_path)
|
45
|
-
end
|
3
|
+
module Motion
|
4
|
+
module Project
|
5
|
+
class Sparkle
|
6
|
+
def vendored_sparkle_path
|
7
|
+
vendor_path.join('Pods/Sparkle')
|
8
|
+
end
|
46
9
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
unzip
|
51
|
-
end
|
10
|
+
def vendored_sparkle_framework_path
|
11
|
+
vendored_sparkle_path.join('Sparkle.framework')
|
12
|
+
end
|
52
13
|
|
53
|
-
|
54
|
-
|
55
|
-
|
14
|
+
def vendored_sparkle_xpc_path
|
15
|
+
vendored_sparkle_path.join('XPCServices')
|
16
|
+
end
|
56
17
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
18
|
+
def installed?
|
19
|
+
File.directory?(vendored_sparkle_framework_path)
|
20
|
+
end
|
61
21
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
please explain your setup and problem as an issue on GitHub at:
|
69
|
-
https://github.com/digitalmoksha/motion-sparkle-sandbox/issues
|
70
|
-
"
|
22
|
+
def verify_installation
|
23
|
+
if installed?
|
24
|
+
App.info 'Sparkle', "Framework installed in #{vendored_sparkle_framework_path}"
|
25
|
+
else
|
26
|
+
App.fail "Sparkle Cocoapod not correctly installed to #{vendored_sparkle_path}. Run `rake pod:install`."
|
27
|
+
end
|
71
28
|
end
|
72
29
|
end
|
73
|
-
|
74
30
|
end
|
75
31
|
end
|
@@ -1,44 +1,60 @@
|
|
1
|
-
|
2
|
-
class Sparkle
|
3
|
-
|
4
|
-
def package
|
5
|
-
return unless setup_ok?
|
6
|
-
create_release_folder
|
7
|
-
@config.build_mode = :release
|
8
|
-
return unless create_zip_file
|
9
|
-
App.info "Release", version_string
|
10
|
-
App.info "Version", @config.short_version
|
11
|
-
App.info "Build", @config.version || 'unspecified in Rakefile'
|
12
|
-
App.info "Size", @package_size.to_s
|
13
|
-
sign_package
|
14
|
-
create_appcast
|
15
|
-
create_release_notes
|
16
|
-
`open #{sparkle_release_path}`
|
17
|
-
end
|
1
|
+
# frozen_string_literal: true
|
18
2
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
3
|
+
module Motion
|
4
|
+
module Project
|
5
|
+
class Sparkle
|
6
|
+
def package
|
7
|
+
return unless setup_ok?
|
8
|
+
|
9
|
+
create_release_folder
|
10
|
+
@config.build_mode = :release
|
11
|
+
return unless create_zip_file
|
12
|
+
|
13
|
+
App.info 'Release', version_string
|
14
|
+
App.info 'Version', @config.short_version
|
15
|
+
App.info 'Build', @config.version || 'unspecified in Rakefile'
|
16
|
+
App.info 'Size', @package_size.to_s
|
17
|
+
|
18
|
+
sign_package
|
19
|
+
create_release_notes
|
20
|
+
|
21
|
+
`open #{sparkle_release_path}`
|
25
22
|
end
|
26
|
-
|
27
|
-
|
23
|
+
|
24
|
+
def create_zip_file
|
25
|
+
App.fail 'You need to build your app with the Release target to use Sparkle' unless File.exist?(app_bundle_path)
|
26
|
+
|
27
|
+
App.info 'Create', "./#{sparkle_release_path}/#{zip_file}"
|
28
|
+
|
29
|
+
if File.exist?("#{sparkle_release_path}/#{zip_file}")
|
30
|
+
App.fail "Release already exists at ./#{sparkle_release_path}/#{zip_file} (remove it manually with `rake sparkle:clean`)"
|
31
|
+
end
|
32
|
+
|
33
|
+
FileUtils.cd(app_release_path) do
|
34
|
+
`zip -r --symlinks "#{zip_file}" "#{app_file}"`
|
35
|
+
end
|
36
|
+
|
37
|
+
FileUtils.mv "#{app_release_path}/#{zip_file}", "./#{sparkle_release_path}/"
|
38
|
+
|
39
|
+
@package_file = zip_file
|
40
|
+
@package_size = File.size "./#{sparkle_release_path}/#{zip_file}"
|
28
41
|
end
|
29
|
-
FileUtils.mv "#{app_release_path}/#{zip_file}", "./#{sparkle_release_path}/"
|
30
|
-
App.info "Create", "./#{sparkle_release_path}/#{zip_file}"
|
31
|
-
@package_file = zip_file
|
32
|
-
@package_size = File.size "./#{sparkle_release_path}/#{zip_file}"
|
33
|
-
end
|
34
42
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
43
|
+
def sign_package
|
44
|
+
package = "./#{sparkle_release_path}/#{zip_file}"
|
45
|
+
sign_update_app = "#{vendored_sparkle_path}/bin/sign_update"
|
46
|
+
args = []
|
47
|
+
|
48
|
+
if appcast.use_exported_private_key && File.exist?(private_key_path)
|
49
|
+
# -s <private-key> The private EdDSA (ed25519) key
|
50
|
+
private_key = File.read(private_key_path)
|
51
|
+
args << "-s=#{private_key}"
|
52
|
+
end
|
41
53
|
|
54
|
+
results, _status = Open3.capture2e(sign_update_app, *args, package)
|
42
55
|
|
56
|
+
App.info 'Signature', results
|
57
|
+
end
|
58
|
+
end
|
43
59
|
end
|
44
60
|
end
|
@@ -1,67 +1,57 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
variable :sparkle
|
3
|
+
require 'open3'
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
@sparkle
|
12
|
-
end
|
13
|
-
end
|
5
|
+
module Motion
|
6
|
+
module Project
|
7
|
+
class Config
|
8
|
+
variable :sparkle
|
14
9
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
App.info "Sparkle", "Setup OK"
|
20
|
-
else
|
21
|
-
exit 1
|
22
|
-
end
|
23
|
-
build_without_sparkle(platform, opts)
|
10
|
+
def sparkle(&block)
|
11
|
+
@sparkle ||= Motion::Project::Sparkle.new(self)
|
12
|
+
@sparkle.instance_eval(&block) if block
|
13
|
+
@sparkle
|
24
14
|
end
|
25
|
-
|
26
|
-
alias_method "build_without_sparkle", "build"
|
27
|
-
alias_method "build", "build_with_sparkle"
|
28
15
|
end
|
29
|
-
end
|
30
16
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
#------------------------------------------------------------------------------
|
36
|
-
def codesign_with_sparkle(config, platform)
|
37
|
-
if App.config.embedded_frameworks.any? {|item| item.to_s.include?('Sparkle.framework')}
|
38
|
-
bundle_path = App.config.app_bundle('MacOSX')
|
17
|
+
class App
|
18
|
+
class << self
|
19
|
+
def build_with_sparkle(platform, opts = {})
|
20
|
+
App.fail 'Sparkle not setup correctly' unless App.config.sparkle.setup_ok?
|
39
21
|
|
40
|
-
|
41
|
-
App.info 'Sparkle', 'Removing unnecessary executables...'
|
42
|
-
sparkle_path = File.join(bundle_path, "Frameworks/Sparkle.framework")
|
43
|
-
['Resources/Autoupdate', 'Resources/Updater.app'].each do |item|
|
44
|
-
bundle = File.join(sparkle_path, item)
|
45
|
-
FileUtils.rm_r(bundle) if File.exist?(bundle)
|
46
|
-
end
|
22
|
+
App.info 'Sparkle', 'Setup OK'
|
47
23
|
|
48
|
-
|
49
|
-
|
24
|
+
build_without_sparkle(platform, opts)
|
25
|
+
end
|
50
26
|
|
51
|
-
|
52
|
-
|
27
|
+
alias_method 'build_without_sparkle', 'build'
|
28
|
+
alias_method 'build', 'build_with_sparkle'
|
29
|
+
end
|
30
|
+
end
|
53
31
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
32
|
+
class Builder
|
33
|
+
# The XPC services are already residing in the Sparkle package. But we need
|
34
|
+
# to re-sign the entire package to ensure all executables have the hardened
|
35
|
+
# runtime and correct certificate.
|
36
|
+
#------------------------------------------------------------------------------
|
37
|
+
def codesign_with_sparkle(config, platform)
|
38
|
+
if App.config.embedded_frameworks.any? { |item| item.to_s.include?('Sparkle.framework') }
|
39
|
+
bundle_path = App.config.app_bundle('MacOSX')
|
40
|
+
sparkle_path = File.join(bundle_path, 'Frameworks', 'Sparkle.framework')
|
41
|
+
|
42
|
+
`/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}/Versions/B/Autoupdate"`
|
43
|
+
`/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}/Versions/B/Updater.app"`
|
44
|
+
`/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}/Versions/B/XPCServices/org.sparkle-project.InstallerLauncher.xpc"`
|
45
|
+
`/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime --entitlements "./vendor/Pods/Sparkle/Entitlements/org.sparkle-project.Downloader.entitlements" "#{sparkle_path}/Versions/B/XPCServices/org.sparkle-project.Downloader.xpc"` # rubocop:disable Layout/LineLength
|
46
|
+
|
47
|
+
`/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}"`
|
58
48
|
end
|
49
|
+
|
50
|
+
codesign_without_sparkle(config, platform)
|
59
51
|
end
|
60
52
|
|
61
|
-
codesign_without_sparkle
|
53
|
+
alias_method 'codesign_without_sparkle', 'codesign'
|
54
|
+
alias_method 'codesign', 'codesign_with_sparkle'
|
62
55
|
end
|
63
|
-
|
64
|
-
alias_method "codesign_without_sparkle", "codesign"
|
65
|
-
alias_method "codesign", "codesign_with_sparkle"
|
66
56
|
end
|
67
57
|
end
|