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.
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
- def fetch(url, directory = nil, cookies = nil, output = nil, progress = true)
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
- progress = progress ? '--progress-bar' : '--silent'
25
- command = ['curl', *options, *retry_options, '--location', '--continue-at', '-', progress, '--output', output, url].map(&:to_s)
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
- 3.times do
32
- io = IO.popen(command)
33
- io.each { |line| puts line }
34
- io.close
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
- exit_code = $?.exitstatus
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
- return unless url || exist?(version)
62
- xcode = seedlist.find { |x| x.name == version } unless url
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(url || xcode.url, CACHE_DIR, url ? nil : spaceship.cookie, dmg_file, progress)
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
- list_versions.include?(version)
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
- `'#{archive_util}' #{dmg_path}`
90
- xcode_orig_path = dmg_path.dirname + 'Xcode.app'
91
- xcode_beta_path = dmg_path.dirname + 'Xcode-beta.app'
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
- unless verify_integrity(xcode_path)
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
- def install_version(version, switch = true, clean = true, install = true, progress = true, url = nil, show_release_notes = true)
146
- dmg_path = get_dmg(version, progress, url)
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(' ')[0]}", switch, clean)
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(&:version)
166
- xcodes_list.map { |x| installed.include?(x) ? "#{x} (installed)" : x }.join("\n")
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
- $stderr.puts 'The specified Apple developer account credentials are incorrect.'
203
- exit(1)
352
+ raise 'The specified Apple developer account credentials are incorrect.'
204
353
  rescue Spaceship::Client::NoUserCredentialsError
205
- $stderr.puts <<-HELP
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
- cache_path = Pathname.new(ENV['XCODE_INSTALL_CACHE_DIR']) + Pathname.new("xcode-#{version}.dmg")
235
- return cache_path if cache_path.exist?
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
- unless (`mdutil -s /` =~ /disabled/).nil?
257
- $stderr.puts 'Please enable Spotlight indexing for /Applications.'
258
- exit(1)
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 seedlist
318
- @xcodes = Marshal.load(File.read(LIST_FILE)) if LIST_FILE.exist? && xcodes.nil?
319
- xcodes || fetch_seedlist
320
- end
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
- def verify_integrity(path)
323
- puts `/usr/sbin/spctl --assess --verbose=4 --type execute #{path}`
324
- $?.exitstatus.zero?
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(source, CACHE_DIR, nil, nil, progress)
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(plist_entry(':DTXcode').to_i.to_s.split(//).join('.'))
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
- license_path = "#{@path}/Contents/Resources/English.lproj/License.rtf"
482
- license_id = IO.read(license_path).match(/\bEA\d{4}\b/)
483
- license_plist_path = '/Library/Preferences/com.apple.dt.Xcode.plist'
484
- `sudo rm -rf #{license_plist_path}`
485
- `sudo /usr/libexec/PlistBuddy -c "add :IDELastGMLicenseAgreedTo string #{license_id}" #{license_plist_path}`
486
- `sudo /usr/libexec/PlistBuddy -c "add :IDEXcodeVersionForAgreedToGMLicense string #{@version}" #{license_plist_path}`
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(@version) >= Gem::Version.new('9')
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 fetch_version
520
- output = `DEVELOPER_DIR='' "#{@path}/Contents/Developer/usr/bin/xcodebuild" -version`
521
- return '0.0' if output.nil? || output.empty? # ¯\_(ツ)_/¯
522
- output.split("\n").first.split(' ')[1]
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
@@ -18,7 +18,7 @@ module XcodeInstall
18
18
  curl = XcodeInstall::Curl.new
19
19
  result = nil
20
20
  XcodeInstall.silence_stderr do
21
- result = curl.fetch('http://0.0.0.0/test')
21
+ result = curl.fetch(url: 'http://0.0.0.0/test')
22
22
  end
23
23
  result.should == false
24
24
  end
@@ -1 +1,30 @@
1
- {"active":true,"availableInMemberSite":true,"categories":[{"active":null,"dateCreated":1052825460000,"dateModified":1079108316000,"enabled":true,"id":187,"name":"Developer Tools","sortOrder":28}],"dateAvailable":null,"dateCreated":1413472373000,"dateDisabled":null,"dateModified":1413472373000,"description":"This package enables UNIX-style development via Terminal by installing command line developer tools, as well as Mac OS X SDK frameworks and headers. Many useful tools are included, such as the Apple LLVM compiler, linker, and Make. If you use Xcode, these tools are also embedded within the Xcode IDE.","files":[{"active":true,"dateCreated":1413472373000,"dateModified":1413472373000,"fileSize":107698152,"format":{"active":null,"dateCreated":1066327569000,"dateModified":null,"description":"Disk Image","extension":".dmg","id":12},"id":33285,"name":"Command Line Tools for OSX 10.9 for Xcode 6.1","remotePath":"/Developer_Tools/command_line_tools_os_x_10.9_for_xcode__xcode_6.1/command_line_tools_for_osx_10.9_for_xcode_6.1.dmg","sortOrder":1,"state":"ENABLED"}],"id":21958,"name":"Command Line Tools (OS X 10.9) for Xcode - Xcode 6.1","prerelease":false,"releasedSeed":true,"state":"ENABLED"}
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
+ }
@@ -1,46 +1,30 @@
1
1
  {
2
- "active": true,
3
- "availableInMemberSite": true,
4
- "categories": [
5
- {
6
- "active": null,
7
- "dateCreated": 1052825460000,
8
- "dateModified": 1079108316000,
9
- "enabled": true,
10
- "id": 187,
11
- "name": "Developer Tools",
12
- "sortOrder": 28
13
- }
14
- ],
15
- "dateAvailable": null,
16
- "dateCreated": 1425695047000,
17
- "dateDisabled": null,
18
- "dateModified": 1425943753000,
19
- "description": "This is the complete Xcode developer toolset for Mac, iPhone, and iPad. It includes the Xcode IDE, iOS Simulator, and all required tools and frameworks for building OS X and iOS apps.",
20
- "files": [
21
- {
22
- "active": true,
23
- "dateCreated": 1425696402000,
24
- "dateModified": 1425929405000,
25
- "fileSize": 2778059898,
26
- "format": {
27
- "active": null,
28
- "dateCreated": 1066327569000,
29
- "dateModified": null,
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
  }