xcode-install 2.3.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 94d2ba944e260009634b80f9962d20322fd1f902
4
- data.tar.gz: eeefd86022b7f7b2649c2d336b74edcb86fae74e
3
+ metadata.gz: fd49f95272394358487c0eed4f39884120ea0717
4
+ data.tar.gz: f6dc4b73867986caaa06811a2c91e90f6741ec21
5
5
  SHA512:
6
- metadata.gz: 0c35741166c044efbf422f5957bcb8527b6935ed0c8177d02f890d6ef5ee2b96bf46bfe379931317f5aca5491a387b199b08fe4e099299b406d2944530e802e1
7
- data.tar.gz: 58e3d669156fe437fb0519be9f2c08e2a987bda6c7c859cfd2c4019d6bd97a01fac06fc18ed5bd73bfe7712ebbde62e70794f8b33d428574d010b0523230ed74
6
+ metadata.gz: 97e5d324c43f25f40f1b1682eac49544f6d695f44f2e27a942eb9782b635fb770bc271ef5ec81c93f4ace20523fa4cb2ad6dfb7dee03a446923d04feccae663e
7
+ data.tar.gz: 3ee8b79219737c56cb9d9a8050be6bc88aa15d74b94329907ec742e11bf95341268517ce8aff4218a14192638b465441896bfbd77412fc28bae3615800d2dd9b
data/.gitignore CHANGED
@@ -13,3 +13,4 @@
13
13
  *.a
14
14
  mkmf.log
15
15
  .DS_Store
16
+ test
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Xcode::Install
2
2
 
3
- [![Gem Version](http://img.shields.io/gem/v/xcode-install.svg?style=flat)](http://badge.fury.io/rb/xcode-install)
3
+ [![Gem Version](http://img.shields.io/gem/v/xcode-install.svg?style=flat)](http://badge.fury.io/rb/xcode-install) [![CircleCI](https://circleci.com/gh/KrauseFx/xcode-install.svg?style=svg)](https://circleci.com/gh/KrauseFx/xcode-install)
4
4
 
5
5
  Install and update your Xcodes automatically.
6
6
 
@@ -9,6 +9,8 @@ $ gem install xcode-install
9
9
  $ xcversion install 6.3
10
10
  ```
11
11
 
12
+ This tool uses the [Downloads for Apple Developer](https://developer.apple.com/download/more/) page.
13
+
12
14
  ## Installation
13
15
 
14
16
  ```
@@ -34,6 +36,8 @@ XCODE_INSTALL_USER
34
36
  XCODE_INSTALL_PASSWORD
35
37
  ```
36
38
 
39
+ ### List
40
+
37
41
  To list available versions:
38
42
 
39
43
  ```
@@ -41,17 +45,21 @@ $ xcversion list
41
45
  6.0.1
42
46
  6.1
43
47
  6.1.1
44
- 6.2
48
+ 6.2 (installed)
45
49
  6.3
46
50
  ```
47
51
 
48
- Installed versions will be omitted and by default, only the latest major version is listed.
52
+ Already installed versions are marked with `(installed)`.
53
+ (Use `$ xcversion installed` to only list installed Xcodes with their path).
54
+
55
+ To update the list of available versions, run:
49
56
 
50
- To list all available versions run
51
57
  ```
52
- $ xcversion list --all
58
+ $ xcversion update
53
59
  ```
54
60
 
61
+ ### Install
62
+
55
63
  To install a certain version, simply:
56
64
 
57
65
  ```
@@ -64,7 +72,10 @@ Xcode 8
64
72
  Build version 6D570
65
73
  ```
66
74
 
67
- This will download and install that version of Xcode. It will also be automatically selected.
75
+ This will download and install that version of Xcode. Then you can start it from `/Applications` as usual.
76
+ The new version will also be automatically selected for CLI commands (see below).
77
+
78
+ #### GMs and beta versions
68
79
 
69
80
  Note: GMs and beta versions usually have special names, e.g.
70
81
 
@@ -74,7 +85,24 @@ $ xcversion list
74
85
  7.1 beta
75
86
  ```
76
87
 
77
- they have to be installed using the full name, e.g. `xcversion install '7 GM seed'`.
88
+ They have to be installed using the full name, e.g. `xcversion install '7 GM seed'`.
89
+
90
+ ### Select
91
+
92
+ To see the currently selected version, run
93
+ ```
94
+ $ xcversion selected
95
+ ```
96
+
97
+ To select a version as active, run
98
+ ```
99
+ $ xcversion select 8
100
+ ```
101
+
102
+ To select a version as active and change the symlink at `/Applications/Xcode`, run
103
+ ```
104
+ $ xcversion select 8 --symlink
105
+ ```
78
106
 
79
107
  ### Command Line Tools
80
108
 
@@ -127,6 +155,8 @@ versions on unindexed volumes.
127
155
 
128
156
  ## Thanks
129
157
 
158
+ Thanks to [@neonichu](https://github.com/neonichu), the original (and best) author.
159
+
130
160
  [This][3] downloading script which has been used for some inspiration, also [this][4]
131
161
  for doing the installation. Additionally, many thanks to everyone who has contributed to this
132
162
  project, especially [@henrikhodne][6] and [@lacostej][7] for making XcodeInstall C extension free.
@@ -139,6 +169,18 @@ project, especially [@henrikhodne][6] and [@lacostej][7] for making XcodeInstall
139
169
  4. Push to the branch (`git push origin my-new-feature`)
140
170
  5. Create a new Pull Request
141
171
 
172
+ ### Running tests
173
+
174
+ ```
175
+ bundle exec rake spec
176
+ ```
177
+
178
+ ### Running code style linter
179
+
180
+ ```
181
+ bundle exec rubocop -a
182
+ ```
183
+
142
184
  ## License
143
185
 
144
186
  This project is licensed under the terms of the MIT license. See the [LICENSE](LICENSE) file.
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- load File.expand_path("../xcversion", __FILE__)
3
+ load File.expand_path('../xcversion', __FILE__)
@@ -0,0 +1,7 @@
1
+ machine:
2
+ xcode:
3
+ version: 9.0
4
+ test:
5
+ override:
6
+ - bundle exec rake spec
7
+ - bundle exec rake rubocop
@@ -6,42 +6,116 @@ 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 'fileutils'
9
12
 
10
13
  module XcodeInstall
11
14
  CACHE_DIR = Pathname.new("#{ENV['HOME']}/Library/Caches/XcodeInstall")
12
15
  class Curl
13
16
  COOKIES_PATH = Pathname.new('/tmp/curl-cookies.txt')
14
17
 
15
- def fetch(url, directory = nil, cookies = nil, output = nil, progress = true)
18
+ # @param url: The URL to download
19
+ # @param directory: The directory to download this file into
20
+ # @param cookies: Any cookies we should use for the download (used for auth with Apple)
21
+ # @param output: A PathName for where we want to store the file
22
+ # @param progress: parse and show the progress?
23
+ # @param progress_block: A block that's called whenever we have an updated progress %
24
+ # the parameter is a single number that's literally percent (e.g. 1, 50, 80 or 100)
25
+ # rubocop:disable Metrics/AbcSize
26
+ def fetch(url: nil,
27
+ directory: nil,
28
+ cookies: nil,
29
+ output: nil,
30
+ progress: nil,
31
+ progress_block: nil)
16
32
  options = cookies.nil? ? [] : ['--cookie', cookies, '--cookie-jar', COOKIES_PATH]
17
- # options << ' -vvv'
18
33
 
19
34
  uri = URI.parse(url)
20
35
  output ||= File.basename(uri.path)
21
36
  output = (Pathname.new(directory) + Pathname.new(output)) if directory
22
37
 
38
+ # Piping over all of stderr over to a temporary file
39
+ # the file content looks like this:
40
+ # 0 4766M 0 6835k 0 0 573k 0 2:21:58 0:00:11 2:21:47 902k
41
+ # This way we can parse the current %
42
+ # The header is
43
+ # % Total % Received % Xferd Average Speed Time Time Time Current
44
+ #
45
+ # Discussion for this on GH: https://github.com/KrauseFx/xcode-install/issues/276
46
+ # It was not easily possible to reimplement the same system using built-in methods
47
+ # especially when it comes to resuming downloads
48
+ # Piping over stderror to Ruby directly didn't work, due to the lack of flushing
49
+ # from curl. The only reasonable way to trigger this, is to pipe things directly into a
50
+ # local file, and parse that, and just poll that. We could get real time updates using
51
+ # the `tail` command or similar, however the download task is not time sensitive enough
52
+ # to make this worth the extra complexity, that's why we just poll and
53
+ # wait for the process to be finished
54
+ progress_log_file = File.join(CACHE_DIR, "progress.#{Time.now.to_i}.progress")
55
+ FileUtils.rm_f(progress_log_file)
56
+
23
57
  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)
58
+ command = [
59
+ 'curl',
60
+ *options,
61
+ *retry_options,
62
+ '--location',
63
+ '--continue-at',
64
+ '-',
65
+ '--output',
66
+ output,
67
+ url
68
+ ].map(&:to_s)
69
+
70
+ command_string = command.collect(&:shellescape).join(' ')
71
+ command_string += " 2> #{progress_log_file}" # to not run shellescape on the `2>`
26
72
 
27
73
  # Run the curl command in a loop, retry when curl exit status is 18
28
74
  # "Partial file. Only a part of the file was transferred."
29
75
  # https://curl.haxx.se/mail/archive-2008-07/0098.html
30
76
  # https://github.com/KrauseFx/xcode-install/issues/210
31
77
  3.times do
32
- io = IO.popen(command)
33
- io.each { |line| puts line }
34
- io.close
78
+ # Non-blocking call of Open3
79
+ # We're not using the block based syntax, as the bacon testing
80
+ # library doesn't seem to support writing tests for it
81
+ stdin, stdout, stderr, wait_thr = Open3.popen3(command_string)
82
+
83
+ # Poll the file and see if we're done yet
84
+ while wait_thr.alive?
85
+ sleep(0.5) # it's not critical for this to be real-time
86
+ next unless File.exist?(progress_log_file) # it might take longer for it to be created
87
+
88
+ progress_content = File.read(progress_log_file).split("\r").last
89
+
90
+ # Print out the progress for the CLI
91
+ if progress
92
+ print "\r#{progress_content}%"
93
+ $stdout.flush
94
+ end
95
+
96
+ # Call back the block for other processes that might be interested
97
+ matched = progress_content.match(/^\s*(\d+)/)
98
+ next unless matched.length == 2
99
+ percent = matched[1].to_i
100
+ progress_block.call(percent) if progress_block
101
+ end
35
102
 
36
- exit_code = $?.exitstatus
37
- return exit_code.zero? unless exit_code == 18
103
+ # as we're not making use of the block-based syntax
104
+ # we need to manually close those
105
+ stdin.close
106
+ stdout.close
107
+ stderr.close
108
+
109
+ return wait_thr.value.success? if wait_thr.value.success?
38
110
  end
39
111
  false
40
112
  ensure
41
113
  FileUtils.rm_f(COOKIES_PATH)
114
+ FileUtils.rm_f(progress_log_file)
42
115
  end
43
116
  end
44
117
 
118
+ # rubocop:disable Metrics/ClassLength
45
119
  class Installer
46
120
  attr_reader :xcodes
47
121
 
@@ -57,17 +131,47 @@ module XcodeInstall
57
131
  File.symlink?(SYMLINK_PATH) ? SYMLINK_PATH : nil
58
132
  end
59
133
 
60
- def download(version, progress, url = nil)
61
- return unless url || exist?(version)
62
- xcode = seedlist.find { |x| x.name == version } unless url
134
+ def download(version, progress, url = nil, progress_block = nil)
135
+ xcode = find_xcode_version(version) if url.nil?
136
+ return if url.nil? && xcode.nil?
137
+
63
138
  dmg_file = Pathname.new(File.basename(url || xcode.path))
64
139
 
65
- result = Curl.new.fetch(url || xcode.url, CACHE_DIR, url ? nil : spaceship.cookie, dmg_file, progress)
140
+ result = Curl.new.fetch(
141
+ url: url || xcode.url,
142
+ directory: CACHE_DIR,
143
+ cookies: url ? nil : spaceship.cookie,
144
+ output: dmg_file,
145
+ progress: progress,
146
+ progress_block: progress_block
147
+ )
66
148
  result ? CACHE_DIR + dmg_file : nil
67
149
  end
68
150
 
151
+ def find_xcode_version(version)
152
+ # By checking for the name and the version we have the best success rate
153
+ # Sometimes the user might pass
154
+ # "4.3 for Lion"
155
+ # or they might pass an actual Gem::Version
156
+ # Gem::Version.new("8.0.0")
157
+ # which should automatically match with "Xcode 8"
158
+
159
+ begin
160
+ parsed_version = Gem::Version.new(version)
161
+ rescue ArgumentError
162
+ nil
163
+ end
164
+
165
+ seedlist.each do |current_seed|
166
+ return current_seed if current_seed.name == version
167
+ return current_seed if parsed_version && current_seed.version == parsed_version
168
+ end
169
+ nil
170
+ end
171
+
69
172
  def exist?(version)
70
- list_versions.include?(version)
173
+ return true if find_xcode_version(version)
174
+ false
71
175
  end
72
176
 
73
177
  def installed?(version)
@@ -80,6 +184,31 @@ module XcodeInstall
80
184
  end
81
185
  end
82
186
 
187
+ # Returns an array of `XcodeInstall::Xcode`
188
+ # <XcodeInstall::Xcode:0x007fa1d451c390
189
+ # @date_modified=2015,
190
+ # @name="6.4",
191
+ # @path="/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg",
192
+ # @url=
193
+ # "https://developer.apple.com/devcenter/download.action?path=/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg",
194
+ # @version=Gem::Version.new("6.4")>,
195
+ #
196
+ # the resulting list is sorted with the most recent release as first element
197
+ def seedlist
198
+ @xcodes = Marshal.load(File.read(LIST_FILE)) if LIST_FILE.exist? && xcodes.nil?
199
+ all_xcodes = (xcodes || fetch_seedlist)
200
+
201
+ # We have to set the `installed` value here, as we might still use
202
+ # the cached list of available Xcode versions, but have a new Xcode
203
+ # installed in the mean-time
204
+ cached_installed_versions = installed_versions.map(&:bundle_version)
205
+ all_xcodes.each do |current_xcode|
206
+ current_xcode.installed = cached_installed_versions.include?(current_xcode.version)
207
+ end
208
+
209
+ all_xcodes.sort_by(&:version)
210
+ end
211
+
83
212
  def install_dmg(dmg_path, suffix = '', switch = true, clean = true)
84
213
  archive_util = '/System/Library/CoreServices/Applications/Archive Utility.app/Contents/MacOS/Archive Utility'
85
214
  prompt = "Please authenticate for Xcode installation.\nPassword: "
@@ -142,12 +271,13 @@ HELP
142
271
  FileUtils.rm_f(dmg_path) if clean
143
272
  end
144
273
 
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)
274
+ # rubocop:disable Metrics/ParameterLists
275
+ def install_version(version, switch = true, clean = true, install = true, progress = true, url = nil, show_release_notes = true, progress_block = nil)
276
+ dmg_path = get_dmg(version, progress, url, progress_block)
147
277
  fail Informative, "Failed to download Xcode #{version}." if dmg_path.nil?
148
278
 
149
279
  if install
150
- install_dmg(dmg_path, "-#{version.split(' ')[0]}", switch, clean)
280
+ install_dmg(dmg_path, "-#{version.to_s.split(' ')[0]}", switch, clean)
151
281
  else
152
282
  puts "Downloaded Xcode #{version} to '#{dmg_path}'"
153
283
  end
@@ -199,14 +329,12 @@ HELP
199
329
  begin
200
330
  Spaceship.login(ENV['XCODE_INSTALL_USER'], ENV['XCODE_INSTALL_PASSWORD'])
201
331
  rescue Spaceship::Client::InvalidUserCredentialsError
202
- $stderr.puts 'The specified Apple developer account credentials are incorrect.'
203
- exit(1)
332
+ raise 'The specified Apple developer account credentials are incorrect.'
204
333
  rescue Spaceship::Client::NoUserCredentialsError
205
- $stderr.puts <<-HELP
334
+ raise <<-HELP
206
335
  Please provide your Apple developer account credentials via the
207
336
  XCODE_INSTALL_USER and XCODE_INSTALL_PASSWORD environment variables.
208
337
  HELP
209
- exit(1)
210
338
  end
211
339
 
212
340
  if ENV.key?('XCODE_INSTALL_TEAM_ID')
@@ -225,7 +353,7 @@ HELP
225
353
  `sudo /usr/sbin/dseditgroup -o edit -t group -a staff _developer`
226
354
  end
227
355
 
228
- def get_dmg(version, progress = true, url = nil)
356
+ def get_dmg(version, progress = true, url = nil, progress_block = nil)
229
357
  if url
230
358
  path = Pathname.new(url)
231
359
  return path if path.exist?
@@ -235,7 +363,7 @@ HELP
235
363
  return cache_path if cache_path.exist?
236
364
  end
237
365
 
238
- download(version, progress, url)
366
+ download(version, progress, url, progress_block)
239
367
  end
240
368
 
241
369
  def fetch_seedlist
@@ -254,8 +382,7 @@ HELP
254
382
 
255
383
  def installed
256
384
  unless (`mdutil -s /` =~ /disabled/).nil?
257
- $stderr.puts 'Please enable Spotlight indexing for /Applications.'
258
- exit(1)
385
+ raise 'Please enable Spotlight indexing for /Applications.'
259
386
  end
260
387
 
261
388
  `mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'" 2>/dev/null`.split("\n")
@@ -314,11 +441,6 @@ HELP
314
441
  links
315
442
  end
316
443
 
317
- def seedlist
318
- @xcodes = Marshal.load(File.read(LIST_FILE)) if LIST_FILE.exist? && xcodes.nil?
319
- xcodes || fetch_seedlist
320
- end
321
-
322
444
  def verify_integrity(path)
323
445
  puts `/usr/sbin/spctl --assess --verbose=4 --type execute #{path}`
324
446
  $?.exitstatus.zero?
@@ -375,8 +497,13 @@ HELP
375
497
  end
376
498
  end
377
499
 
378
- def download(progress)
379
- result = Curl.new.fetch(source, CACHE_DIR, nil, nil, progress)
500
+ def download(progress, progress_block = nil)
501
+ result = Curl.new.fetch(
502
+ url: source,
503
+ directory: CACHE_DIR,
504
+ progress: progress,
505
+ progress_block: progress_block
506
+ )
380
507
  result ? dmg_path : nil
381
508
  end
382
509
 
@@ -478,12 +605,16 @@ HELP
478
605
  end
479
606
 
480
607
  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}`
608
+ if Gem::Version.new(version) < Gem::Version.new('7.3')
609
+ license_path = "#{@path}/Contents/Resources/English.lproj/License.rtf"
610
+ license_id = IO.read(license_path).match(/\bEA\d{4}\b/)
611
+ license_plist_path = '/Library/Preferences/com.apple.dt.Xcode.plist'
612
+ `sudo rm -rf #{license_plist_path}`
613
+ `sudo /usr/libexec/PlistBuddy -c "add :IDELastGMLicenseAgreedTo string #{license_id}" #{license_plist_path}`
614
+ `sudo /usr/libexec/PlistBuddy -c "add :IDEXcodeVersionForAgreedToGMLicense string #{@version}" #{license_plist_path}`
615
+ else
616
+ `sudo #{@path}/Contents/Developer/usr/bin/xcodebuild -license accept`
617
+ end
487
618
  end
488
619
 
489
620
  def available_simulators
@@ -510,27 +641,46 @@ HELP
510
641
  `touch #{cache_dir}com.apple.dt.Xcode.InstallCheckCache_#{osx_build_version}_#{tools_version}`
511
642
  end
512
643
 
513
- :private
514
-
515
- def plist_entry(keypath)
516
- `/usr/libexec/PlistBuddy -c "Print :#{keypath}" "#{path}/Contents/Info.plist"`.chomp
517
- end
518
-
644
+ # This method might take a few ms, this could be improved by implementing https://github.com/KrauseFx/xcode-install/issues/273
519
645
  def fetch_version
520
646
  output = `DEVELOPER_DIR='' "#{@path}/Contents/Developer/usr/bin/xcodebuild" -version`
521
647
  return '0.0' if output.nil? || output.empty? # ¯\_(ツ)_/¯
522
648
  output.split("\n").first.split(' ')[1]
523
649
  end
650
+
651
+ :private
652
+
653
+ def plist_entry(keypath)
654
+ `/usr/libexec/PlistBuddy -c "Print :#{keypath}" "#{path}/Contents/Info.plist"`.chomp
655
+ end
524
656
  end
525
657
 
658
+ # A version of Xcode we fetched from the Apple Developer Portal
659
+ # we can download & install.
660
+ #
661
+ # Sample object:
662
+ # <XcodeInstall::Xcode:0x007fa1d451c390
663
+ # @date_modified=2015,
664
+ # @name="6.4",
665
+ # @path="/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg",
666
+ # @url=
667
+ # "https://developer.apple.com/devcenter/download.action?path=/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg",
668
+ # @version=Gem::Version.new("6.4")>,
526
669
  class Xcode
527
670
  attr_reader :date_modified
671
+
672
+ # The name might include extra information like "for Lion" or "beta 2"
528
673
  attr_reader :name
529
674
  attr_reader :path
530
675
  attr_reader :url
531
676
  attr_reader :version
532
677
  attr_reader :release_notes_url
533
678
 
679
+ # Accessor since it's set by the `Installer`
680
+ attr_accessor :installed
681
+
682
+ alias installed? installed
683
+
534
684
  def initialize(json, url = nil, release_notes_url = nil)
535
685
  if url.nil?
536
686
  @date_modified = json['dateModified'].to_i
@@ -8,9 +8,14 @@ module XcodeInstall
8
8
  CLAide::Argument.new('VERSION', :true)
9
9
  ]
10
10
 
11
+ def self.options
12
+ [['--symlink', 'Update symlink in /Applications with selected Xcode']].concat(super)
13
+ end
14
+
11
15
  def initialize(argv)
12
16
  @installer = Installer.new
13
17
  @version = argv.shift_argument
18
+ @should_symlink = argv.flag?('symlink', false)
14
19
  super
15
20
  end
16
21
 
@@ -24,6 +29,7 @@ module XcodeInstall
24
29
  def run
25
30
  xcode = @installer.installed_versions.detect { |v| v.version == @version }
26
31
  `sudo xcode-select --switch #{xcode.path}`
32
+ @installer.symlink xcode.version if @should_symlink
27
33
  end
28
34
  end
29
35
  end
@@ -1,3 +1,3 @@
1
1
  module XcodeInstall
2
- VERSION = '2.3.1'.freeze
2
+ VERSION = '2.4.0'.freeze
3
3
  end
@@ -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
@@ -12,26 +12,26 @@ module XcodeInstall
12
12
  end
13
13
 
14
14
  it 'downloads and installs' do
15
- Installer.any_instance.expects(:download).with('6.3', true, nil).returns('/some/path')
15
+ Installer.any_instance.expects(:download).with('6.3', true, nil, nil).returns('/some/path')
16
16
  Installer.any_instance.expects(:install_dmg).with('/some/path', '-6.3', true, true)
17
17
  Command::Install.run(['6.3'])
18
18
  end
19
19
 
20
20
  it 'downloads and installs with custom HTTP URL' do
21
21
  url = 'http://yolo.com/xcode.dmg'
22
- Installer.any_instance.expects(:download).with('6.3', true, url).returns('/some/path')
22
+ Installer.any_instance.expects(:download).with('6.3', true, url, nil).returns('/some/path')
23
23
  Installer.any_instance.expects(:install_dmg).with('/some/path', '-6.3', true, true)
24
24
  Command::Install.run(['6.3', "--url=#{url}"])
25
25
  end
26
26
 
27
27
  it 'downloads and installs and does not switch if --no-switch given' do
28
- Installer.any_instance.expects(:download).with('6.3', true, nil).returns('/some/path')
28
+ Installer.any_instance.expects(:download).with('6.3', true, nil, nil).returns('/some/path')
29
29
  Installer.any_instance.expects(:install_dmg).with('/some/path', '-6.3', false, true)
30
30
  Command::Install.run(['6.3', '--no-switch'])
31
31
  end
32
32
 
33
33
  it 'downloads without progress if switch --no-progress is given' do
34
- Installer.any_instance.expects(:download).with('6.3', false, nil).returns('/some/path')
34
+ Installer.any_instance.expects(:download).with('6.3', false, nil, nil).returns('/some/path')
35
35
  Installer.any_instance.expects(:install_dmg).with('/some/path', '-6.3', true, true)
36
36
  Command::Install.run(['6.3', '--no-progress'])
37
37
  end
@@ -0,0 +1,100 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ module XcodeInstall
4
+ describe '#fetch' do
5
+ before do
6
+ client = mock
7
+ client.stubs(:cookie).returns('customCookie')
8
+ Spaceship::PortalClient.stubs(:login).returns(client)
9
+ end
10
+
11
+ it 'downloads the file and calls the `progress_block` with the percentage' do
12
+ installer = Installer.new
13
+
14
+ xcode = XcodeInstall::Xcode.new('name' => 'Xcode 9.3',
15
+ 'files' => [{
16
+ 'remotePath' => '/Developer_Tools/Xcode_9.3/Xcode_9.3.xip'
17
+ }])
18
+
19
+ installer.stubs(:fetch_seedlist).returns([xcode])
20
+
21
+ stdin = 'stdin'
22
+ stdout = 'stdout'
23
+ stderr = 'stderr'
24
+ wait_thr = 'wait_thr'
25
+
26
+ stdin.stubs(:close)
27
+ stdout.stubs(:close)
28
+ stderr.stubs(:close)
29
+
30
+ current_time = '123123'
31
+ Time.stubs(:now).returns(current_time)
32
+
33
+ xip_path = File.join(File.expand_path('~'), '/Library/Caches/XcodeInstall/Xcode_9.3.xip')
34
+ progress_log_file = File.join(File.expand_path('~'), "/Library/Caches/XcodeInstall/progress.#{current_time}.progress")
35
+
36
+ command = [
37
+ 'curl',
38
+ '--cookie customCookie',
39
+ '--cookie-jar /tmp/curl-cookies.txt',
40
+ '--retry 3',
41
+ '--location',
42
+ '--continue-at -',
43
+ "--output #{xip_path}",
44
+ 'https://developer.apple.com/devcenter/download.action\\?path\\=/Developer_Tools/Xcode_9.3/Xcode_9.3.xip',
45
+ "2> #{progress_log_file}"
46
+ ]
47
+ Open3.stubs(:popen3).with(command.join(' ')).returns([stdin, stdout, stderr, wait_thr])
48
+
49
+ wait_thr.stubs(:alive?).returns(true)
50
+
51
+ thr_value = 'thr_value'
52
+ wait_thr.stubs(:value).returns(thr_value)
53
+ thr_value.stubs(:success?).returns(true)
54
+
55
+ installer.stubs(:install_dmg).with(Pathname.new(xip_path), '-9.3', false, false)
56
+
57
+ Thread.new do
58
+ sleep(1)
59
+ File.write(progress_log_file, ' 0 4766M 0 6835k 0 0 573k 0 2:21:58 0:00:11 2:21:47 902k')
60
+ sleep(1)
61
+ File.write(progress_log_file, ' 5 4766M 0 6835k 0 0 573k 0 2:21:58 0:00:11 2:21:47 902k')
62
+ sleep(1)
63
+ File.write(progress_log_file, '50 4766M 0 6835k 0 0 573k 0 2:21:58 0:00:11 2:21:47 902k')
64
+ sleep(1)
65
+ File.write(progress_log_file, '100 4766M 0 6835k 0 0 573k 0 2:21:58 0:00:11 2:21:47 902k')
66
+ sleep(0.5)
67
+ wait_thr.stubs(:alive?).returns(false)
68
+ end
69
+
70
+ percentages = []
71
+ installer.install_version(
72
+ # version: the version to install
73
+ '9.3',
74
+ # `should_switch
75
+ false,
76
+ # `should_clean`
77
+ false, # false for now for faster debugging
78
+ # `should_install`
79
+ true,
80
+ # `progress`
81
+ false,
82
+ # `url` is nil, as we don't have a custom source
83
+ nil,
84
+ # `show_release_notes` is `false`, as this is a non-interactive machine
85
+ false,
86
+ # `progress_block` be updated on the download progress
87
+ proc do |percent|
88
+ percentages << percent
89
+ end
90
+ )
91
+
92
+ percentages.each do |current_percent|
93
+ # Verify all reported percentages are between 0 and 100
94
+ current_percent.should.be.close(50, 50)
95
+ end
96
+ # Verify we got a good amount of percentages reported
97
+ percentages.count.should.be.close(8, 4)
98
+ end
99
+ end
100
+ end
@@ -29,7 +29,7 @@ module XcodeInstall
29
29
  it 'can parse prereleases from 20150414' do
30
30
  prereleases = parse_prereleases('20150414')
31
31
 
32
- prereleases.should == [Xcode.new_prerelease('6.4', '/Developer_Tools/Xcode_6.4_Beta/Xcode_6.4_beta.dmg', '/Developer_Tools/Xcode_6.4_Beta/Xcode_6.4_beta_Release_Notes.pdf')]
32
+ prereleases.should == [Xcode.new_prerelease('6.4 beta', '/Developer_Tools/Xcode_6.4_Beta/Xcode_6.4_beta.dmg', '/Developer_Tools/Xcode_6.4_Beta/Xcode_6.4_beta_Release_Notes.pdf')]
33
33
  end
34
34
 
35
35
  it 'can parse prereleases from 20150427' do
@@ -21,7 +21,10 @@ Gem::Specification.new do |spec|
21
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
22
  spec.require_paths = ['lib']
23
23
 
24
+ # CLI parsing
24
25
  spec.add_dependency 'claide', '>= 0.9.1', '< 1.1.0'
26
+
27
+ # contains spaceship, which is used for auth and dev portal interactions
25
28
  spec.add_dependency 'fastlane', '>= 2.1.0', '< 3.0.0'
26
29
 
27
30
  spec.add_development_dependency 'bundler', '~> 1.7'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcode-install
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.1
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boris Bügling
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-04 00:00:00.000000000 Z
11
+ date: 2018-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: claide
@@ -91,13 +91,13 @@ files:
91
91
  - ".gitignore"
92
92
  - ".rubocop.yml"
93
93
  - ".rubocop_todo.yml"
94
- - ".travis.yml"
95
94
  - Gemfile
96
95
  - LICENSE
97
96
  - README.md
98
97
  - Rakefile
99
98
  - bin/xcversion
100
99
  - "bin/\U0001F389"
100
+ - circle.yml
101
101
  - lib/xcode/install.rb
102
102
  - lib/xcode/install/cleanup.rb
103
103
  - lib/xcode/install/cli.rb
@@ -133,6 +133,7 @@ files:
133
133
  - spec/fixtures/yolo.json
134
134
  - spec/install_spec.rb
135
135
  - spec/installed_spec.rb
136
+ - spec/installer_spec.rb
136
137
  - spec/json_spec.rb
137
138
  - spec/list_spec.rb
138
139
  - spec/prerelease_spec.rb
@@ -186,9 +187,9 @@ test_files:
186
187
  - spec/fixtures/yolo.json
187
188
  - spec/install_spec.rb
188
189
  - spec/installed_spec.rb
190
+ - spec/installer_spec.rb
189
191
  - spec/json_spec.rb
190
192
  - spec/list_spec.rb
191
193
  - spec/prerelease_spec.rb
192
194
  - spec/spec_helper.rb
193
195
  - spec/uninstall_spec.rb
194
- has_rdoc:
@@ -1,10 +0,0 @@
1
- language: ruby
2
- os: osx
3
- osx_image: xcode7.3
4
- rvm: 2.0.0-p598
5
- cache: bundler
6
- before_install:
7
- - gem install bundler
8
- script:
9
- - bundle exec rake spec
10
- - bundle exec rake rubocop