xcode-install 2.3.1 → 2.6.7
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 +5 -5
- data/.circleci/config.yml +33 -0
- data/.github/workflows/ci.yml +36 -0
- data/.gitignore +1 -0
- data/README.md +53 -9
- data/bin//360/237/216/211 +1 -1
- data/lib/xcode/install/cli.rb +4 -1
- data/lib/xcode/install/install.rb +10 -3
- data/lib/xcode/install/select.rb +7 -1
- data/lib/xcode/install/version.rb +1 -1
- data/lib/xcode/install.rb +296 -63
- data/spec/curl_spec.rb +1 -1
- data/spec/fixtures/xcode.json +30 -1
- data/spec/fixtures/xcode_63.json +28 -44
- data/spec/fixtures/yolo.json +1 -1
- data/spec/install_spec.rb +12 -4
- data/spec/installer_spec.rb +126 -0
- data/spec/json_spec.rb +14 -4
- data/spec/list_spec.rb +56 -3
- data/spec/prerelease_spec.rb +1 -1
- data/xcode-install.gemspec +4 -1
- metadata +14 -13
- data/.travis.yml +0 -10
data/lib/xcode/install.rb
CHANGED
|
@@ -6,42 +6,122 @@ require 'json'
|
|
|
6
6
|
require 'rubygems/version'
|
|
7
7
|
require 'xcode/install/command'
|
|
8
8
|
require 'xcode/install/version'
|
|
9
|
+
require 'shellwords'
|
|
10
|
+
require 'open3'
|
|
11
|
+
require 'fastlane'
|
|
12
|
+
require 'fastlane/helper/sh_helper'
|
|
13
|
+
require 'fastlane/action'
|
|
14
|
+
require 'fastlane/actions/verify_xcode'
|
|
9
15
|
|
|
10
16
|
module XcodeInstall
|
|
11
17
|
CACHE_DIR = Pathname.new("#{ENV['HOME']}/Library/Caches/XcodeInstall")
|
|
12
18
|
class Curl
|
|
13
19
|
COOKIES_PATH = Pathname.new('/tmp/curl-cookies.txt')
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
# @param url: The URL to download
|
|
22
|
+
# @param directory: The directory to download this file into
|
|
23
|
+
# @param cookies: Any cookies we should use for the download (used for auth with Apple)
|
|
24
|
+
# @param output: A PathName for where we want to store the file
|
|
25
|
+
# @param progress: parse and show the progress?
|
|
26
|
+
# @param progress_block: A block that's called whenever we have an updated progress %
|
|
27
|
+
# the parameter is a single number that's literally percent (e.g. 1, 50, 80 or 100)
|
|
28
|
+
# @param retry_download_count: A count to retry the downloading Xcode dmg/xip
|
|
29
|
+
# rubocop:disable Metrics/AbcSize
|
|
30
|
+
def fetch(url: nil,
|
|
31
|
+
directory: nil,
|
|
32
|
+
cookies: nil,
|
|
33
|
+
output: nil,
|
|
34
|
+
progress: nil,
|
|
35
|
+
progress_block: nil,
|
|
36
|
+
retry_download_count: 3)
|
|
16
37
|
options = cookies.nil? ? [] : ['--cookie', cookies, '--cookie-jar', COOKIES_PATH]
|
|
17
|
-
# options << ' -vvv'
|
|
18
38
|
|
|
19
39
|
uri = URI.parse(url)
|
|
20
40
|
output ||= File.basename(uri.path)
|
|
21
41
|
output = (Pathname.new(directory) + Pathname.new(output)) if directory
|
|
22
42
|
|
|
43
|
+
# Piping over all of stderr over to a temporary file
|
|
44
|
+
# the file content looks like this:
|
|
45
|
+
# 0 4766M 0 6835k 0 0 573k 0 2:21:58 0:00:11 2:21:47 902k
|
|
46
|
+
# This way we can parse the current %
|
|
47
|
+
# The header is
|
|
48
|
+
# % Total % Received % Xferd Average Speed Time Time Time Current
|
|
49
|
+
#
|
|
50
|
+
# Discussion for this on GH: https://github.com/KrauseFx/xcode-install/issues/276
|
|
51
|
+
# It was not easily possible to reimplement the same system using built-in methods
|
|
52
|
+
# especially when it comes to resuming downloads
|
|
53
|
+
# Piping over stderror to Ruby directly didn't work, due to the lack of flushing
|
|
54
|
+
# from curl. The only reasonable way to trigger this, is to pipe things directly into a
|
|
55
|
+
# local file, and parse that, and just poll that. We could get real time updates using
|
|
56
|
+
# the `tail` command or similar, however the download task is not time sensitive enough
|
|
57
|
+
# to make this worth the extra complexity, that's why we just poll and
|
|
58
|
+
# wait for the process to be finished
|
|
59
|
+
progress_log_file = File.join(CACHE_DIR, "progress.#{Time.now.to_i}.progress")
|
|
60
|
+
FileUtils.rm_f(progress_log_file)
|
|
61
|
+
|
|
23
62
|
retry_options = ['--retry', '3']
|
|
24
|
-
|
|
25
|
-
|
|
63
|
+
command = [
|
|
64
|
+
'curl',
|
|
65
|
+
'--disable',
|
|
66
|
+
*options,
|
|
67
|
+
*retry_options,
|
|
68
|
+
'--location',
|
|
69
|
+
'--continue-at',
|
|
70
|
+
'-',
|
|
71
|
+
'--output',
|
|
72
|
+
output,
|
|
73
|
+
url
|
|
74
|
+
].map(&:to_s)
|
|
75
|
+
|
|
76
|
+
command_string = command.collect(&:shellescape).join(' ')
|
|
77
|
+
command_string += " 2> #{progress_log_file}" # to not run shellescape on the `2>`
|
|
26
78
|
|
|
27
79
|
# Run the curl command in a loop, retry when curl exit status is 18
|
|
28
80
|
# "Partial file. Only a part of the file was transferred."
|
|
29
81
|
# https://curl.haxx.se/mail/archive-2008-07/0098.html
|
|
30
82
|
# https://github.com/KrauseFx/xcode-install/issues/210
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
83
|
+
retry_download_count.times do
|
|
84
|
+
# Non-blocking call of Open3
|
|
85
|
+
# We're not using the block based syntax, as the bacon testing
|
|
86
|
+
# library doesn't seem to support writing tests for it
|
|
87
|
+
stdin, stdout, stderr, wait_thr = Open3.popen3(command_string)
|
|
88
|
+
|
|
89
|
+
# Poll the file and see if we're done yet
|
|
90
|
+
while wait_thr.alive?
|
|
91
|
+
sleep(0.5) # it's not critical for this to be real-time
|
|
92
|
+
next unless File.exist?(progress_log_file) # it might take longer for it to be created
|
|
93
|
+
|
|
94
|
+
progress_content = File.read(progress_log_file).split("\r").last
|
|
95
|
+
|
|
96
|
+
# Print out the progress for the CLI
|
|
97
|
+
if progress
|
|
98
|
+
print "\r#{progress_content}%"
|
|
99
|
+
$stdout.flush
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Call back the block for other processes that might be interested
|
|
103
|
+
matched = progress_content.match(/^\s*(\d+)/)
|
|
104
|
+
next unless matched && matched.length == 2
|
|
105
|
+
percent = matched[1].to_i
|
|
106
|
+
progress_block.call(percent) if progress_block
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# as we're not making use of the block-based syntax
|
|
110
|
+
# we need to manually close those
|
|
111
|
+
stdin.close
|
|
112
|
+
stdout.close
|
|
113
|
+
stderr.close
|
|
35
114
|
|
|
36
|
-
|
|
37
|
-
return exit_code.zero? unless exit_code == 18
|
|
115
|
+
return wait_thr.value.success? if wait_thr.value.success?
|
|
38
116
|
end
|
|
39
117
|
false
|
|
40
118
|
ensure
|
|
41
119
|
FileUtils.rm_f(COOKIES_PATH)
|
|
120
|
+
FileUtils.rm_f(progress_log_file)
|
|
42
121
|
end
|
|
43
122
|
end
|
|
44
123
|
|
|
124
|
+
# rubocop:disable Metrics/ClassLength
|
|
45
125
|
class Installer
|
|
46
126
|
attr_reader :xcodes
|
|
47
127
|
|
|
@@ -57,17 +137,52 @@ module XcodeInstall
|
|
|
57
137
|
File.symlink?(SYMLINK_PATH) ? SYMLINK_PATH : nil
|
|
58
138
|
end
|
|
59
139
|
|
|
60
|
-
def download(version, progress, url = nil)
|
|
61
|
-
|
|
62
|
-
|
|
140
|
+
def download(version, progress, url = nil, progress_block = nil, retry_download_count = 3)
|
|
141
|
+
xcode = find_xcode_version(version) if url.nil?
|
|
142
|
+
return if url.nil? && xcode.nil?
|
|
143
|
+
|
|
63
144
|
dmg_file = Pathname.new(File.basename(url || xcode.path))
|
|
64
145
|
|
|
65
|
-
result = Curl.new.fetch(
|
|
146
|
+
result = Curl.new.fetch(
|
|
147
|
+
url: url || xcode.url,
|
|
148
|
+
directory: CACHE_DIR,
|
|
149
|
+
cookies: url ? nil : spaceship.cookie,
|
|
150
|
+
output: dmg_file,
|
|
151
|
+
progress: progress,
|
|
152
|
+
progress_block: progress_block,
|
|
153
|
+
retry_download_count: retry_download_count
|
|
154
|
+
)
|
|
66
155
|
result ? CACHE_DIR + dmg_file : nil
|
|
67
156
|
end
|
|
68
157
|
|
|
158
|
+
def find_xcode_version(version)
|
|
159
|
+
# By checking for the name and the version we have the best success rate
|
|
160
|
+
# Sometimes the user might pass
|
|
161
|
+
# "4.3 for Lion"
|
|
162
|
+
# or they might pass an actual Gem::Version
|
|
163
|
+
# Gem::Version.new("8.0.0")
|
|
164
|
+
# which should automatically match with "Xcode 8"
|
|
165
|
+
|
|
166
|
+
begin
|
|
167
|
+
parsed_version = Gem::Version.new(version)
|
|
168
|
+
rescue ArgumentError
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
seedlist.each do |current_seed|
|
|
173
|
+
return current_seed if current_seed.name == version
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
seedlist.each do |current_seed|
|
|
177
|
+
return current_seed if parsed_version && current_seed.version == parsed_version
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
nil
|
|
181
|
+
end
|
|
182
|
+
|
|
69
183
|
def exist?(version)
|
|
70
|
-
|
|
184
|
+
return true if find_xcode_version(version)
|
|
185
|
+
false
|
|
71
186
|
end
|
|
72
187
|
|
|
73
188
|
def installed?(version)
|
|
@@ -80,15 +195,39 @@ module XcodeInstall
|
|
|
80
195
|
end
|
|
81
196
|
end
|
|
82
197
|
|
|
198
|
+
# Returns an array of `XcodeInstall::Xcode`
|
|
199
|
+
# <XcodeInstall::Xcode:0x007fa1d451c390
|
|
200
|
+
# @date_modified=2015,
|
|
201
|
+
# @name="6.4",
|
|
202
|
+
# @path="/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg",
|
|
203
|
+
# @url=
|
|
204
|
+
# "https://developer.apple.com/devcenter/download.action?path=/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg",
|
|
205
|
+
# @version=Gem::Version.new("6.4")>,
|
|
206
|
+
#
|
|
207
|
+
# the resulting list is sorted with the most recent release as first element
|
|
208
|
+
def seedlist
|
|
209
|
+
@xcodes = Marshal.load(File.read(LIST_FILE)) if LIST_FILE.exist? && xcodes.nil?
|
|
210
|
+
all_xcodes = (xcodes || fetch_seedlist)
|
|
211
|
+
|
|
212
|
+
# We have to set the `installed` value here, as we might still use
|
|
213
|
+
# the cached list of available Xcode versions, but have a new Xcode
|
|
214
|
+
# installed in the mean-time
|
|
215
|
+
cached_installed_versions = installed_versions.map(&:bundle_version)
|
|
216
|
+
all_xcodes.each do |current_xcode|
|
|
217
|
+
current_xcode.installed = cached_installed_versions.include?(current_xcode.version)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
all_xcodes.sort_by { |seed| [seed.version, -seed.date_modified] }.reverse
|
|
221
|
+
end
|
|
222
|
+
|
|
83
223
|
def install_dmg(dmg_path, suffix = '', switch = true, clean = true)
|
|
84
|
-
archive_util = '/System/Library/CoreServices/Applications/Archive Utility.app/Contents/MacOS/Archive Utility'
|
|
85
224
|
prompt = "Please authenticate for Xcode installation.\nPassword: "
|
|
86
225
|
xcode_path = "/Applications/Xcode#{suffix}.app"
|
|
87
226
|
|
|
88
227
|
if dmg_path.extname == '.xip'
|
|
89
|
-
`
|
|
90
|
-
xcode_orig_path =
|
|
91
|
-
xcode_beta_path =
|
|
228
|
+
`xip -x #{dmg_path}`
|
|
229
|
+
xcode_orig_path = File.join(Dir.pwd, 'Xcode.app')
|
|
230
|
+
xcode_beta_path = File.join(Dir.pwd, 'Xcode-beta.app')
|
|
92
231
|
if Pathname.new(xcode_orig_path).exist?
|
|
93
232
|
`sudo -p "#{prompt}" mv "#{xcode_orig_path}" "#{xcode_path}"`
|
|
94
233
|
elsif Pathname.new(xcode_beta_path).exist?
|
|
@@ -121,13 +260,14 @@ HELP
|
|
|
121
260
|
`umount "/Volumes/Xcode"`
|
|
122
261
|
end
|
|
123
262
|
|
|
124
|
-
|
|
263
|
+
xcode = InstalledXcode.new(xcode_path)
|
|
264
|
+
|
|
265
|
+
unless xcode.verify_integrity
|
|
125
266
|
`sudo rm -rf #{xcode_path}`
|
|
126
267
|
return
|
|
127
268
|
end
|
|
128
269
|
|
|
129
270
|
enable_developer_mode
|
|
130
|
-
xcode = InstalledXcode.new(xcode_path)
|
|
131
271
|
xcode.approve_license
|
|
132
272
|
xcode.install_components
|
|
133
273
|
|
|
@@ -142,12 +282,13 @@ HELP
|
|
|
142
282
|
FileUtils.rm_f(dmg_path) if clean
|
|
143
283
|
end
|
|
144
284
|
|
|
145
|
-
|
|
146
|
-
|
|
285
|
+
# rubocop:disable Metrics/ParameterLists
|
|
286
|
+
def install_version(version, switch = true, clean = true, install = true, progress = true, url = nil, show_release_notes = true, progress_block = nil, retry_download_count = 3)
|
|
287
|
+
dmg_path = get_dmg(version, progress, url, progress_block, retry_download_count)
|
|
147
288
|
fail Informative, "Failed to download Xcode #{version}." if dmg_path.nil?
|
|
148
289
|
|
|
149
290
|
if install
|
|
150
|
-
install_dmg(dmg_path, "-#{version.split(' ')
|
|
291
|
+
install_dmg(dmg_path, "-#{version.to_s.split(' ').join('.')}", switch, clean)
|
|
151
292
|
else
|
|
152
293
|
puts "Downloaded Xcode #{version} to '#{dmg_path}'"
|
|
153
294
|
end
|
|
@@ -162,12 +303,21 @@ HELP
|
|
|
162
303
|
end
|
|
163
304
|
|
|
164
305
|
def list_annotated(xcodes_list)
|
|
165
|
-
installed = installed_versions.map(&:
|
|
166
|
-
|
|
306
|
+
installed = installed_versions.map(&:appname_version)
|
|
307
|
+
|
|
308
|
+
xcodes_list.map do |x|
|
|
309
|
+
xcode_version = x.split(' ') # split version and "beta N", "for Lion"
|
|
310
|
+
xcode_version[0] << '.0' unless xcode_version[0].include?('.')
|
|
311
|
+
|
|
312
|
+
# to match InstalledXcode.appname_version format
|
|
313
|
+
version = Gem::Version.new(xcode_version.join('.'))
|
|
314
|
+
|
|
315
|
+
installed.include?(version) ? "#{x} (installed)" : x
|
|
316
|
+
end.join("\n")
|
|
167
317
|
end
|
|
168
318
|
|
|
169
319
|
def list
|
|
170
|
-
list_annotated(list_versions.sort)
|
|
320
|
+
list_annotated(list_versions.sort { |first, second| compare_versions(first, second) })
|
|
171
321
|
end
|
|
172
322
|
|
|
173
323
|
def rm_list_cache
|
|
@@ -199,14 +349,12 @@ HELP
|
|
|
199
349
|
begin
|
|
200
350
|
Spaceship.login(ENV['XCODE_INSTALL_USER'], ENV['XCODE_INSTALL_PASSWORD'])
|
|
201
351
|
rescue Spaceship::Client::InvalidUserCredentialsError
|
|
202
|
-
|
|
203
|
-
exit(1)
|
|
352
|
+
raise 'The specified Apple developer account credentials are incorrect.'
|
|
204
353
|
rescue Spaceship::Client::NoUserCredentialsError
|
|
205
|
-
|
|
354
|
+
raise <<-HELP
|
|
206
355
|
Please provide your Apple developer account credentials via the
|
|
207
356
|
XCODE_INSTALL_USER and XCODE_INSTALL_PASSWORD environment variables.
|
|
208
357
|
HELP
|
|
209
|
-
exit(1)
|
|
210
358
|
end
|
|
211
359
|
|
|
212
360
|
if ENV.key?('XCODE_INSTALL_TEAM_ID')
|
|
@@ -225,17 +373,18 @@ HELP
|
|
|
225
373
|
`sudo /usr/sbin/dseditgroup -o edit -t group -a staff _developer`
|
|
226
374
|
end
|
|
227
375
|
|
|
228
|
-
def get_dmg(version, progress = true, url = nil)
|
|
376
|
+
def get_dmg(version, progress = true, url = nil, progress_block = nil, retry_download_count = 3)
|
|
229
377
|
if url
|
|
230
378
|
path = Pathname.new(url)
|
|
231
379
|
return path if path.exist?
|
|
232
380
|
end
|
|
233
381
|
if ENV.key?('XCODE_INSTALL_CACHE_DIR')
|
|
234
|
-
|
|
235
|
-
|
|
382
|
+
Pathname.glob(ENV['XCODE_INSTALL_CACHE_DIR'] + '/*').each do |fpath|
|
|
383
|
+
return fpath if /^xcode_#{version}\.dmg|xip$/ =~ fpath.basename.to_s
|
|
384
|
+
end
|
|
236
385
|
end
|
|
237
386
|
|
|
238
|
-
download(version, progress, url)
|
|
387
|
+
download(version, progress, url, progress_block, retry_download_count)
|
|
239
388
|
end
|
|
240
389
|
|
|
241
390
|
def fetch_seedlist
|
|
@@ -253,12 +402,13 @@ HELP
|
|
|
253
402
|
end
|
|
254
403
|
|
|
255
404
|
def installed
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
405
|
+
result = `mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'" 2>/dev/null`.split("\n")
|
|
406
|
+
if result.empty?
|
|
407
|
+
result = `find /Applications -maxdepth 1 -name '*.app' -type d -exec sh -c \
|
|
408
|
+
'if [ "$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" \
|
|
409
|
+
"{}/Contents/Info.plist" 2>/dev/null)" == "com.apple.dt.Xcode" ]; then echo "{}"; fi' ';'`.split("\n")
|
|
259
410
|
end
|
|
260
|
-
|
|
261
|
-
`mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'" 2>/dev/null`.split("\n")
|
|
411
|
+
result
|
|
262
412
|
end
|
|
263
413
|
|
|
264
414
|
def parse_seedlist(seedlist)
|
|
@@ -306,7 +456,7 @@ HELP
|
|
|
306
456
|
return [] if scan.empty?
|
|
307
457
|
|
|
308
458
|
version = scan.first.gsub(/<.*?>/, '').gsub(/.*Xcode /, '')
|
|
309
|
-
link = body.scan(%r{<button .*"(.+?.xip)".*</button>}).first.first
|
|
459
|
+
link = body.scan(%r{<button .*"(.+?.(dmg|xip))".*</button>}).first.first
|
|
310
460
|
notes = body.scan(%r{<a.+?href="(/go/\?id=xcode-.+?)".*>(.*)</a>}).first.first
|
|
311
461
|
links << Xcode.new(version, link, notes)
|
|
312
462
|
end
|
|
@@ -314,14 +464,25 @@ HELP
|
|
|
314
464
|
links
|
|
315
465
|
end
|
|
316
466
|
|
|
317
|
-
def
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
467
|
+
def compare_versions(first, second)
|
|
468
|
+
# Sort by version number
|
|
469
|
+
numeric_comparation = first.to_f <=> second.to_f
|
|
470
|
+
return numeric_comparation if numeric_comparation != 0
|
|
321
471
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
472
|
+
# Return beta versions before others
|
|
473
|
+
is_first_beta = first.include?('beta')
|
|
474
|
+
is_second_beta = second.include?('beta')
|
|
475
|
+
return -1 if is_first_beta && !is_second_beta
|
|
476
|
+
return 1 if !is_first_beta && is_second_beta
|
|
477
|
+
|
|
478
|
+
# Return GM versions before others
|
|
479
|
+
is_first_gm = first.include?('GM')
|
|
480
|
+
is_second_gm = second.include?('GM')
|
|
481
|
+
return -1 if is_first_gm && !is_second_gm
|
|
482
|
+
return 1 if !is_first_gm && is_second_gm
|
|
483
|
+
|
|
484
|
+
# Sort alphabetically
|
|
485
|
+
first <=> second
|
|
325
486
|
end
|
|
326
487
|
|
|
327
488
|
def hdiutil(*args)
|
|
@@ -375,8 +536,14 @@ HELP
|
|
|
375
536
|
end
|
|
376
537
|
end
|
|
377
538
|
|
|
378
|
-
def download(progress)
|
|
379
|
-
result = Curl.new.fetch(
|
|
539
|
+
def download(progress, progress_block = nil, retry_download_count = 3)
|
|
540
|
+
result = Curl.new.fetch(
|
|
541
|
+
url: source,
|
|
542
|
+
directory: CACHE_DIR,
|
|
543
|
+
progress: progress,
|
|
544
|
+
progress_block: progress_block,
|
|
545
|
+
retry_download_count: retry_download_count
|
|
546
|
+
)
|
|
380
547
|
result ? dmg_path : nil
|
|
381
548
|
end
|
|
382
549
|
|
|
@@ -460,7 +627,18 @@ HELP
|
|
|
460
627
|
end
|
|
461
628
|
|
|
462
629
|
def bundle_version
|
|
463
|
-
@bundle_version ||= Gem::Version.new(
|
|
630
|
+
@bundle_version ||= Gem::Version.new(bundle_version_string)
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def appname_version
|
|
634
|
+
appname = @path.basename('.app').to_s
|
|
635
|
+
version_string = appname.split('-').last
|
|
636
|
+
begin
|
|
637
|
+
Gem::Version.new(version_string)
|
|
638
|
+
rescue ArgumentError
|
|
639
|
+
puts 'Unable to determine Xcode version from path name, installed list may not correctly identify installed betas'
|
|
640
|
+
Gem::Version.new(nil)
|
|
641
|
+
end
|
|
464
642
|
end
|
|
465
643
|
|
|
466
644
|
def uuid
|
|
@@ -478,12 +656,22 @@ HELP
|
|
|
478
656
|
end
|
|
479
657
|
|
|
480
658
|
def approve_license
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
659
|
+
if Gem::Version.new(version) < Gem::Version.new('7.3')
|
|
660
|
+
license_info_path = File.join(@path, 'Contents/Resources/LicenseInfo.plist')
|
|
661
|
+
license_id = `/usr/libexec/PlistBuddy -c 'Print :licenseID' #{license_info_path}`
|
|
662
|
+
license_type = `/usr/libexec/PlistBuddy -c 'Print :licenseType' #{license_info_path}`
|
|
663
|
+
license_plist_path = '/Library/Preferences/com.apple.dt.Xcode.plist'
|
|
664
|
+
`sudo rm -rf #{license_plist_path}`
|
|
665
|
+
if license_type == 'GM'
|
|
666
|
+
`sudo /usr/libexec/PlistBuddy -c "add :IDELastGMLicenseAgreedTo string #{license_id}" #{license_plist_path}`
|
|
667
|
+
`sudo /usr/libexec/PlistBuddy -c "add :IDEXcodeVersionForAgreedToGMLicense string #{version}" #{license_plist_path}`
|
|
668
|
+
else
|
|
669
|
+
`sudo /usr/libexec/PlistBuddy -c "add :IDELastBetaLicenseAgreedTo string #{license_id}" #{license_plist_path}`
|
|
670
|
+
`sudo /usr/libexec/PlistBuddy -c "add :IDEXcodeVersionForAgreedToBetaLicense string #{version}" #{license_plist_path}`
|
|
671
|
+
end
|
|
672
|
+
else
|
|
673
|
+
`sudo #{@path}/Contents/Developer/usr/bin/xcodebuild -license accept`
|
|
674
|
+
end
|
|
487
675
|
end
|
|
488
676
|
|
|
489
677
|
def available_simulators
|
|
@@ -497,7 +685,7 @@ HELP
|
|
|
497
685
|
def install_components
|
|
498
686
|
# starting with Xcode 9, we have `xcodebuild -runFirstLaunch` available to do package
|
|
499
687
|
# postinstalls using a documented option
|
|
500
|
-
if Gem::Version.new(
|
|
688
|
+
if Gem::Version.new(version) >= Gem::Version.new('9')
|
|
501
689
|
`sudo #{@path}/Contents/Developer/usr/bin/xcodebuild -runFirstLaunch`
|
|
502
690
|
else
|
|
503
691
|
Dir.glob("#{@path}/Contents/Resources/Packages/*.pkg").each do |pkg|
|
|
@@ -510,30 +698,74 @@ HELP
|
|
|
510
698
|
`touch #{cache_dir}com.apple.dt.Xcode.InstallCheckCache_#{osx_build_version}_#{tools_version}`
|
|
511
699
|
end
|
|
512
700
|
|
|
701
|
+
# This method might take a few ms, this could be improved by implementing https://github.com/KrauseFx/xcode-install/issues/273
|
|
702
|
+
def fetch_version
|
|
703
|
+
output = `DEVELOPER_DIR='' "#{@path}/Contents/Developer/usr/bin/xcodebuild" -version`
|
|
704
|
+
return '0.0' if output.nil? || output.empty? # ¯\_(ツ)_/¯
|
|
705
|
+
output.split("\n").first.split(' ')[1]
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def verify_integrity
|
|
709
|
+
verify_app_security_assessment && verify_app_cert
|
|
710
|
+
end
|
|
711
|
+
|
|
513
712
|
:private
|
|
514
713
|
|
|
714
|
+
def bundle_version_string
|
|
715
|
+
digits = plist_entry(':DTXcode').to_i.to_s
|
|
716
|
+
if digits.length < 3
|
|
717
|
+
digits.split(//).join('.')
|
|
718
|
+
else
|
|
719
|
+
"#{digits[0..-3]}.#{digits[-2]}.#{digits[-1]}"
|
|
720
|
+
end
|
|
721
|
+
end
|
|
722
|
+
|
|
515
723
|
def plist_entry(keypath)
|
|
516
724
|
`/usr/libexec/PlistBuddy -c "Print :#{keypath}" "#{path}/Contents/Info.plist"`.chomp
|
|
517
725
|
end
|
|
518
726
|
|
|
519
|
-
def
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
727
|
+
def verify_app_security_assessment
|
|
728
|
+
puts `/usr/bin/codesign --verify --verbose #{@path}`
|
|
729
|
+
$?.exitstatus.zero?
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
def verify_app_cert
|
|
733
|
+
Fastlane::Actions::VerifyXcodeAction.run(xcode_path: @path.to_s)
|
|
734
|
+
true
|
|
735
|
+
rescue
|
|
736
|
+
false
|
|
523
737
|
end
|
|
524
738
|
end
|
|
525
739
|
|
|
740
|
+
# A version of Xcode we fetched from the Apple Developer Portal
|
|
741
|
+
# we can download & install.
|
|
742
|
+
#
|
|
743
|
+
# Sample object:
|
|
744
|
+
# <XcodeInstall::Xcode:0x007fa1d451c390
|
|
745
|
+
# @date_modified=1573661580,
|
|
746
|
+
# @name="6.4",
|
|
747
|
+
# @path="/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg",
|
|
748
|
+
# @url=
|
|
749
|
+
# "https://developer.apple.com/devcenter/download.action?path=/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg",
|
|
750
|
+
# @version=Gem::Version.new("6.4")>,
|
|
526
751
|
class Xcode
|
|
527
752
|
attr_reader :date_modified
|
|
753
|
+
|
|
754
|
+
# The name might include extra information like "for Lion" or "beta 2"
|
|
528
755
|
attr_reader :name
|
|
529
756
|
attr_reader :path
|
|
530
757
|
attr_reader :url
|
|
531
758
|
attr_reader :version
|
|
532
759
|
attr_reader :release_notes_url
|
|
533
760
|
|
|
761
|
+
# Accessor since it's set by the `Installer`
|
|
762
|
+
attr_accessor :installed
|
|
763
|
+
|
|
764
|
+
alias installed? installed
|
|
765
|
+
|
|
534
766
|
def initialize(json, url = nil, release_notes_url = nil)
|
|
535
767
|
if url.nil?
|
|
536
|
-
@date_modified = json['dateModified'].to_i
|
|
768
|
+
@date_modified = DateTime.strptime(json['dateModified'], '%m/%d/%y %H:%M').strftime('%s').to_i
|
|
537
769
|
@name = json['name'].gsub(/^Xcode /, '')
|
|
538
770
|
@path = json['files'].first['remotePath']
|
|
539
771
|
url_prefix = 'https://developer.apple.com/devcenter/download.action?path='
|
|
@@ -565,6 +797,7 @@ HELP
|
|
|
565
797
|
|
|
566
798
|
def self.new_prerelease(version, url, release_notes_path)
|
|
567
799
|
new('name' => version,
|
|
800
|
+
'dateModified' => '01/01/70 00:00',
|
|
568
801
|
'files' => [{ 'remotePath' => url.split('=').last }],
|
|
569
802
|
'release_notes_path' => release_notes_path)
|
|
570
803
|
end
|
data/spec/curl_spec.rb
CHANGED
data/spec/fixtures/xcode.json
CHANGED
|
@@ -1 +1,30 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "Xcode 9.3",
|
|
3
|
+
"description": "This is the complete Xcode developer toolset for Apple Watch, Apple TV, iPhone, iPad, and Mac. It includes the Xcode IDE, iOS Simulator, and all required tools and frameworks for building iOS, watchOS, tvOS and macOS apps.",
|
|
4
|
+
"isReleased": 1,
|
|
5
|
+
"datePublished": "03/23/18 09:47",
|
|
6
|
+
"dateCreated": "11/01/19 12:00",
|
|
7
|
+
"dateModified": "11/01/19 12:58",
|
|
8
|
+
"categories": [
|
|
9
|
+
{
|
|
10
|
+
"id": 187,
|
|
11
|
+
"name": "Developer Tools",
|
|
12
|
+
"sortOrder": 28
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"filename": "Xcode 9.3.xip",
|
|
18
|
+
"displayName": "Xcode 9.3",
|
|
19
|
+
"remotePath": "/Developer_Tools/Xcode_9.3/Xcode_9.3.xip",
|
|
20
|
+
"fileSize": 5235094775,
|
|
21
|
+
"sortOrder": 0,
|
|
22
|
+
"dateCreated": "11/01/19 19:00",
|
|
23
|
+
"dateModified": "11/01/19 19:58",
|
|
24
|
+
"fileFormat": {
|
|
25
|
+
"extension": ".xip",
|
|
26
|
+
"description": "XIP"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
data/spec/fixtures/xcode_63.json
CHANGED
|
@@ -1,46 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"description": "Disk Image",
|
|
31
|
-
"extension": ".dmg",
|
|
32
|
-
"id": 12
|
|
33
|
-
},
|
|
34
|
-
"id": 35240,
|
|
35
|
-
"name": "Xcode 6.3",
|
|
36
|
-
"remotePath": "/Developer_Tools/Xcode_6.2/Xcode_6.2.dmg",
|
|
37
|
-
"sortOrder": null,
|
|
38
|
-
"state": "ENABLED"
|
|
39
|
-
}
|
|
40
|
-
],
|
|
41
|
-
"id": 23600,
|
|
42
|
-
"name": "Xcode 6.3",
|
|
43
|
-
"prerelease": false,
|
|
44
|
-
"releasedSeed": true,
|
|
45
|
-
"state": "ENABLED"
|
|
2
|
+
"name": "Xcode 6.3",
|
|
3
|
+
"description": "This is the complete Xcode developer toolset for Apple Watch, iPhone, iPad, and Mac. It includes the Xcode IDE, iOS Simulator, and all required tools and frameworks for building OS X and iOS apps.",
|
|
4
|
+
"isReleased": 1,
|
|
5
|
+
"datePublished": "04/08/15 14:36",
|
|
6
|
+
"dateCreated": "12/11/19 13:41",
|
|
7
|
+
"dateModified": "12/11/19 14:28",
|
|
8
|
+
"categories": [
|
|
9
|
+
{
|
|
10
|
+
"id": 187,
|
|
11
|
+
"name": "Developer Tools",
|
|
12
|
+
"sortOrder": 28
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"filename": "Xcode 6.3.dmg",
|
|
18
|
+
"displayName": "Xcode 6.3",
|
|
19
|
+
"remotePath": "/Developer_Tools/Xcode_6.3/Xcode_6.3.dmg",
|
|
20
|
+
"fileSize": 2685818165,
|
|
21
|
+
"sortOrder": 0,
|
|
22
|
+
"dateCreated": "12/11/19 21:41",
|
|
23
|
+
"dateModified": "12/11/19 22:28",
|
|
24
|
+
"fileFormat": {
|
|
25
|
+
"extension": ".dmg",
|
|
26
|
+
"description": "Disk Image"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
46
30
|
}
|