motion-sparkle-sandbox 2.0.1 → 2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,45 +1,178 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/ClassLength
3
4
  module Motion
4
5
  module Project
5
6
  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)
7
+ class Appcast
8
+ PARAMS = %i[
9
+ package_base_url
10
+ package_filename
11
+ notes_base_url
12
+ notes_filename
13
+ use_exported_private_key
14
+ base_url
15
+ releases_folder
16
+ feed_base_url
17
+ feed_filename
18
+ ].freeze
19
+
20
+ CLI_OPTIONS = {
21
+ account: '--account',
22
+ private_ed_key_file: '--ed-key-file',
23
+ download_url_prefix: '--download-url-prefix',
24
+ release_notes_url_prefix: '--release-notes-url-prefix',
25
+ full_release_notes_url: '--full-release-notes-url',
26
+ link: '--link',
27
+ versions: '--versions',
28
+ maximum_deltas: '--maximum-deltas',
29
+ delta_compression: '--delta-compression',
30
+ delta_compression_level: '--delta-compression-level',
31
+ channel: '--channel',
32
+ major_version: '--major-version',
33
+ ignore_skipped_upgrades_below_version: '--ignore-skipped-upgrades-below-version',
34
+ phased_rollout_interval: '--phased-rollout-interval',
35
+ critical_update_version: '--critical-update-version',
36
+ informational_update_versions: '--informational-update-versions',
37
+ output_path: '-o'
38
+ }.freeze
39
+
40
+ attr_accessor :base_url,
41
+ :feed_base_url,
42
+ :feed_filename,
43
+ :notes_filename,
44
+ :package_filename,
45
+ :releases_folder,
46
+ :use_exported_private_key,
47
+ :cli_options
48
+ attr_writer :notes_base_url,
49
+ :package_base_url
50
+
51
+ def initialize(sparkle_object)
52
+ @sparkle = sparkle_object
53
+ @cli_options = {
54
+ account: 'ed25519' # Sparkle's default account
55
+ }
56
+
57
+ @feed_base_url = nil
58
+ @feed_filename = 'releases.xml'
59
+ @notes_base_url = nil
60
+ @notes_filename = nil
61
+ @package_base_url = nil
62
+ @package_filename = nil
63
+ @base_url = nil
64
+ @releases_folder = nil
65
+ @use_exported_private_key = false
66
+ end
67
+
68
+ def process_option(key, value)
69
+ if CLI_OPTIONS.keys.include?(key)
70
+ cli_options[key] = value
71
+ elsif PARAMS.include?(key)
72
+ send("#{key}=", value)
73
+ else
74
+ return false
75
+ end
76
+
77
+ true
78
+ end
79
+
80
+ def feed_url
81
+ "#{feed_base_url || base_url}#{feed_filename}"
82
+ end
83
+
84
+ def notes_base_url
85
+ @notes_base_url || base_url
86
+ end
14
87
 
15
- args = []
88
+ def package_base_url
89
+ @package_base_url || base_url
90
+ end
91
+
92
+ def prepare_args
93
+ args = []
94
+
95
+ account(args)
96
+ private_ed_key_file(args)
97
+ download_url_prefix(args)
98
+ release_notes_url_prefix(args)
99
+ full_release_notes_url(args)
100
+ link(args)
101
+ versions(args)
102
+ maximum_deltas(args)
103
+ delta_compression(args)
104
+ delta_compression_level(args)
105
+ channel(args)
106
+ major_version(args)
107
+ ignore_skipped_upgrades_below_version(args)
108
+ phased_rollout_interval(args)
109
+ critical_update_version(args)
110
+ informational_update_versions(args)
111
+ output_path(args)
112
+
113
+ args
114
+ end
115
+
116
+ private
16
117
 
17
- FileUtils.mkdir_p(path) unless File.exist?(path)
118
+ # --account <account> The account name in your keychain associated with
119
+ # your private EdDSA (ed25519) key to use for signing
120
+ # new updates. (default: ed25519)
121
+ def account(args)
122
+ return unless cli_options[:account].present?
18
123
 
19
- App.info('Sparkle', "Generating appcast using `#{generate_appcast_app}`")
20
- puts "from files in `#{path}`...".indent(11)
124
+ args << "--account=#{cli_options[:account]}"
125
+ end
21
126
 
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}"
127
+ # -s <private-EdDSA-key> The private EdDSA string (128 characters). If not
128
+ # specified, the private EdDSA key will be read from
129
+ # the Keychain instead.
130
+ def private_ed_key_file(args)
131
+ if cli_options[:private_ed_key_file].present?
132
+ args << "--ed-key-file=#{cli_options[:private_ed_key_file]}"
133
+ elsif use_exported_private_key && File.exist?(@sparkle.private_key_path)
134
+ args << "--ed-key-file=#{@sparkle.private_key_path}"
135
+ end
28
136
  end
29
137
 
30
138
  # --download-url-prefix <url> A URL that will be used as prefix for the URL from
31
139
  # where updates will be downloaded.
32
- args << "--download-url-prefix=#{appcast.package_base_url}" if appcast.package_base_url.present?
140
+ def download_url_prefix(args)
141
+ if cli_options[:download_url_prefix].present?
142
+ args << "--download-url-prefix=#{cli_options[:download_url_prefix]}"
143
+ elsif package_base_url.present?
144
+ args << "--download-url-prefix=#{package_base_url}"
145
+ end
146
+ end
33
147
 
34
148
  # --release-notes-url-prefix <url> A URL that will be used as prefix for constructing
35
149
  # URLs for release notes.
36
- args << "--release-notes-url-prefix=#{appcast.notes_base_url}" if appcast.notes_base_url.present?
150
+ def release_notes_url_prefix(args)
151
+ if cli_options[:release_notes_url_prefix].present?
152
+ args << "--release-notes-url-prefix=#{cli_options[:release_notes_url_prefix]}"
153
+ elsif notes_base_url.present?
154
+ args << "--release-notes-url-prefix=#{notes_base_url}"
155
+ end
156
+ end
157
+
158
+ # --full-release-notes-url <url>
159
+ # A URL that will be used for the full release notes.
160
+ def full_release_notes_url(args)
161
+ return unless cli_options[:full_release_notes_url].present?
162
+
163
+ args << "--full-release-notes-url=#{cli_options[:full_release_notes_url]}"
164
+ end
37
165
 
38
166
  # --link <link> A URL to the application's website which Sparkle may
39
167
  # use for directing users to if they cannot download a
40
168
  # new update from within the application. This will be
41
169
  # used for new generated update items. By default, no
42
170
  # product link is used.
171
+ def link(args)
172
+ return unless cli_options[:link].present?
173
+
174
+ args << "--link=#{cli_options[:link]}"
175
+ end
43
176
 
44
177
  # --versions <versions> An optional comma delimited list of application
45
178
  # versions (specified by CFBundleVersion) to generate
@@ -47,27 +180,88 @@ module Motion
47
180
  # are inferred from the available archives and are only
48
181
  # generated if they are in the latest 5 updates in the
49
182
  # appcast.
183
+ def versions(args)
184
+ return unless cli_options[:versions].present?
185
+
186
+ args << "--versions=#{cli_options[:versions]}"
187
+ end
50
188
 
51
189
  # --maximum-deltas <maximum-deltas>
52
190
  # The maximum number of delta items to create for the
53
191
  # latest update for each minimum required operating
54
192
  # system. (default: 5)
193
+ def maximum_deltas(args)
194
+ return unless cli_options[:maximum_deltas].present?
195
+
196
+ args << "--maximum-deltas=#{cli_options[:maximum_deltas]}"
197
+ end
198
+
199
+ # --delta-compression <delta-compression>
200
+ # The compression method to use for generating delta
201
+ # updates. Supported methods for version 3 delta files
202
+ # are 'lzma', 'bzip2', 'zlib', 'lzfse', 'lz4', 'none',
203
+ # and 'default'. Note that version 2 delta files only
204
+ # support 'bzip2', 'none', and 'default' so other
205
+ # methods will be ignored if version 2 files are being
206
+ # generated. The 'default' compression for version 3
207
+ # delta files is currently lzma. (default: default)
208
+ def delta_compression(args)
209
+ return unless cli_options[:delta_compression].present?
210
+
211
+ args << "--delta-compression=#{cli_options[:delta_compression]}"
212
+ end
213
+
214
+ # --delta-compression-level <delta-compression-level>
215
+ # The compression level to use for generating delta
216
+ # updates. This only applies if the compression method
217
+ # used is bzip2 which accepts values from 1 - 9. A
218
+ # special value of 0 will use the default compression
219
+ # level. (default: 0)
220
+ def delta_compression_level(args)
221
+ return unless cli_options[:delta_compression_level].present?
222
+
223
+ args << "--delta-compression-level=#{cli_options[:delta_compression_level]}"
224
+ end
55
225
 
56
226
  # --channel <channel-name>
57
227
  # The Sparkle channel name that will be used for
58
228
  # generating new updates. By default, no channel is
59
229
  # used. Old applications need to be using Sparkle 2 to
60
230
  # use this feature.
231
+ def channel(args)
232
+ return unless cli_options[:channel].present?
233
+
234
+ args << "--channel=#{cli_options[:channel]}"
235
+ end
61
236
 
62
237
  # --major-version <major-version>
63
238
  # The last major or minimum autoupdate sparkle:version
64
239
  # that will be used for generating new updates. By
65
240
  # default, no last major version is used.
241
+ def major_version(args)
242
+ return unless cli_options[:major_version].present?
243
+
244
+ args << "--major-version=#{cli_options[:major_version]}"
245
+ end
246
+
247
+ # --ignore-skipped-upgrades-below-version <below-version>
248
+ # Ignore skipped major upgrades below this specified
249
+ # version. Only applicable for major upgrades.
250
+ def ignore_skipped_upgrades_below_version(args)
251
+ return unless cli_options[:ignore_skipped_upgrades_below_version].present?
252
+
253
+ args << "--ignore-skipped-upgrades-below-version=#{cli_options[:ignore_skipped_upgrades_below_version]}"
254
+ end
66
255
 
67
256
  # --phased-rollout-interval <phased-rollout-interval>
68
257
  # The phased rollout interval in seconds that will be
69
258
  # used for generating new updates. By default, no
70
259
  # phased rollout interval is used.
260
+ def phased_rollout_interval(args)
261
+ return unless cli_options[:phased_rollout_interval].present?
262
+
263
+ args << "--phased-rollout-interval=#{cli_options[:phased_rollout_interval]}"
264
+ end
71
265
 
72
266
  # --critical-update-version <critical-update-version>
73
267
  # The last critical update sparkle:version that will be
@@ -76,125 +270,40 @@ module Motion
76
270
  # from any application version. By default, no last
77
271
  # critical update version is used. Old applications
78
272
  # need to be using Sparkle 2 to use this feature.
273
+ def critical_update_version(args)
274
+ return unless cli_options[:critical_update_version].present?
275
+
276
+ args << "--critical-update-version=#{cli_options[:critical_update_version]}"
277
+ end
79
278
 
80
279
  # --informational-update-versions <informational-update-versions>
81
280
  # A comma delimited list of application
82
281
  # sparkle:version's that will see newly generated
83
282
  # updates as being informational only. An empty string
84
283
  # 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.
284
+ # coming from any application version. Prefix a version
285
+ # string with '<' to indicate (eg "<2.5") to indicate
286
+ # older versions than the one specified should treat
287
+ # the update as informational only. By default, updates
288
+ # are not informational only. --link must also be
289
+ # provided. Old applications need to be using Sparkle 2
290
+ # to use this feature, and 2.1 or later to use the '<'
291
+ # upper bound feature.
292
+ def informational_update_versions(args)
293
+ return unless cli_options[:informational_update_versions].present?
294
+
295
+ args << "--informational-update-versions=#{cli_options[:informational_update_versions]}"
296
+ end
89
297
 
90
298
  # -o <output-path> Path to filename for the generated appcast (allowed
91
299
  # when only one will be created).
300
+ def output_path(args)
301
+ return unless cli_options[:output_path].present?
92
302
 
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}"
101
- end
102
-
103
- args << "-o=#{appcast_filename}" if appcast_filename.present?
104
-
105
- App.info 'Executing', [generate_appcast_app, *args, path.to_s].join(' ')
106
-
107
- results, status = Open3.capture2e(generate_appcast_app, *args, path.to_s)
108
-
109
- App.info('Sparkle', "Saved appcast to `#{appcast_filename}`") if status.success?
110
- puts results.indent(11)
111
-
112
- return unless status.success?
113
-
114
- puts
115
- puts "SUFeedURL : #{feed_url}".indent(11)
116
- puts "SUPublicEDKey : #{public_EdDSA_key}".indent(11)
117
- end
118
- # rubocop:enable Metrics/CyclomaticComplexity
119
-
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
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
135
-
136
- App.info 'Create', "./#{release_notes_path}"
137
- end
138
-
139
- def release_notes_template_path
140
- sparkle_config_path.join('release_notes.template.erb')
141
- end
142
-
143
- def release_notes_content_path
144
- sparkle_config_path.join('release_notes.content.html')
145
- end
146
-
147
- def release_notes_path
148
- sparkle_release_path + (appcast.notes_filename || "#{app_name}.#{@config.short_version}.html")
149
- end
150
-
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
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
303
+ args << "-o=#{cli_options[:output_path]}"
196
304
  end
197
305
  end
198
306
  end
199
307
  end
200
308
  end
309
+ # rubocop:enable Metrics/ClassLength
@@ -10,6 +10,7 @@ module Motion
10
10
  def sparkle(&block)
11
11
  @sparkle ||= Motion::Project::Sparkle.new(self)
12
12
  @sparkle.instance_eval(&block) if block
13
+ @sparkle.after_initialize
13
14
  @sparkle
14
15
  end
15
16
  end
@@ -39,10 +40,18 @@ module Motion
39
40
  bundle_path = App.config.app_bundle('MacOSX')
40
41
  sparkle_path = File.join(bundle_path, 'Frameworks', 'Sparkle.framework')
41
42
 
43
+ # strip out any header files, as they are not needed for a built product, and Xcode
44
+ # typically does this. Their permissions are set such that it conflicts
45
+ # with building the deltas. See https://github.com/sparkle-project/Sparkle/issues/1972
46
+ `rm -fr "#{sparkle_path}/Versions/B/PrivateHeaders"`
47
+ `rm "#{sparkle_path}/PrivateHeaders"`
48
+ `rm -fr "#{sparkle_path}/Versions/B/Headers"`
49
+ `rm "#{sparkle_path}/Headers"`
50
+
42
51
  `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}/Versions/B/Autoupdate"`
43
52
  `/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
53
+ `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}/Versions/B/XPCServices/Installer.xpc"`
54
+ `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime --entitlements "./vendor/Pods/Sparkle/Entitlements/Downloader.entitlements" "#{sparkle_path}/Versions/B/XPCServices/Downloader.xpc"` # rubocop:disable Layout/LineLength
46
55
 
47
56
  `/usr/bin/codesign -f -s "#{config.codesign_certificate}" -o runtime "#{sparkle_path}"`
48
57
  end
@@ -1,17 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Sparkle specific rake tasks
4
+ # rubocop:disable Metrics/BlockLength
4
5
  namespace :sparkle do
5
6
  desc 'Sparkle Help'
6
7
  task :help do
7
8
  puts <<~HELP
9
+
10
+
8
11
  During initial Sparkle setup, run these rake tasks:
9
12
 
10
13
  1. `rake sparkle:setup_certificates`
11
14
  2. `rake sparkle:setup`
12
15
 
13
16
  Then after running `rake build:release`, you can run
14
- `rake sparkle:package`
17
+
18
+ 3. `rake sparkle:package` create the zipped package and release notes
19
+ 4. `rake sparkle:copy_to_release` copy package/notes into release folder
20
+ 5. `rake sparkle:generate_appcast` generate the appcast
15
21
  HELP
16
22
  end
17
23
 
@@ -40,6 +46,13 @@ namespace :sparkle do
40
46
  sparkle.sign_package
41
47
  end
42
48
 
49
+ desc 'Copy the release notes and zip archive to the release folder'
50
+ task :copy_to_release do
51
+ App.config_without_setup.build_mode = :release
52
+ sparkle = App.config.sparkle
53
+ sparkle.copy_to_release
54
+ end
55
+
43
56
  desc "Generate the appcast xml feed using Sparkle's `generate_appcast`"
44
57
  task :generate_appcast do
45
58
  App.config_without_setup.build_mode = :release
@@ -83,3 +96,4 @@ namespace :clean do
83
96
  end
84
97
  end
85
98
  end
99
+ # rubocop:enable Metrics/BlockLength
@@ -27,7 +27,7 @@ module Motion
27
27
  end
28
28
 
29
29
  def check_public_key
30
- return true if public_EdDSA_key.present?
30
+ return true if public_ed_dsa_key.present?
31
31
 
32
32
  App.fail 'Sparkle :public_key is nil or blank. Please check your Rakefile.'
33
33
  end
@@ -45,7 +45,7 @@ module Motion
45
45
  App.fail "Missing `#{private_key_path}`. Please run `rake sparkle:setup_certificates` or check the docs to know where to put them."
46
46
  end
47
47
 
48
- unless public_EdDSA_key.present?
48
+ unless public_ed_dsa_key.present?
49
49
  return false if silence
50
50
 
51
51
  App.fail "Missing `#{public_key_path}`. Did you configure `release :public_key` correctly in the Rakefile? Advanced: recreate your public key with `rake sparkle:recreate_public_key`"