xcode-install 2.6.4 → 2.7.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
  SHA256:
3
- metadata.gz: 77e63eb1a9a586f24c71906f12ba305837dea1c9c8b85d2d6eff7e6316ceccc4
4
- data.tar.gz: 9de18b7a27cd8aa7b35c9a2bc79d179838c376cca2320e48dd31179fd08bcacc
3
+ metadata.gz: f5f95c1ec6494fb1482c6862a786fd3f149327096468dbfd01886d6a320ab31f
4
+ data.tar.gz: ef0e89795d7a5169a97adb29467025693644db1a8805e1ae395cd76066ae2569
5
5
  SHA512:
6
- metadata.gz: d39934561eb6d766229b547e723af2fcf819403c24bcb2df5ab07261115bfc76faf48a180b689c810c1404a061f1b994343808c91fdeeebb99647e20fe3d7638
7
- data.tar.gz: 67c420c58617fb77a13bae2d2173612e0b25257f8740df475b0aef74e3a174157f51bf5c95eac8f6aa96d9011c0e7b9cbc90cd5c72c63ce94dab4ac5ff093968
6
+ metadata.gz: 4fa023f63479190d92da8f7a54d9ac0a9556eab534e4e4ea4a784f6bbd10837678910570dd9665d409214d52e47432ebafa99aaae0a9022097bde4540d42390f
7
+ data.tar.gz: 73c28ae14d7081a62376ce4611cae869987b8e2681325770b20d8d5f3be153abc02c7c0e3b94828c703ab786dd5960a62c6f3b097b162b82be39cf197e031d99
@@ -0,0 +1,42 @@
1
+ name: "CI"
2
+ on: [push, pull_request]
3
+
4
+ jobs:
5
+ build:
6
+ strategy:
7
+ fail-fast: false
8
+ matrix:
9
+ ruby: ["2.5", "2.6", "2.7"]
10
+
11
+ runs-on: macos-latest
12
+ steps:
13
+ # Setup env
14
+ - uses: actions/checkout@v2
15
+ - uses: actions/setup-ruby@v1
16
+ with:
17
+ ruby-version: "${{ matrix.ruby }}"
18
+
19
+ # Show env
20
+ - name: Show macOS version
21
+ run: sw_vers
22
+ - name: Show env versions
23
+ run: |
24
+ ruby --version
25
+ bundler --version
26
+ echo $HOME
27
+
28
+ # Prepare
29
+ - name: Install bundler 2.2.14
30
+ run: gem install bundler -v "~> 2.2.14"
31
+ - name: Install ruby dependencies
32
+ run: |
33
+ bundle config --local clean 'true'
34
+ bundle config --local path '.vendor'
35
+ bundle config --local jobs 8
36
+ bundle config --local without 'system_tests'
37
+ bundle install
38
+
39
+ - name: Run test
40
+ run: bundle exec rake spec
41
+ - name: Run lint
42
+ run: bundle exec rake rubocop
data/.gitignore CHANGED
@@ -14,3 +14,4 @@
14
14
  mkmf.log
15
15
  .DS_Store
16
16
  test
17
+ .vendor
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) [![CircleCI](https://circleci.com/gh/xcpretty/xcode-install.svg?style=svg)](https://circleci.com/gh/xcpretty/xcode-install)
3
+ [![Gem Version](http://img.shields.io/gem/v/xcode-install.svg?style=flat)](http://badge.fury.io/rb/xcode-install) [![Build Status](https://github.com/xcpretty/xcode-install/actions/workflows/ci.yml/badge.svg)](https://github.com/xcpretty/xcode-install/actions)
4
4
 
5
5
  Install and update your Xcodes automatically.
6
6
 
@@ -87,6 +87,16 @@ $ xcversion list
87
87
 
88
88
  They have to be installed using the full name, e.g. `xcversion install '7 GM seed'`.
89
89
 
90
+ #### `.xcode-version`
91
+
92
+ We recommend the creation of a `.xcode-version` file to explicitly declare and store the Xcode version to be used by your CI environment as well as your team.
93
+
94
+ ```
95
+ 12.5
96
+ ```
97
+
98
+ Read [the proposal](/XCODE_VERSION.md) of `.xcode-version`.
99
+
90
100
  ### Select
91
101
 
92
102
  To see the currently selected version, run
@@ -151,7 +161,7 @@ to a dialog popping up. Feel free to dupe [the radar][5]. 📡
151
161
 
152
162
  XcodeInstall normally relies on the Spotlight index to locate installed versions of Xcode. If you use it while
153
163
  indexing is happening, it might show inaccurate results and it will not be able to see installed
154
- versions on unindexed volumes.
164
+ versions on unindexed volumes.
155
165
 
156
166
  To workaround the Spotlight limitation, XcodeInstall searches `/Applications` folder to locate Xcodes when Spotlight is disabled on the machine, or when Spotlight query for Xcode does not return any results. But it still won't work if your Xcodes are not located under `/Applications` folder.
157
167
 
@@ -165,7 +175,7 @@ project, especially [@henrikhodne][6] and [@lacostej][7] for making XcodeInstall
165
175
 
166
176
  ## Contributing
167
177
 
168
- 1. Fork it ( https://github.com/KrauseFx/xcode-install/fork )
178
+ 1. Fork it ( https://github.com/xcpretty/xcode-install/fork )
169
179
  2. Create your feature branch (`git checkout -b my-new-feature`)
170
180
  3. Commit your changes (`git commit -am 'Add some feature'`)
171
181
  4. Push to the branch (`git push origin my-new-feature`)
data/XCODE_VERSION.md ADDED
@@ -0,0 +1,49 @@
1
+ # `.xcode-version`
2
+
3
+ ## Introduction
4
+
5
+ This is a proposal for a new standard for the iOS community: a text-based file that defines the Xcode version to use to compile and package a given iOS project.
6
+
7
+ This will be used by this gem, however it's designed in a way that any tool in the future can pick it up, no matter if it's Ruby based, Swift, JavaScript, etc.
8
+
9
+ Similar to the [.ruby-version file](https://en.wikipedia.org/wiki/Ruby_Version_Manager), the `.xcode-version` file allows any CI system or IDE to automatically install and switch to the Xcode version needed for a given project to successfully compile your project.
10
+
11
+ ## Filename
12
+
13
+ The filename must always be `.xcode-version`.
14
+
15
+ ## File location
16
+
17
+ The file must be located in the same directory as your Xcode project/workspace, and you should add it to your versioning system (e.g. git).
18
+
19
+ ## File content
20
+
21
+ The file content must be a simple string in a text file. The file may or may not end with an empty new line, this gem is responsible for stripping out the trailing `\n` (if used).
22
+
23
+ ### Sample files
24
+
25
+ To define an official Xcode release
26
+
27
+ ```
28
+ 9.3
29
+ ```
30
+
31
+ ```
32
+ 7.2.1
33
+ ```
34
+
35
+ You can also use pre-releases
36
+
37
+ ```
38
+ 11.5 GM Seed
39
+ ```
40
+
41
+ ```
42
+ 12 beta 6
43
+ ```
44
+
45
+ Always following the same version naming listed by `xcversion list`.
46
+
47
+ **Note**: Be aware that pre-releases might be eventually taken down from Apple's servers, meaning that it won't allow you to have fully reproducible builds as you won't be able to download the Xcode release once it's gone.
48
+
49
+ It is recommended to only use non-beta releases in an `.xcode-version` file to have fully reproducible builds that you'll be able to run in a few years also.
data/lib/xcode/install.rb CHANGED
@@ -25,13 +25,14 @@ module XcodeInstall
25
25
  # @param progress: parse and show the progress?
26
26
  # @param progress_block: A block that's called whenever we have an updated progress %
27
27
  # the parameter is a single number that's literally percent (e.g. 1, 50, 80 or 100)
28
- # rubocop:disable Metrics/AbcSize
28
+ # @param retry_download_count: A count to retry the downloading Xcode dmg/xip
29
29
  def fetch(url: nil,
30
30
  directory: nil,
31
31
  cookies: nil,
32
32
  output: nil,
33
33
  progress: nil,
34
- progress_block: nil)
34
+ progress_block: nil,
35
+ retry_download_count: 3)
35
36
  options = cookies.nil? ? [] : ['--cookie', cookies, '--cookie-jar', COOKIES_PATH]
36
37
 
37
38
  uri = URI.parse(url)
@@ -78,38 +79,8 @@ module XcodeInstall
78
79
  # "Partial file. Only a part of the file was transferred."
79
80
  # https://curl.haxx.se/mail/archive-2008-07/0098.html
80
81
  # https://github.com/KrauseFx/xcode-install/issues/210
81
- 3.times do
82
- # Non-blocking call of Open3
83
- # We're not using the block based syntax, as the bacon testing
84
- # library doesn't seem to support writing tests for it
85
- stdin, stdout, stderr, wait_thr = Open3.popen3(command_string)
86
-
87
- # Poll the file and see if we're done yet
88
- while wait_thr.alive?
89
- sleep(0.5) # it's not critical for this to be real-time
90
- next unless File.exist?(progress_log_file) # it might take longer for it to be created
91
-
92
- progress_content = File.read(progress_log_file).split("\r").last
93
-
94
- # Print out the progress for the CLI
95
- if progress
96
- print "\r#{progress_content}%"
97
- $stdout.flush
98
- end
99
-
100
- # Call back the block for other processes that might be interested
101
- matched = progress_content.match(/^\s*(\d+)/)
102
- next unless matched && matched.length == 2
103
- percent = matched[1].to_i
104
- progress_block.call(percent) if progress_block
105
- end
106
-
107
- # as we're not making use of the block-based syntax
108
- # we need to manually close those
109
- stdin.close
110
- stdout.close
111
- stderr.close
112
-
82
+ retry_download_count.times do
83
+ wait_thr = poll_file(command_string: command_string, progress_log_file: progress_log_file, progress: progress, progress_block: progress_block)
113
84
  return wait_thr.value.success? if wait_thr.value.success?
114
85
  end
115
86
  false
@@ -117,6 +88,41 @@ module XcodeInstall
117
88
  FileUtils.rm_f(COOKIES_PATH)
118
89
  FileUtils.rm_f(progress_log_file)
119
90
  end
91
+
92
+ def poll_file(command_string:, progress_log_file:, progress: nil, progress_block: nil)
93
+ # Non-blocking call of Open3
94
+ # We're not using the block based syntax, as the bacon testing
95
+ # library doesn't seem to support writing tests for it
96
+ stdin, stdout, stderr, wait_thr = Open3.popen3(command_string)
97
+
98
+ # Poll the file and see if we're done yet
99
+ while wait_thr.alive?
100
+ sleep(0.5) # it's not critical for this to be real-time
101
+ next unless File.exist?(progress_log_file) # it might take longer for it to be created
102
+
103
+ progress_content = File.read(progress_log_file).split("\r").last || ''
104
+
105
+ # Print out the progress for the CLI
106
+ if progress
107
+ print "\r#{progress_content}%"
108
+ $stdout.flush
109
+ end
110
+
111
+ # Call back the block for other processes that might be interested
112
+ matched = progress_content.match(/^\s*(\d+)/)
113
+ next unless matched && matched.length == 2
114
+ percent = matched[1].to_i
115
+ progress_block.call(percent) if progress_block
116
+ end
117
+
118
+ # as we're not making use of the block-based syntax
119
+ # we need to manually close those
120
+ stdin.close
121
+ stdout.close
122
+ stderr.close
123
+
124
+ wait_thr
125
+ end
120
126
  end
121
127
 
122
128
  # rubocop:disable Metrics/ClassLength
@@ -135,7 +141,7 @@ module XcodeInstall
135
141
  File.symlink?(SYMLINK_PATH) ? SYMLINK_PATH : nil
136
142
  end
137
143
 
138
- def download(version, progress, url = nil, progress_block = nil)
144
+ def download(version, progress, url = nil, progress_block = nil, retry_download_count = 3)
139
145
  xcode = find_xcode_version(version) if url.nil?
140
146
  return if url.nil? && xcode.nil?
141
147
 
@@ -147,7 +153,8 @@ module XcodeInstall
147
153
  cookies: url ? nil : spaceship.cookie,
148
154
  output: dmg_file,
149
155
  progress: progress,
150
- progress_block: progress_block
156
+ progress_block: progress_block,
157
+ retry_download_count: retry_download_count
151
158
  )
152
159
  result ? CACHE_DIR + dmg_file : nil
153
160
  end
@@ -173,7 +180,7 @@ module XcodeInstall
173
180
  seedlist.each do |current_seed|
174
181
  return current_seed if parsed_version && current_seed.version == parsed_version
175
182
  end
176
-
183
+
177
184
  nil
178
185
  end
179
186
 
@@ -214,7 +221,7 @@ module XcodeInstall
214
221
  current_xcode.installed = cached_installed_versions.include?(current_xcode.version)
215
222
  end
216
223
 
217
- all_xcodes.sort_by(&:version)
224
+ all_xcodes.sort_by { |seed| [seed.version, -seed.date_modified] }.reverse
218
225
  end
219
226
 
220
227
  def install_dmg(dmg_path, suffix = '', switch = true, clean = true)
@@ -280,8 +287,8 @@ HELP
280
287
  end
281
288
 
282
289
  # rubocop:disable Metrics/ParameterLists
283
- def install_version(version, switch = true, clean = true, install = true, progress = true, url = nil, show_release_notes = true, progress_block = nil)
284
- dmg_path = get_dmg(version, progress, url, progress_block)
290
+ 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)
291
+ dmg_path = get_dmg(version, progress, url, progress_block, retry_download_count)
285
292
  fail Informative, "Failed to download Xcode #{version}." if dmg_path.nil?
286
293
 
287
294
  if install
@@ -300,17 +307,21 @@ HELP
300
307
  end
301
308
 
302
309
  def list_annotated(xcodes_list)
303
- installed = installed_versions.map(&:version)
310
+ installed = installed_versions.map(&:appname_version)
311
+
304
312
  xcodes_list.map do |x|
305
- xcode_version = x.split(' ').first # exclude "beta N", "for Lion".
306
- xcode_version << '.0' unless xcode_version.include?('.')
313
+ xcode_version = x.split(' ') # split version and "beta N", "for Lion"
314
+ xcode_version[0] << '.0' unless xcode_version[0].include?('.')
315
+
316
+ # to match InstalledXcode.appname_version format
317
+ version = Gem::Version.new(xcode_version.join('.'))
307
318
 
308
- installed.include?(xcode_version) ? "#{x} (installed)" : x
319
+ installed.include?(version) ? "#{x} (installed)" : x
309
320
  end.join("\n")
310
321
  end
311
322
 
312
323
  def list
313
- list_annotated(list_versions.sort_by(&:to_f))
324
+ list_annotated(list_versions.sort { |first, second| compare_versions(first, second) })
314
325
  end
315
326
 
316
327
  def rm_list_cache
@@ -366,7 +377,7 @@ HELP
366
377
  `sudo /usr/sbin/dseditgroup -o edit -t group -a staff _developer`
367
378
  end
368
379
 
369
- def get_dmg(version, progress = true, url = nil, progress_block = nil)
380
+ def get_dmg(version, progress = true, url = nil, progress_block = nil, retry_download_count = 3)
370
381
  if url
371
382
  path = Pathname.new(url)
372
383
  return path if path.exist?
@@ -377,7 +388,7 @@ HELP
377
388
  end
378
389
  end
379
390
 
380
- download(version, progress, url, progress_block)
391
+ download(version, progress, url, progress_block, retry_download_count)
381
392
  end
382
393
 
383
394
  def fetch_seedlist
@@ -457,6 +468,35 @@ HELP
457
468
  links
458
469
  end
459
470
 
471
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
472
+ def compare_versions(first, second)
473
+ # Sort by version number
474
+ numeric_comparation = first.to_f <=> second.to_f
475
+ return numeric_comparation if numeric_comparation != 0
476
+
477
+ # Return beta versions before others
478
+ is_first_beta = first.include?('beta')
479
+ is_second_beta = second.include?('beta')
480
+ return -1 if is_first_beta && !is_second_beta
481
+ return 1 if !is_first_beta && is_second_beta
482
+
483
+ # Return GM versions before others
484
+ is_first_gm = first.include?('GM')
485
+ is_second_gm = second.include?('GM')
486
+ return -1 if is_first_gm && !is_second_gm
487
+ return 1 if !is_first_gm && is_second_gm
488
+
489
+ # Return Release Candidate versions before others
490
+ is_first_rc = first.include?('RC') || first.include?('Release Candidate')
491
+ is_second_rc = second.include?('RC') || second.include?('Release Candidate')
492
+ return -1 if is_first_rc && !is_second_rc
493
+ return 1 if !is_first_rc && is_second_rc
494
+
495
+ # Sort alphabetically
496
+ first <=> second
497
+ end
498
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
499
+
460
500
  def hdiutil(*args)
461
501
  io = IO.popen(['hdiutil', *args])
462
502
  result = io.read
@@ -508,12 +548,13 @@ HELP
508
548
  end
509
549
  end
510
550
 
511
- def download(progress, progress_block = nil)
551
+ def download(progress, progress_block = nil, retry_download_count = 3)
512
552
  result = Curl.new.fetch(
513
553
  url: source,
514
554
  directory: CACHE_DIR,
515
555
  progress: progress,
516
- progress_block: progress_block
556
+ progress_block: progress_block,
557
+ retry_download_count: retry_download_count
517
558
  )
518
559
  result ? dmg_path : nil
519
560
  end
@@ -601,6 +642,17 @@ HELP
601
642
  @bundle_version ||= Gem::Version.new(bundle_version_string)
602
643
  end
603
644
 
645
+ def appname_version
646
+ appname = @path.basename('.app').to_s
647
+ version_string = appname.split('-').last
648
+ begin
649
+ Gem::Version.new(version_string)
650
+ rescue ArgumentError
651
+ puts 'Unable to determine Xcode version from path name, installed list may not correctly identify installed betas'
652
+ Gem::Version.new(nil)
653
+ end
654
+ end
655
+
604
656
  def uuid
605
657
  @uuid ||= plist_entry(':DVTPlugInCompatibilityUUID')
606
658
  end
@@ -702,7 +754,7 @@ HELP
702
754
  #
703
755
  # Sample object:
704
756
  # <XcodeInstall::Xcode:0x007fa1d451c390
705
- # @date_modified=2015,
757
+ # @date_modified=1573661580,
706
758
  # @name="6.4",
707
759
  # @path="/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg",
708
760
  # @url=
@@ -725,7 +777,7 @@ HELP
725
777
 
726
778
  def initialize(json, url = nil, release_notes_url = nil)
727
779
  if url.nil?
728
- @date_modified = json['dateModified'].to_i
780
+ @date_modified = DateTime.strptime(json['dateModified'], '%m/%d/%y %H:%M').strftime('%s').to_i
729
781
  @name = json['name'].gsub(/^Xcode /, '')
730
782
  @path = json['files'].first['remotePath']
731
783
  url_prefix = 'https://developer.apple.com/devcenter/download.action?path='
@@ -757,6 +809,7 @@ HELP
757
809
 
758
810
  def self.new_prerelease(version, url, release_notes_path)
759
811
  new('name' => version,
812
+ 'dateModified' => '01/01/70 00:00',
760
813
  'files' => [{ 'remotePath' => url.split('=').last }],
761
814
  'release_notes_path' => release_notes_path)
762
815
  end
@@ -17,13 +17,14 @@ module XcodeInstall
17
17
  ['--no-install', 'Only download DMG, but do not install it.'],
18
18
  ['--no-progress', 'Don’t show download progress.'],
19
19
  ['--no-clean', 'Don’t delete DMG after installation.'],
20
- ['--no-show-release-notes', 'Don’t open release notes in browser after installation.']].concat(super)
20
+ ['--no-show-release-notes', 'Don’t open release notes in browser after installation.'],
21
+ ['--retry-download-count', 'Count of retrying download when curl is failed.']].concat(super)
21
22
  end
22
23
 
23
24
  def initialize(argv)
24
25
  @installer = Installer.new
25
26
  @version = argv.shift_argument
26
- @version ||= File.read('.xcode-version') if File.exist?('.xcode-version')
27
+ @version ||= File.read('.xcode-version').strip if File.exist?('.xcode-version')
27
28
  @url = argv.option('url')
28
29
  @force = argv.flag?('force', false)
29
30
  @should_clean = argv.flag?('clean', true)
@@ -31,6 +32,7 @@ module XcodeInstall
31
32
  @should_switch = argv.flag?('switch', true)
32
33
  @progress = argv.flag?('progress', true)
33
34
  @show_release_notes = argv.flag?('show-release-notes', true)
35
+ @retry_download_count = argv.option('retry-download-count', '3')
34
36
  super
35
37
  end
36
38
 
@@ -44,11 +46,12 @@ module XcodeInstall
44
46
  end
45
47
  fail Informative, "Version #{@version} doesn't exist." unless @url || @installer.exist?(@version)
46
48
  fail Informative, "Invalid URL: `#{@url}`" unless !@url || @url =~ /\A#{URI.regexp}\z/
49
+ fail Informative, "Invalid Retry: `#{@retry_download_count} is not positive number.`" if (@retry_download_count =~ /\A[0-9]*\z/).nil?
47
50
  end
48
51
 
49
52
  def run
50
53
  @installer.install_version(@version, @should_switch, @should_clean, @should_install,
51
- @progress, @url, @show_release_notes)
54
+ @progress, @url, @show_release_notes, nil, @retry_download_count.to_i)
52
55
  end
53
56
  end
54
57
  end