motion-sparkle-sandbox 2.0.0 → 2.0.1

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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +7 -0
  5. data/.rubocop_todo.yml +183 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +37 -0
  8. data/Gemfile +13 -3
  9. data/README.md +73 -71
  10. data/Rakefile +2 -7
  11. data/bin/bundle +103 -0
  12. data/bin/byebug +29 -0
  13. data/bin/coderay +29 -0
  14. data/bin/fuzzy_match +29 -0
  15. data/bin/htmldiff +29 -0
  16. data/bin/httpclient +29 -0
  17. data/bin/ldiff +29 -0
  18. data/bin/pod +29 -0
  19. data/bin/pry +29 -0
  20. data/bin/rake +29 -0
  21. data/bin/rspec +29 -0
  22. data/bin/rubocop +29 -0
  23. data/bin/ruby-parse +29 -0
  24. data/bin/ruby-rewrite +29 -0
  25. data/bin/sandbox-pod +29 -0
  26. data/bin/xcodeproj +29 -0
  27. data/lib/motion/project/appcast.rb +177 -104
  28. data/lib/motion/project/indent_string.rb +18 -0
  29. data/lib/motion/project/install.rb +22 -66
  30. data/lib/motion/project/package.rb +52 -36
  31. data/lib/motion/project/project.rb +41 -51
  32. data/lib/motion/project/rake_tasks.rb +28 -32
  33. data/lib/motion/project/setup.rb +67 -73
  34. data/lib/motion/project/sparkle.rb +244 -148
  35. data/lib/motion/project/templates.rb +24 -22
  36. data/lib/motion-sparkle-sandbox/version.rb +5 -0
  37. data/lib/motion-sparkle-sandbox.rb +19 -3
  38. data/motion-sparkle-sandbox.gemspec +26 -16
  39. data/sample-app/.gitignore +9 -0
  40. data/sample-app/Gemfile +8 -0
  41. data/sample-app/README.md +87 -0
  42. data/sample-app/Rakefile +44 -0
  43. data/sample-app/app/app_delegate.rb +15 -0
  44. data/sample-app/app/menu.rb +155 -0
  45. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/1024x1024.png +0 -0
  46. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  47. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png +0 -0
  48. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png +0 -0
  49. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png +0 -0
  50. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png +0 -0
  51. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png +0 -0
  52. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png +0 -0
  53. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png +0 -0
  54. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png +0 -0
  55. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png +0 -0
  56. data/sample-app/resources/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png +0 -0
  57. data/sample-app/resources/Assets.xcassets/Contents.json +6 -0
  58. data/sample-app/resources/Credits.rtf +29 -0
  59. data/sample-app/spec/main_spec.rb +11 -0
  60. data/spec/setup_spec.rb +67 -0
  61. data/spec/sparkle_spec.rb +113 -65
  62. data/spec/spec_helper.rb +94 -29
  63. data/spec/spec_utils.rb +32 -0
  64. data/vendor/.git_keep +0 -0
  65. metadata +78 -14
  66. data/vendor/README.md +0 -34
  67. data/vendor/Sparkle.zip +0 -0
  68. data/vendor/codesign_embedded_executable +0 -46
  69. data/vendor/generate_appcast +0 -0
@@ -1,127 +1,200 @@
1
- module Motion::Project
2
- class Sparkle
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
- def create_release_notes
5
- if File.exist?(release_notes_template_path)
6
- File.open("#{release_notes_path}", "w") do |f|
7
- template = File.read(release_notes_template_path)
8
- f << ERB.new(template).result(binding)
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
- def create_appcast
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
- def appcast_xml
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
- def release_notes_template_path
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
- def release_notes_content_path
71
- sparkle_config_path + "release_notes.content.html"
72
- end
109
+ App.info('Sparkle', "Saved appcast to `#{appcast_filename}`") if status.success?
110
+ puts results.indent(11)
73
111
 
74
- def release_notes_path
75
- sparkle_release_path + appcast.notes_filename.to_s
76
- end
112
+ return unless status.success?
77
113
 
78
- def release_notes_content
79
- if File.exist?(release_notes_content_path)
80
- File.read(release_notes_content_path)
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
- end
118
+ # rubocop:enable Metrics/CyclomaticComplexity
85
119
 
86
- def release_notes_html
87
- release_notes_content
88
- end
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
- class Appcast
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 feed_url
113
- "#{feed_base_url || base_url}/#{feed_filename}"
139
+ def release_notes_template_path
140
+ sparkle_config_path.join('release_notes.template.erb')
114
141
  end
115
142
 
116
- def notes_url
117
- "#{notes_base_url || base_url}/#{notes_filename}"
143
+ def release_notes_content_path
144
+ sparkle_config_path.join('release_notes.content.html')
118
145
  end
119
146
 
120
- def package_url
121
- "#{package_base_url || base_url}/#{package_filename}"
147
+ def release_notes_path
148
+ sparkle_release_path + (appcast.notes_filename || "#{app_name}.#{@config.short_version}.html")
122
149
  end
123
150
 
124
- end
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
- module Motion::Project
2
- class Sparkle
1
+ # frozen_string_literal: true
3
2
 
4
- SPARKLE_ZIP_FILE = 'Sparkle.zip'
5
-
6
- def sparkle_distrib
7
- file_path = Pathname.new File.dirname(__FILE__)
8
- distrib_path = "vendor/#{SPARKLE_ZIP_FILE}"
9
- (file_path.parent.parent.parent + distrib_path).to_s
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
- def install
48
- FileUtils.rm_rf(sparkle_path) if File.directory?(sparkle_path) # force clean install
49
- copy_zipball
50
- unzip
51
- end
10
+ def vendored_sparkle_framework_path
11
+ vendored_sparkle_path.join('Sparkle.framework')
12
+ end
52
13
 
53
- def embed
54
- @config.embedded_frameworks << sparkle_framework_path
55
- end
14
+ def vendored_sparkle_xpc_path
15
+ vendored_sparkle_path.join('XPCServices')
16
+ end
56
17
 
57
- def install_and_embed
58
- install unless installed?
59
- embed
60
- end
18
+ def installed?
19
+ File.directory?(vendored_sparkle_framework_path)
20
+ end
61
21
 
62
- def verify_installation
63
- if installed?
64
- App.info "Sparkle", "Framework installed in #{sparkle_framework_path.to_s}"
65
- else
66
- App.fail "Sparkle framework not correctly copied to #{sparkle_framework_path.to_s}
67
- Run `rake sparkle:install` manually or, if the problem persists,
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
- module Motion::Project
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
- def create_zip_file
20
- unless File.exist?(app_bundle_path)
21
- App.fail "You need to build your app with the Release target to use Sparkle"
22
- end
23
- if File.exist?("#{sparkle_release_path}/#{zip_file}")
24
- App.fail "Release already exists at ./#{sparkle_release_path}/#{zip_file} (remove it manually with `rake sparkle:clean`)"
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
- FileUtils.cd(app_release_path) do
27
- `zip -r --symlinks "#{zip_file}" "#{app_file}"`
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
- def sign_package
36
- package = "./#{sparkle_release_path}/#{zip_file}"
37
- @package_signature = `#{openssl} dgst -sha1 -binary < "#{package}" | #{openssl} dgst -dss1 -sign "#{private_key_path}" | #{openssl} enc -base64`
38
- @package_signature = @package_signature.strip
39
- App.info "Signature", "\"#{@package_signature}\""
40
- end
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
- module Motion::Project
1
+ # frozen_string_literal: true
2
2
 
3
- class Config
4
- variable :sparkle
3
+ require 'open3'
5
4
 
6
- def sparkle(&block)
7
- @sparkle ||= Motion::Project::Sparkle.new(self)
8
- if block
9
- @sparkle.instance_eval &block
10
- end
11
- @sparkle
12
- end
13
- end
5
+ module Motion
6
+ module Project
7
+ class Config
8
+ variable :sparkle
14
9
 
15
- class App
16
- class << self
17
- def build_with_sparkle(platform, opts = {})
18
- if App.config.sparkle.setup_ok?
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
- class Builder
32
- # if we're using the sandboxed version of Sparkle, then we need to copy the
33
- # xpc services to the proper folder and sign them. This has to be done
34
- # before we sign the app itself
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
- if File.directory?(App.config.sparkle.sparkle_xpc_path)
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
- xpc_path = File.join(bundle_path, "XPCServices")
49
- App.info 'Sparkle', "Copying XPCServices to #{xpc_path}"
24
+ build_without_sparkle(platform, opts)
25
+ end
50
26
 
51
- FileUtils.mkdir_p(xpc_path)
52
- `cp -R #{App.config.sparkle.sparkle_xpc_path}/*.xpc "#{xpc_path}"`
27
+ alias_method 'build_without_sparkle', 'build'
28
+ alias_method 'build', 'build_with_sparkle'
29
+ end
30
+ end
53
31
 
54
- Dir.glob("#{xpc_path}/*.xpc").each do |path|
55
- App.info 'Codesign', path
56
- results = `#{App.config.sparkle.sparkle_vendor_path}/codesign_embedded_executable "#{App.config.codesign_certificate}" "#{File.expand_path(path)}" 2>&1`
57
- end
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(config, platform)
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