├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── Gemfile ├── LICENSE ├── MIGRATION.md ├── README.md ├── Rakefile ├── XCODE_VERSION.md ├── bin ├── xcversion └── 🎉 ├── lib └── xcode │ ├── install.rb │ └── install │ ├── cleanup.rb │ ├── cli.rb │ ├── command.rb │ ├── install.rb │ ├── installed.rb │ ├── list.rb │ ├── select.rb │ ├── selected.rb │ ├── simulators.rb │ ├── uninstall.rb │ ├── update.rb │ └── version.rb ├── spec ├── cli_spec.rb ├── curl_spec.rb ├── fixtures │ ├── devcenter │ │ ├── xcode-20150414.html │ │ ├── xcode-20150427.html │ │ ├── xcode-20150508.html │ │ ├── xcode-20150601.html │ │ ├── xcode-20150608.html │ │ ├── xcode-20150624.html │ │ ├── xcode-20150909.html │ │ ├── xcode-20160601.html │ │ ├── xcode-20160705-alt.html │ │ ├── xcode-20160705.html │ │ ├── xcode-20160922.html │ │ └── xcode-20161024.html │ ├── hdiutil.plist │ ├── mail-verify.html │ ├── not_registered_as_developer.json │ ├── xcode.json │ ├── xcode_63.json │ └── yolo.json ├── install_spec.rb ├── installed_spec.rb ├── installer_spec.rb ├── json_spec.rb ├── list_spec.rb ├── prerelease_spec.rb ├── spec_helper.rb └── uninstall_spec.rb └── xcode-install.gemspec /.gitattributes: -------------------------------------------------------------------------------- 1 | spec/fixtures/* linguist-documentation 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 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", "3.0"] 10 | 11 | runs-on: macos-latest 12 | steps: 13 | # Setup env 14 | - uses: actions/checkout@v2 15 | - uses: ruby/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.20 30 | run: gem install bundler -v "~> 2.2.20" 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 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | .DS_Store 16 | test 17 | .vendor 18 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | Metrics/LineLength: 4 | Max: 215 5 | 6 | Style/AsciiComments: 7 | Enabled: false 8 | 9 | Style/Documentation: 10 | Enabled: false 11 | 12 | Style/FileName: 13 | Exclude: 14 | - bin/🎉 15 | - bin/xcode-install 16 | 17 | Style/SpecialGlobalVars: 18 | Enabled: false 19 | 20 | Lint/Void: 21 | Enabled: false 22 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2021-07-06 16:06:45 UTC using RuboCop version 1.12.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # Configuration parameters: Include. 11 | # Include: **/*.gemspec 12 | Gemspec/RequiredRubyVersion: 13 | Exclude: 14 | - 'xcode-install.gemspec' 15 | 16 | # Offense count: 3 17 | # Cop supports --auto-correct. 18 | Layout/ClosingHeredocIndentation: 19 | Exclude: 20 | - 'lib/xcode/install.rb' 21 | 22 | # Offense count: 12 23 | # Cop supports --auto-correct. 24 | Layout/EmptyLineAfterGuardClause: 25 | Exclude: 26 | - 'lib/xcode/install.rb' 27 | - 'lib/xcode/install/cleanup.rb' 28 | - 'lib/xcode/install/simulators.rb' 29 | - 'lib/xcode/install/uninstall.rb' 30 | 31 | # Offense count: 3 32 | # Cop supports --auto-correct. 33 | Layout/HeredocIndentation: 34 | Exclude: 35 | - 'lib/xcode/install.rb' 36 | 37 | # Offense count: 3 38 | # Cop supports --auto-correct. 39 | Lint/BooleanSymbol: 40 | Exclude: 41 | - 'lib/xcode/install/install.rb' 42 | - 'lib/xcode/install/select.rb' 43 | - 'lib/xcode/install/uninstall.rb' 44 | 45 | # Offense count: 6 46 | Lint/DuplicateMethods: 47 | Exclude: 48 | - 'lib/xcode/install.rb' 49 | 50 | # Offense count: 2 51 | # Configuration parameters: MaximumRangeSize. 52 | Lint/MissingCopEnableDirective: 53 | Exclude: 54 | - 'lib/xcode/install.rb' 55 | 56 | # Offense count: 1 57 | # Cop supports --auto-correct. 58 | Lint/UriRegexp: 59 | Exclude: 60 | - 'lib/xcode/install/install.rb' 61 | 62 | # Offense count: 12 63 | # Configuration parameters: IgnoredMethods, CountRepeatedAttributes. 64 | Metrics/AbcSize: 65 | Max: 45 66 | 67 | # Offense count: 5 68 | # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. 69 | # IgnoredMethods: refine 70 | Metrics/BlockLength: 71 | Max: 76 72 | 73 | # Offense count: 6 74 | # Configuration parameters: IgnoredMethods. 75 | Metrics/CyclomaticComplexity: 76 | Max: 10 77 | 78 | # Offense count: 17 79 | # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. 80 | Metrics/MethodLength: 81 | Max: 50 82 | 83 | # Offense count: 1 84 | # Configuration parameters: CountKeywordArgs, MaxOptionalParameters. 85 | Metrics/ParameterLists: 86 | Max: 7 87 | 88 | # Offense count: 2 89 | # Configuration parameters: IgnoredMethods. 90 | Metrics/PerceivedComplexity: 91 | Max: 12 92 | 93 | # Offense count: 1 94 | Security/MarshalLoad: 95 | Exclude: 96 | - 'lib/xcode/install.rb' 97 | 98 | # Offense count: 16 99 | # Cop supports --auto-correct. 100 | # Configuration parameters: EnforcedStyle. 101 | # SupportedStyles: separated, grouped 102 | Style/AccessorGrouping: 103 | Exclude: 104 | - 'lib/xcode/install.rb' 105 | 106 | # Offense count: 1 107 | Style/CombinableLoops: 108 | Exclude: 109 | - 'lib/xcode/install.rb' 110 | 111 | # Offense count: 1 112 | # Cop supports --auto-correct. 113 | Style/Encoding: 114 | Exclude: 115 | - 'xcode-install.gemspec' 116 | 117 | # Offense count: 14 118 | # Cop supports --auto-correct. 119 | Style/ExpandPathArguments: 120 | Exclude: 121 | - 'bin/xcversion' 122 | - 'bin/🎉' 123 | - 'spec/cli_spec.rb' 124 | - 'spec/curl_spec.rb' 125 | - 'spec/install_spec.rb' 126 | - 'spec/installed_spec.rb' 127 | - 'spec/installer_spec.rb' 128 | - 'spec/json_spec.rb' 129 | - 'spec/list_spec.rb' 130 | - 'spec/prerelease_spec.rb' 131 | - 'spec/spec_helper.rb' 132 | - 'spec/uninstall_spec.rb' 133 | - 'xcode-install.gemspec' 134 | 135 | # Offense count: 28 136 | # Cop supports --auto-correct. 137 | # Configuration parameters: EnforcedStyle. 138 | # SupportedStyles: always, always_true, never 139 | Style/FrozenStringLiteralComment: 140 | Enabled: false 141 | 142 | # Offense count: 2 143 | # Cop supports --auto-correct. 144 | Style/IfUnlessModifier: 145 | Exclude: 146 | - 'lib/xcode/install.rb' 147 | 148 | # Offense count: 8 149 | # Configuration parameters: AllowedMethods. 150 | # AllowedMethods: respond_to_missing? 151 | Style/OptionalBooleanParameter: 152 | Exclude: 153 | - 'lib/xcode/install.rb' 154 | 155 | # Offense count: 1 156 | # Cop supports --auto-correct. 157 | Style/RedundantBegin: 158 | Exclude: 159 | - 'lib/xcode/install.rb' 160 | 161 | # Offense count: 2 162 | # Cop supports --auto-correct. 163 | Style/RedundantRegexpEscape: 164 | Exclude: 165 | - 'lib/xcode/install.rb' 166 | 167 | # Offense count: 1 168 | # Cop supports --auto-correct. 169 | # Configuration parameters: AllowMultipleReturnValues. 170 | Style/RedundantReturn: 171 | Exclude: 172 | - 'lib/xcode/install.rb' 173 | 174 | # Offense count: 2 175 | # Cop supports --auto-correct. 176 | # Configuration parameters: EnforcedStyle. 177 | # SupportedStyles: implicit, explicit 178 | Style/RescueStandardError: 179 | Exclude: 180 | - 'lib/xcode/install.rb' 181 | 182 | # Offense count: 1 183 | # Cop supports --auto-correct. 184 | # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. 185 | # AllowedMethods: present?, blank?, presence, try, try! 186 | Style/SafeNavigation: 187 | Exclude: 188 | - 'lib/xcode/install.rb' 189 | 190 | # Offense count: 14 191 | # Cop supports --auto-correct. 192 | # Configuration parameters: EnforcedStyle. 193 | # SupportedStyles: only_raise, only_fail, semantic 194 | Style/SignalException: 195 | Exclude: 196 | - 'lib/xcode/install.rb' 197 | - 'lib/xcode/install/install.rb' 198 | - 'lib/xcode/install/select.rb' 199 | - 'lib/xcode/install/simulators.rb' 200 | - 'lib/xcode/install/uninstall.rb' 201 | 202 | # Offense count: 2 203 | # Cop supports --auto-correct. 204 | Style/StderrPuts: 205 | Exclude: 206 | - 'lib/xcode/install.rb' 207 | 208 | # Offense count: 4 209 | # Cop supports --auto-correct. 210 | Style/StringConcatenation: 211 | Exclude: 212 | - 'lib/xcode/install.rb' 213 | - 'spec/spec_helper.rb' 214 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'bacon' 7 | gem 'coveralls', require: false 8 | gem 'mocha', '~> 0.11.4' 9 | gem 'mocha-on-bacon' 10 | gem 'prettybacon' 11 | gem 'rubocop', '~> 1.18', require: false 12 | end 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Boris Bügling 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # This project is being sunset 2 | 3 | A brief history of `xcode-install` aka `xcversion`, as well as a guide on how to migrate from it, to a more modern tool. 4 | 5 | For brevity sake I'm going to refer to this project always as `xcode-install` in this document. 6 | 7 | ## Overview 8 | 9 | The more time goes by, the more we realized this project had already provided the community the value it needed, served its purpose, and become obsolete. We believe it was the time to officially sunset this project, and this document will guide you to use a more modern and well maintained tool. 10 | 11 | ## Some Context 12 | 13 | `xcode-install` (originally a @neonichu's project, which got transferred to @KrauseFx, which got transferred to the @xcpretty GitHub organization), had been around since April 2015, back when there were no other good options to manage multiple versions of Xcode. 14 | 15 | Fast forward to Feb 2019, [`xcodes`](https://github.com/RobotsAndPencils/xcodes) was born to provide a more user friendly experience. It based itself off of this project to figure out the complex Xcode downloading logic, but has since then been actively maintained and new features are incorporated into it on a regular basis. To name a few, that are not present in `xcode-install`: 16 | 17 | - GUI (via [`Xcodes.app`](https://github.com/RobotsAndPencils/XcodesApp)) 18 | - Support to `aria2` (which has been requested in https://github.com/xcpretty/xcode-install/issues/425 but we never got to implement it), which uses up to 16 connections to download Xcode 3-5x faster 19 | - Support to [`unxip`](https://github.com/saagarjha/unxip), providing unxipping up to 70% faster 20 | - New in Xcode 14: Sessionless downloads, just announced by [@xcodesapp](https://twitter.com/xcodesapp): https://twitter.com/xcodesapp/status/1570991082359627779?s=46&t=qVETxqxGI7ZZsFLLrledIg, available in https://github.com/RobotsAndPencils/XcodesApp/releases/tag/v1.8.0b16 21 | 22 | These features, plus the fact that this project wasn't getting the attention it needed to keep supporting newer versions of Xcode and bug fixes, made us believe it was time to sunset this project. 23 | 24 | # Migrating _fastlane_ actions that depend on the `xcode-install` gem, to use `xcodes` 25 | 26 | As of https://github.com/fastlane/fastlane/pull/20672, a new action was introduced to _fastlane_ called `xcodes`. You can find its full documentation here: https://docs.fastlane.tools/actions/xcodes 27 | 28 | Keep in mind that the `xcodes` action depends on [`xcodes`](https://github.com/RobotsAndPencils/xcodes) CLI, so make sure you have it installed in your environment. For the installation guide, see: https://github.com/RobotsAndPencils/xcodes#installation 29 | 30 | There are 3 actions that depend on `xcode-install` gem. Below you can find how to migrate each one of them: 31 | 32 | ## 1. `xcode_install` 33 | 34 | `xcode_install` used to receive an Xcode version and "install if needed", which is the new `xcodes` action's main purpose. 35 | 36 | Before: 37 | 38 | ```ruby 39 | xcode_install( 40 | version: '14', 41 | username: 'example@example.com', 42 | team_id: 'ABCD1234', 43 | download_retry_attempts: 5, 44 | ) 45 | ``` 46 | 47 | Now: 48 | 49 | The `team_id` and `download_retry_attempts` options are no longer needed (nor supported). 50 | 51 | ```ruby 52 | xcodes( 53 | version: '14', 54 | username: 'example@example.com', 55 | ) 56 | ``` 57 | 58 | ## 2. `xcversion` 59 | 60 | `xcversion` used to receive an Xcode version and select it for the current build steps, which in `xcodes` action that's the `select_for_current_build_only` option. 61 | 62 | Before: 63 | 64 | ```ruby 65 | xcversion(version: '14') 66 | ``` 67 | 68 | Now: 69 | 70 | ```ruby 71 | xcodes( 72 | version: '14', 73 | select_for_current_build_only: true, 74 | ) 75 | ``` 76 | 77 | ## 3. `ensure_xcode_version` 78 | 79 | This action wasn't migrated to use `xcodes` within _fastlane_ yet, mainly because of the somewhat complex logic around the non-strict version checking. This document as well as fastlane's `ensure_xcode_version` action documentation will be updated when the new `xcodes` action officially deprecates the `ensure_xcode_version` action. For now, if you don't use the `strict: false` option of `ensure_xcode_version`, you can migrate to `xcodes` action by passing `select_for_current_build_only: true`, which will raise an error if the given version can't be selected: 80 | 81 | Before: 82 | 83 | ```ruby 84 | ensure_xcode_version( 85 | version: '14', 86 | strict: false, 87 | ) 88 | ``` 89 | 90 | ```ruby 91 | ensure_xcode_version(version: '14') 92 | ``` 93 | 94 | Now: 95 | 96 | ```ruby 97 | xcodes( 98 | version: '14', 99 | select_for_current_build_only: true, 100 | ) 101 | ``` 102 | 103 | ## Managing Simulator Runtimes 104 | 105 | You can install them via, e.g.: 106 | 107 | ```sh 108 | xcodes runtimes install 'iOS 16.0' 109 | ``` 110 | 111 | More documentation about this will be created soon, directly in the `xcodes` repository. 112 | 113 | ## Advanced Usage 114 | 115 | If there are other use cases that you don't see covered so far, check out the full documentation here: https://docs.fastlane.tools/actions/xcodes 116 | 117 | All the lanes that supported `.xcode-version` still support it :tada: 118 | 119 | # Migrating `xcode-install` CLI to `xcodes` 120 | 121 | If you're using `xcode-install` as a CLI, the process to migrate to `xcodes` is more straightforward: simply visit https://github.com/RobotsAndPencils/xcodes and check their installation and usage guide. 122 | 123 | `xcode-install` CLI supported `.xcode-version` and so does `xcodes` :tada: 124 | 125 | ## Shout Outs & Mentions 126 | 127 | Huge shout out to @neonichu, @KrauseFx, @mrcljx, @jpsim, @timsutton, and many other contributors (which you can check here: https://github.com/xcpretty/xcode-install/graphs/contributors) for the work they put into this project! It advanced the state of the art in its field, and the community benefitted a lot from it! `xcodes` wouldn't be where it is today without your effort into this project 💟 128 | 129 | Thank you all, and see you on the other side! 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xcode::Install 2 | 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 | 5 | # This project is being sunset. See the migration guide here: [MIGRATION.md](/MIGRATION.md) 6 | 7 | Install and update your Xcodes automatically. 8 | 9 | ``` 10 | $ gem install xcode-install 11 | $ xcversion install 6.3 12 | ``` 13 | 14 | This tool uses the [Downloads for Apple Developer](https://developer.apple.com/download/more/) page. 15 | 16 | ## Installation 17 | 18 | ``` 19 | $ gem install xcode-install 20 | ``` 21 | 22 | Note: unfortunately, XcodeInstall has a transitive dependency on a gem with native extensions and this is not really fixable at this point in time. If you are installing this on a machine without a working compiler, please use these alternative instructions instead: 23 | 24 | ``` 25 | $ curl -sL -O https://github.com/neonichu/ruby-domain_name/releases/download/v0.5.99999999/domain_name-0.5.99999999.gem 26 | $ gem install domain_name-0.5.99999999.gem 27 | $ gem install --conservative xcode-install 28 | $ rm -f domain_name-0.5.99999999.gem 29 | ``` 30 | 31 | ## Usage 32 | 33 | XcodeInstall needs environment variables with your credentials to access the Apple Developer 34 | Center, they are stored using the [credentials_manager][1] of [fastlane][2]: 35 | 36 | ``` 37 | XCODE_INSTALL_USER 38 | XCODE_INSTALL_PASSWORD 39 | ``` 40 | 41 | ### List 42 | 43 | To list available versions: 44 | 45 | ``` 46 | $ xcversion list 47 | 6.0.1 48 | 6.1 49 | 6.1.1 50 | 6.2 (installed) 51 | 6.3 52 | ``` 53 | 54 | Already installed versions are marked with `(installed)`. 55 | (Use `$ xcversion installed` to only list installed Xcodes with their path). 56 | 57 | To update the list of available versions, run: 58 | 59 | ``` 60 | $ xcversion update 61 | ``` 62 | 63 | ### Install 64 | 65 | To install a certain version, simply: 66 | 67 | ``` 68 | $ xcversion install 8 69 | ########################################################### 82.1% 70 | ######################################################################## 100.0% 71 | Please authenticate for Xcode installation... 72 | 73 | Xcode 8 74 | Build version 6D570 75 | ``` 76 | 77 | This will download and install that version of Xcode. Then you can start it from `/Applications` as usual. 78 | The new version will also be automatically selected for CLI commands (see below). 79 | 80 | #### GMs and beta versions 81 | 82 | Note: GMs and beta versions usually have special names, e.g. 83 | 84 | ``` 85 | $ xcversion list 86 | 7 GM seed 87 | 7.1 beta 88 | ``` 89 | 90 | They have to be installed using the full name, e.g. `xcversion install '7 GM seed'`. 91 | 92 | #### `.xcode-version` 93 | 94 | 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. 95 | 96 | ``` 97 | 12.5 98 | ``` 99 | 100 | Read [the proposal](/XCODE_VERSION.md) of `.xcode-version`. 101 | 102 | ### Select 103 | 104 | To see the currently selected version, run 105 | ``` 106 | $ xcversion selected 107 | ``` 108 | 109 | To select a version as active, run 110 | ``` 111 | $ xcversion select 8 112 | ``` 113 | 114 | To select a version as active and change the symlink at `/Applications/Xcode`, run 115 | ``` 116 | $ xcversion select 8 --symlink 117 | ``` 118 | 119 | ### Command Line Tools 120 | 121 | XcodeInstall can also install Xcode's Command Line Tools by calling `xcversion install-cli-tools`. 122 | 123 | ### Simulators 124 | 125 | XcodeInstall can also manage your local simulators using the `simulators` command. 126 | 127 | ``` 128 | $ xcversion simulators 129 | Xcode 6.4 (/Applications/Xcode-6.4.app) 130 | iOS 7.1 Simulator (installed) 131 | iOS 8.1 Simulator (not installed) 132 | iOS 8.2 Simulator (not installed) 133 | iOS 8.3 Simulator (installed) 134 | Xcode 7.2.1 (/Applications/Xcode-7.2.1.app) 135 | iOS 8.1 Simulator (not installed) 136 | iOS 8.2 Simulator (not installed) 137 | iOS 8.3 Simulator (installed) 138 | iOS 8.4 Simulator (not installed) 139 | iOS 9.0 Simulator (not installed) 140 | iOS 9.1 Simulator (not installed) 141 | tvOS 9.0 Simulator (not installed) 142 | watchOS 2.0 Simulator (installed) 143 | ``` 144 | 145 | To install a simulator, use `--install` and the beginning of a simulator name: 146 | 147 | ``` 148 | $ xcversion simulators --install='iOS 8.4' 149 | ########################################################### 82.1% 150 | ######################################################################## 100.0% 151 | Please authenticate to install iOS 8.4 Simulator... 152 | 153 | Successfully installed iOS 8.4 Simulator 154 | ``` 155 | 156 | ## Limitations 157 | 158 | Unfortunately, the installation size of Xcodes downloaded will be bigger than when downloading via the Mac App Store, see [#10](/../../issues/10) and feel free to dupe the radar. 📡 159 | 160 | XcodeInstall automatically installs additional components so that it is immediately usable from the 161 | commandline. Unfortunately, Xcode will load third-party plugins even in that situation, which leads 162 | to a dialog popping up. Feel free to dupe [the radar][5]. 📡 163 | 164 | XcodeInstall normally relies on the Spotlight index to locate installed versions of Xcode. If you use it while 165 | indexing is happening, it might show inaccurate results and it will not be able to see installed 166 | versions on unindexed volumes. 167 | 168 | 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. 169 | 170 | ## Thanks 171 | 172 | Thanks to [@neonichu](https://github.com/neonichu), the original (and best) author. 173 | 174 | [This][3] downloading script which has been used for some inspiration, also [this][4] 175 | for doing the installation. Additionally, many thanks to everyone who has contributed to this 176 | project, especially [@henrikhodne][6] and [@lacostej][7] for making XcodeInstall C extension free. 177 | 178 | ## Contributing 179 | 180 | 1. Fork it ( https://github.com/xcpretty/xcode-install/fork ) 181 | 2. Create your feature branch (`git checkout -b my-new-feature`) 182 | 3. Commit your changes (`git commit -am 'Add some feature'`) 183 | 4. Push to the branch (`git push origin my-new-feature`) 184 | 5. Create a new Pull Request 185 | 186 | ### Running tests 187 | 188 | ``` 189 | bundle exec rake spec 190 | ``` 191 | 192 | ### Running code style linter 193 | 194 | ``` 195 | bundle exec rubocop -a 196 | ``` 197 | 198 | ## License 199 | 200 | This project is licensed under the terms of the MIT license. See the [LICENSE](LICENSE) file. 201 | 202 | > This project and all fastlane tools are in no way affiliated with Apple Inc or Google. This project is open source under the MIT license, which means you have full access to the source code and can modify it to fit your own needs. All fastlane tools run on your own computer or server, so your credentials or other sensitive information will never leave your own computer. You are responsible for how you use fastlane tools. 203 | 204 | [1]: https://github.com/fastlane/fastlane/tree/master/credentials_manager#using-environment-variables 205 | [2]: http://fastlane.tools 206 | [3]: http://atastypixel.com/blog/resuming-adc-downloads-cos-safari-sucks/ 207 | [4]: https://github.com/magneticbear/Jenkins_Bootstrap 208 | [5]: http://www.openradar.me/22001810 209 | [6]: https://github.com/henrikhodne 210 | [7]: https://github.com/lacostej 211 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rubocop/rake_task' 3 | 4 | def specs(dir) 5 | FileList["spec/#{dir}/*_spec.rb"].shuffle.join(' ') 6 | end 7 | 8 | desc 'Runs all the specs' 9 | task :spec do 10 | sh "bundle exec bacon #{specs('**')}" 11 | end 12 | 13 | desc 'Lints all the files' 14 | RuboCop::RakeTask.new(:rubocop) 15 | 16 | task default: %i[spec rubocop] 17 | -------------------------------------------------------------------------------- /XCODE_VERSION.md: -------------------------------------------------------------------------------- 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. 50 | -------------------------------------------------------------------------------- /bin/xcversion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | if $PROGRAM_NAME == __FILE__ 4 | ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__) 5 | require 'rubygems' 6 | require 'bundler/setup' 7 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 8 | end 9 | 10 | require 'xcode/install' 11 | 12 | XcodeInstall::Command.run(ARGV) 13 | -------------------------------------------------------------------------------- /bin/🎉: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | load File.expand_path('../xcversion', __FILE__) 4 | -------------------------------------------------------------------------------- /lib/xcode/install.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'pathname' 3 | require 'rexml/document' 4 | require 'spaceship' 5 | require 'json' 6 | require 'rubygems/version' 7 | require 'xcode/install/command' 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' 15 | 16 | module XcodeInstall 17 | CACHE_DIR = Pathname.new("#{ENV['HOME']}/Library/Caches/XcodeInstall") 18 | class Curl 19 | COOKIES_PATH = Pathname.new('/tmp/curl-cookies.txt') 20 | 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 | def fetch(url: nil, 30 | directory: nil, 31 | cookies: nil, 32 | output: nil, 33 | progress: nil, 34 | progress_block: nil, 35 | retry_download_count: 3) 36 | options = cookies.nil? ? [] : ['--cookie', cookies, '--cookie-jar', COOKIES_PATH] 37 | 38 | uri = URI.parse(url) 39 | output ||= File.basename(uri.path) 40 | output = (Pathname.new(directory) + Pathname.new(output)) if directory 41 | 42 | # Piping over all of stderr over to a temporary file 43 | # the file content looks like this: 44 | # 0 4766M 0 6835k 0 0 573k 0 2:21:58 0:00:11 2:21:47 902k 45 | # This way we can parse the current % 46 | # The header is 47 | # % Total % Received % Xferd Average Speed Time Time Time Current 48 | # 49 | # Discussion for this on GH: https://github.com/KrauseFx/xcode-install/issues/276 50 | # It was not easily possible to reimplement the same system using built-in methods 51 | # especially when it comes to resuming downloads 52 | # Piping over stderror to Ruby directly didn't work, due to the lack of flushing 53 | # from curl. The only reasonable way to trigger this, is to pipe things directly into a 54 | # local file, and parse that, and just poll that. We could get real time updates using 55 | # the `tail` command or similar, however the download task is not time sensitive enough 56 | # to make this worth the extra complexity, that's why we just poll and 57 | # wait for the process to be finished 58 | progress_log_file = File.join(CACHE_DIR, "progress.#{Time.now.to_i}.progress") 59 | FileUtils.rm_f(progress_log_file) 60 | 61 | retry_options = ['--retry', '3'] 62 | command = [ 63 | 'curl', 64 | '--disable', 65 | *options, 66 | *retry_options, 67 | '--location', 68 | '--continue-at', 69 | '-', 70 | '--output', 71 | output, 72 | url 73 | ].map(&:to_s) 74 | 75 | command_string = command.collect(&:shellescape).join(' ') 76 | command_string += " 2> #{progress_log_file}" # to not run shellescape on the `2>` 77 | 78 | # Run the curl command in a loop, retry when curl exit status is 18 79 | # "Partial file. Only a part of the file was transferred." 80 | # https://curl.haxx.se/mail/archive-2008-07/0098.html 81 | # https://github.com/KrauseFx/xcode-install/issues/210 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) 84 | return wait_thr.value.success? if wait_thr.value.success? 85 | end 86 | false 87 | ensure 88 | FileUtils.rm_f(COOKIES_PATH) 89 | FileUtils.rm_f(progress_log_file) 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 126 | end 127 | 128 | # rubocop:disable Metrics/ClassLength 129 | class Installer 130 | attr_reader :xcodes 131 | 132 | def initialize 133 | FileUtils.mkdir_p(CACHE_DIR) 134 | end 135 | 136 | def cache_dir 137 | CACHE_DIR 138 | end 139 | 140 | def current_symlink 141 | File.symlink?(SYMLINK_PATH) ? SYMLINK_PATH : nil 142 | end 143 | 144 | def download(version, progress, url = nil, progress_block = nil, retry_download_count = 3) 145 | xcode = find_xcode_version(version) if url.nil? 146 | return if url.nil? && xcode.nil? 147 | 148 | dmg_file = Pathname.new(File.basename(url || xcode.path)) 149 | 150 | result = Curl.new.fetch( 151 | url: url || xcode.url, 152 | directory: CACHE_DIR, 153 | cookies: url ? nil : spaceship.cookie, 154 | output: dmg_file, 155 | progress: progress, 156 | progress_block: progress_block, 157 | retry_download_count: retry_download_count 158 | ) 159 | result ? CACHE_DIR + dmg_file : nil 160 | end 161 | 162 | def find_xcode_version(version) 163 | # By checking for the name and the version we have the best success rate 164 | # Sometimes the user might pass 165 | # "4.3 for Lion" 166 | # or they might pass an actual Gem::Version 167 | # Gem::Version.new("8.0.0") 168 | # which should automatically match with "Xcode 8" 169 | 170 | begin 171 | parsed_version = Gem::Version.new(version) 172 | rescue ArgumentError 173 | nil 174 | end 175 | 176 | seedlist.each do |current_seed| 177 | return current_seed if current_seed.name == version 178 | end 179 | 180 | seedlist.each do |current_seed| 181 | return current_seed if parsed_version && current_seed.version == parsed_version 182 | end 183 | 184 | nil 185 | end 186 | 187 | def exist?(version) 188 | return true if find_xcode_version(version) 189 | false 190 | end 191 | 192 | def installed?(version) 193 | installed_versions.map(&:version).include?(version) 194 | end 195 | 196 | def installed_versions 197 | installed.map { |x| InstalledXcode.new(x) }.sort do |a, b| 198 | Gem::Version.new(a.version) <=> Gem::Version.new(b.version) 199 | end 200 | end 201 | 202 | # Returns an array of `XcodeInstall::Xcode` 203 | # , 210 | # 211 | # the resulting list is sorted with the most recent release as first element 212 | def seedlist 213 | @xcodes = Marshal.load(File.read(LIST_FILE)) if LIST_FILE.exist? && xcodes.nil? 214 | all_xcodes = (xcodes || fetch_seedlist) 215 | 216 | # We have to set the `installed` value here, as we might still use 217 | # the cached list of available Xcode versions, but have a new Xcode 218 | # installed in the mean-time 219 | cached_installed_versions = installed_versions.map(&:bundle_version) 220 | all_xcodes.each do |current_xcode| 221 | current_xcode.installed = cached_installed_versions.include?(current_xcode.version) 222 | end 223 | 224 | all_xcodes.sort_by { |seed| [seed.version, -seed.date_modified] }.reverse 225 | end 226 | 227 | def install_dmg(dmg_path, suffix = '', switch = true, clean = true) 228 | prompt = "Please authenticate for Xcode installation.\nPassword: " 229 | xcode_path = "/Applications/Xcode#{suffix}.app" 230 | 231 | if dmg_path.extname == '.xip' 232 | `xip -x #{dmg_path}` 233 | xcode_orig_path = File.join(Dir.pwd, 'Xcode.app') 234 | xcode_beta_path = File.join(Dir.pwd, 'Xcode-beta.app') 235 | if Pathname.new(xcode_orig_path).exist? 236 | `sudo -p "#{prompt}" mv "#{xcode_orig_path}" "#{xcode_path}"` 237 | elsif Pathname.new(xcode_beta_path).exist? 238 | `sudo -p "#{prompt}" mv "#{xcode_beta_path}" "#{xcode_path}"` 239 | else 240 | out = <<-HELP 241 | No `Xcode.app(or Xcode-beta.app)` found in XIP. Please remove #{dmg_path} if you 242 | suspect a corrupted download or run `xcversion update` to see if the version 243 | you tried to install has been pulled by Apple. If none of this is true, 244 | please open a new GH issue. 245 | HELP 246 | $stderr.puts out.tr("\n", ' ') 247 | return 248 | end 249 | else 250 | mount_dir = mount(dmg_path) 251 | source = Dir.glob(File.join(mount_dir, 'Xcode*.app')).first 252 | 253 | if source.nil? 254 | out = <<-HELP 255 | No `Xcode.app` found in DMG. Please remove #{dmg_path} if you suspect a corrupted 256 | download or run `xcversion update` to see if the version you tried to install 257 | has been pulled by Apple. If none of this is true, please open a new GH issue. 258 | HELP 259 | $stderr.puts out.tr("\n", ' ') 260 | return 261 | end 262 | 263 | `sudo -p "#{prompt}" ditto "#{source}" "#{xcode_path}"` 264 | `umount "/Volumes/Xcode"` 265 | end 266 | 267 | xcode = InstalledXcode.new(xcode_path) 268 | 269 | unless xcode.verify_integrity 270 | `sudo rm -rf #{xcode_path}` 271 | return 272 | end 273 | 274 | enable_developer_mode 275 | xcode.approve_license 276 | xcode.install_components 277 | 278 | if switch 279 | `sudo rm -f #{SYMLINK_PATH}` unless current_symlink.nil? 280 | `sudo ln -sf #{xcode_path} #{SYMLINK_PATH}` unless SYMLINK_PATH.exist? 281 | 282 | `sudo xcode-select --switch #{xcode_path}` 283 | puts `xcodebuild -version` 284 | end 285 | 286 | FileUtils.rm_f(dmg_path) if clean 287 | end 288 | 289 | # rubocop:disable Metrics/ParameterLists 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) 292 | fail Informative, "Failed to download Xcode #{version}." if dmg_path.nil? 293 | 294 | if install 295 | install_dmg(dmg_path, "-#{version.to_s.split(' ').join('.')}", switch, clean) 296 | else 297 | puts "Downloaded Xcode #{version} to '#{dmg_path}'" 298 | end 299 | 300 | open_release_notes_url(version) if show_release_notes && !url 301 | end 302 | 303 | def open_release_notes_url(version) 304 | return if version.nil? 305 | xcode = seedlist.find { |x| x.name == version } 306 | `open #{xcode.release_notes_url}` unless xcode.nil? || xcode.release_notes_url.nil? 307 | end 308 | 309 | def list_annotated(xcodes_list) 310 | installed = installed_versions.map(&:appname_version) 311 | 312 | xcodes_list.map do |x| 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('.')) 318 | 319 | installed.include?(version) ? "#{x} (installed)" : x 320 | end.join("\n") 321 | end 322 | 323 | def list 324 | list_annotated(list_versions.sort { |first, second| compare_versions(first, second) }) 325 | end 326 | 327 | def rm_list_cache 328 | FileUtils.rm_f(LIST_FILE) 329 | end 330 | 331 | def symlink(version) 332 | xcode = installed_versions.find { |x| x.version == version } 333 | `sudo rm -f #{SYMLINK_PATH}` unless current_symlink.nil? 334 | `sudo ln -sf #{xcode.path} #{SYMLINK_PATH}` unless xcode.nil? || SYMLINK_PATH.exist? 335 | end 336 | 337 | def symlinks_to 338 | File.absolute_path(File.readlink(current_symlink), SYMLINK_PATH.dirname) if current_symlink 339 | end 340 | 341 | def mount(dmg_path) 342 | plist = hdiutil('mount', '-plist', '-nobrowse', '-noverify', dmg_path.to_s) 343 | document = REXML::Document.new(plist) 344 | node = REXML::XPath.first(document, "//key[.='mount-point']/following-sibling::*[1]") 345 | fail Informative, 'Failed to mount image.' unless node 346 | node.text 347 | end 348 | 349 | private 350 | 351 | def spaceship 352 | @spaceship ||= begin 353 | begin 354 | Spaceship.login(ENV['XCODE_INSTALL_USER'], ENV['XCODE_INSTALL_PASSWORD']) 355 | rescue Spaceship::Client::InvalidUserCredentialsError 356 | raise 'The specified Apple developer account credentials are incorrect.' 357 | rescue Spaceship::Client::NoUserCredentialsError 358 | raise <<-HELP 359 | Please provide your Apple developer account credentials via the 360 | XCODE_INSTALL_USER and XCODE_INSTALL_PASSWORD environment variables. 361 | HELP 362 | end 363 | 364 | if ENV.key?('XCODE_INSTALL_TEAM_ID') 365 | Spaceship.client.team_id = ENV['XCODE_INSTALL_TEAM_ID'] 366 | end 367 | Spaceship.client 368 | end 369 | end 370 | 371 | LIST_FILE = CACHE_DIR + Pathname.new('xcodes.bin') 372 | MINIMUM_VERSION = Gem::Version.new('4.3') 373 | SYMLINK_PATH = Pathname.new('/Applications/Xcode.app') 374 | 375 | def enable_developer_mode 376 | `sudo /usr/sbin/DevToolsSecurity -enable` 377 | `sudo /usr/sbin/dseditgroup -o edit -t group -a staff _developer` 378 | end 379 | 380 | def get_dmg(version, progress = true, url = nil, progress_block = nil, retry_download_count = 3) 381 | if url 382 | path = Pathname.new(url) 383 | return path if path.exist? 384 | end 385 | if ENV.key?('XCODE_INSTALL_CACHE_DIR') 386 | Pathname.glob(ENV['XCODE_INSTALL_CACHE_DIR'] + '/*').each do |fpath| 387 | return fpath if /^xcode_#{version}\.dmg|xip$/ =~ fpath.basename.to_s 388 | end 389 | end 390 | 391 | download(version, progress, url, progress_block, retry_download_count) 392 | end 393 | 394 | def fetch_seedlist 395 | @xcodes = parse_seedlist(spaceship.send(:request, :post, 396 | '/services-account/QH65B2/downloadws/listDownloads.action').body) 397 | 398 | names = @xcodes.map(&:name) 399 | @xcodes += prereleases.reject { |pre| names.include?(pre.name) } 400 | 401 | File.open(LIST_FILE, 'wb') do |f| 402 | f << Marshal.dump(xcodes) 403 | end 404 | 405 | xcodes 406 | end 407 | 408 | def installed 409 | result = `mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'" 2>/dev/null`.split("\n") 410 | if result.empty? 411 | result = `find /Applications -maxdepth 1 -name '*.app' -type d -exec sh -c \ 412 | 'if [ "$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" \ 413 | "{}/Contents/Info.plist" 2>/dev/null)" == "com.apple.dt.Xcode" ]; then echo "{}"; fi' ';'`.split("\n") 414 | end 415 | result 416 | end 417 | 418 | def parse_seedlist(seedlist) 419 | fail Informative, seedlist['resultString'] unless seedlist['resultCode'].eql? 0 420 | 421 | seeds = Array(seedlist['downloads']).select do |t| 422 | /^Xcode [0-9]/.match(t['name']) 423 | end 424 | 425 | xcodes = seeds.map { |x| Xcode.new(x) }.reject { |x| x.version < MINIMUM_VERSION }.sort do |a, b| 426 | a.date_modified <=> b.date_modified 427 | end 428 | 429 | xcodes.select { |x| x.url.end_with?('.dmg') || x.url.end_with?('.xip') } 430 | end 431 | 432 | def list_versions 433 | seedlist.map(&:name) 434 | end 435 | 436 | def prereleases 437 | body = spaceship.send(:request, :get, '/download/').body 438 | 439 | links = body.scan(%r{(.*)}) 440 | links = links.map do |link| 441 | parent = link[0].scan(%r{path=(/.*/.*/)}).first.first 442 | match = body.scan(/#{Regexp.quote(parent)}(.+?.pdf)/).first 443 | if match 444 | link + [parent + match.first] 445 | else 446 | link + [nil] 447 | end 448 | end 449 | links = links.map { |pre| Xcode.new_prerelease(pre[1].strip.tr('_', ' '), pre[0], pre[4]) } 450 | 451 | if links.count.zero? 452 | rg = %r{platform-title.*Xcode.* beta.*<\/p>} 453 | scan = body.scan(rg) 454 | 455 | if scan.count.zero? 456 | rg = %r{Xcode.* GM.*<\/p>} 457 | scan = body.scan(rg) 458 | end 459 | 460 | return [] if scan.empty? 461 | 462 | version = scan.first.gsub(/<.*?>/, '').gsub(/.*Xcode /, '') 463 | link = body.scan(%r{ 47 | 48 |
  • 49 | 50 | Apple Developer 51 | 52 |
  • 53 | 54 |
    55 |
    56 | 83 |
    84 |
    85 | 86 | 87 | 88 |
    89 | 90 | 94 | 95 | 96 | 102 | 103 |
    104 | 105 |
    106 | 107 |
    108 | 118 |
    119 | 120 |
    121 |
    122 |
    123 |

    Download Xcode for Free

    124 |
    125 |
    126 |
    127 | 128 |
    129 |
    130 |
    131 |
    132 | 133 |
    134 |
    135 |

    Xcode 7 betaPre-release

    136 |

    This release include the Xcode IDE, Swift 2 compiler, Instruments, Simulator, and latest SDKs for OS X, iOS, and watchOS. 137 |

    138 | 142 |
    143 |
    144 |

    Released: Jun 8, 2015
    145 | Build: 7A120f
    146 | SDK: iOS 9 beta, OS X v10.11 beta
    147 |

    148 |
    149 |
    150 |
    151 |
    152 |
    153 |
    154 |
    155 | 156 |
    157 |
    158 |

    Xcode 6.3.1

    159 |

    This release includes the Xcode IDE, LLVM compiler, Instruments, iOS Simulator, the latest OS X and iOS SDKs, WatchKit and the Swift 1.2 programming language. 160 |

    161 |

    View in the Mac App Store

    162 |
    163 |
    164 |

    165 | Updated: Apr 21, 2015
    166 | Build: 6D1002
    167 | SDK: iOS 8.3, OS X v10.10 168 |

    169 |
    170 |
    171 |
    172 | 173 |
    174 |
    175 |
    176 |
    177 | 178 |
    179 |
    180 |

    Xcode 6.4 beta 3Pre-release

    181 |

    This is a pre-release version of the complete Xcode developer toolset for Mac, iPhone, iPad, and Apple Watch. This release requires OS X Yosemite. 182 |

    183 | 187 |
    188 |
    189 |

    Released: May 11, 2015
    190 | Build: 6E23
    191 | SDK: iOS 8.4 beta 3, OS X v10.10
    192 |

    193 |
    194 |
    195 |
    196 |
    197 | 198 |
    199 |
    200 |
    201 |

    Related Downloads and Resources

    202 |
    203 |
    204 |
    205 |
    206 | 207 |

    Additional Tools

    208 |

    Sign in with your Apple ID to download previous versions of Xcode or additional tools from the developer downloads site.

    209 |
    210 |
    211 | 212 |

    Videos

    213 |

    Watch and learn from Apple engineers as they explain how to bring the best of Apple technologies to your apps.

    214 |
    215 |
    216 | 217 |

    Guides and Tutorials

    218 |

    Find a comprehensive set of programming guides, reference, and sample code for developing with the latest tools and technologies on iOS and OS X.

    219 |
    220 |
    221 | 222 |

    Forums

    223 |

    Post questions and share thoughts about using Xcode with fellow developers.

    224 |
    225 |
    226 |
    227 | 228 |
    229 | 230 | 313 | 314 | 315 | 316 | -------------------------------------------------------------------------------- /spec/fixtures/devcenter/xcode-20150624.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Xcode - Downloads - Apple Developer 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 91 | 92 |
    93 | 94 | 98 | 99 | 100 | 106 | 107 |
    108 | 109 |
    110 | 111 |
    112 | 122 |
    123 | 124 |
    125 |
    126 |
    127 |

    Download Xcode for Free

    128 |
    129 |
    130 |
    131 | 132 |
    133 |
    134 |
    135 |
    136 | 137 |
    138 |
    139 |

    Xcode 7 beta 2Pre-release

    140 |

    This release includes the Xcode IDE, Swift 2 compiler, Instruments, Simulator, and latest SDKs for OS X, iOS, and watchOS. 141 |

    142 | 146 |
    147 |
    148 |

    Released: Jun 23, 2015
    149 | Build: 7A121l
    150 | SDK: iOS 9 beta 2, OS X v10.11 beta
    151 |

    152 |
    153 |
    154 |
    155 |
    156 |
    157 |
    158 |
    159 | 160 |
    161 |
    162 |

    Xcode 6.3.2

    163 |

    This release includes the Xcode IDE, LLVM compiler, Instruments, iOS Simulator, the latest OS X and iOS SDKs, WatchKit and the Swift 1.2 programming language. 164 |

    165 |

    View in the Mac App Store

    166 |
    167 |
    168 |

    169 | Updated: May 18, 2015
    170 | Build: 6D2105
    171 | SDK: iOS 8.3, OS X v10.10 172 |

    173 |
    174 |
    175 |
    176 | 177 |
    178 |
    179 |
    180 |
    181 | 182 |
    183 |
    184 |

    Xcode 6.4 beta 4Pre-release

    185 |

    This is a pre-release version of the complete Xcode developer toolset for Mac, iPhone, iPad, and Apple Watch. This release requires OS X Yosemite. 186 |

    187 | 191 |
    192 |
    193 |

    Released: Jun 9, 2015
    194 | Build: 6E31c
    195 | SDK: iOS 8.4 beta 4, OS X v10.10
    196 |

    197 |
    198 |
    199 |
    200 |
    201 | 202 |
    203 |
    204 |
    205 |

    Related Downloads and Resources

    206 |
    207 |
    208 |
    209 |
    210 | 211 |

    Additional Tools

    212 |

    Sign in with your Apple ID to download previous versions of Xcode or additional tools from the developer downloads site.

    213 |
    214 |
    215 | 216 |

    Videos

    217 |

    Watch and learn from Apple engineers as they explain how to bring the best of Apple technologies to your apps.

    218 |
    219 |
    220 | 221 |

    Guides and Tutorials

    222 |

    Find a comprehensive set of programming guides, reference, and sample code for developing with the latest tools and technologies on iOS and OS X.

    223 |
    224 |
    225 | 226 |

    Forums

    227 |

    Post questions and share thoughts about using Xcode with fellow developers.

    228 |
    229 |
    230 |
    231 | 232 |
    233 | 234 | 316 | 317 | 318 | 319 | -------------------------------------------------------------------------------- /spec/fixtures/devcenter/xcode-20150909.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Xcode - Downloads - Apple Developer 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 80 | 81 |
    82 | 83 | 87 | 88 | 89 | 95 | 96 |
    97 | 98 |
    99 | 100 |
    101 | 111 |
    112 | 113 |
    114 |
    115 |
    116 |

    Download Xcode for Free

    117 |
    118 |
    119 |
    120 | 121 |
    122 |
    123 |
    124 |
    125 | 126 |
    127 |
    128 |

    Xcode 7.1 betaPre-release

    129 |

    This is the complete Xcode developer toolset for building apps that run on Apple TV, Apple Watch, iPhone, iPad, and Mac. It includes the Xcode IDE, simulators, and all the required tools and frameworks to build apps for iOS, watchOS, tvOS, and OS X. 130 |

    131 | 135 |
    136 |
    137 |

    Build

    138 |

    7B60

    139 |

    Posted Date

    140 |

    Sep 9, 2015

    141 |

    SDK

    142 |

    iOS 9.1 beta

    143 |

    OS X v10.11

    144 |

    tvOS beta

    145 |
    146 |
    147 |
    148 |
    149 | 150 |
    151 |
    152 |

    Xcode 7 GM seedPre-release

    153 |

    This release includes the Xcode IDE, Swift 2 compiler, Instruments, Simulator, and latest SDKs for OS X, iOS, and watchOS. 154 |

    155 | 158 |
    159 |
    160 |

    Build

    161 |

    7A216

    162 |

    Posted Date

    163 |

    Sep 9, 2015

    164 |

    SDK

    165 |

    iOS 9

    166 |

    OS X v10.11

    167 |
    168 |
    169 |
    170 |
    171 |
    172 |
    173 |
    174 | 175 |
    176 |
    177 |

    Xcode 6.4

    178 |

    This release includes the Xcode IDE, LLVM compiler, Instruments, iOS Simulator, the latest OS X and iOS SDKs, WatchKit and the Swift 1.2 programming language. 179 |

    180 |

    View in the Mac App Store

    181 |
    182 |
    183 |

    Build

    184 |

    6E35b

    185 |

    Posted Date

    186 |

    Jun 30, 2015

    187 |

    SDK

    188 |

    iOS 8.4

    189 |

    OS X v10.10

    190 |
    191 |
    192 |
    193 | 194 |
    195 |
    196 |
    197 |

    Related Downloads and Resources

    198 |
    199 |
    200 |
    201 |
    202 | 203 |

    Additional Tools

    204 |

    Sign in with your Apple ID to download previous versions of Xcode or additional tools from the developer downloads site.

    205 |
    206 |
    207 | 208 |

    Videos

    209 |

    Watch and learn from Apple engineers as they explain how to bring the best of Apple technologies to your apps.

    210 |
    211 |
    212 | 213 |

    Guides and Tutorials

    214 |

    Find a comprehensive set of programming guides, reference, and sample code for developing with the latest tools and technologies on iOS and OS X.

    215 |
    216 |
    217 | 218 |

    Forums

    219 |

    Post questions and share thoughts about using Xcode with fellow developers.

    220 |
    221 |
    222 |
    223 | 224 |
    225 | 226 | 307 | 308 | 309 | 310 | -------------------------------------------------------------------------------- /spec/fixtures/devcenter/xcode-20160705-alt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | Download - Apple Developer 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 99 |
    100 |
    101 | 102 | 103 |
    104 | 105 | 109 | 110 | 111 | 117 | 118 |
    119 | 120 |
    121 | 122 |
    123 |
    124 |
    125 | 132 |

    Downloads

    133 |

    Get the latest beta releases of Xcode, iOS, macOS, watchOS, tvOS, and more.

    134 |
    135 |
    136 |
    137 | 138 | 139 |
    140 |
    141 |
    142 | 143 |
    144 |
    145 |

    Xcode 8 beta 2

    146 |

    147 |

    148 | 151 | 154 |
    155 |
    156 |

    Build

    157 |

    8S162m

    158 |

    Posted Date

    159 |

    Jul 5, 2016

    160 |

    SDKs

    161 |

    iOS 10

    162 |

    macOS 10.12

    163 |

    watchOS 3

    164 |

    tvOS 10

    165 |
    166 |
    167 | 168 |
    169 |
    170 | 171 |
    172 |
    173 |

    Xcode 7.3.1

    174 |

    This release includes the Xcode IDE, Swift 2 compiler, Instruments, Simulator, and latest SDKs for OS X, iOS, tvOS and watchOS.

    175 |

    View in the Mac App Store

    176 | 179 |
    180 |
    181 |

    Build

    182 |

    7D1014

    183 |

    Posted Date

    184 |

    May 3, 2016

    185 |

    SDK

    186 |

    iOS 9.3

    187 |

    OS X 10.11.4

    188 |

    watchOS 2.2

    189 |

    tvOS 9.2

    190 |
    191 |
    192 |
    193 | 194 |
    195 |
    196 |
    197 |
    198 |

    Your Apple ID must be associated with a paid Apple Developer Program or Apple Developer Enterprise Program to access certain software downloads. You can download Xcode below or sign in with a different account.

    199 | 200 |

    Are you signed in with the wrong account? Sign out and sign back in.

    201 |
    202 |
    203 |
    204 |
    205 | 206 | 207 | 208 | 209 | 210 |
    211 | 212 | 228 | 229 | 230 | 315 | 316 | 317 | 318 | -------------------------------------------------------------------------------- /spec/fixtures/hdiutil.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | system-entities 4 | 5 | 6 | content-hint 7 | GUID_partition_scheme 8 | dev-entry 9 | /dev/disk2 10 | potentially-mountable 11 | 12 | unmapped-content-hint 13 | GUID_partition_scheme 14 | 15 | 16 | content-hint 17 | Apple_HFS 18 | dev-entry 19 | /dev/disk2s1 20 | mount-point 21 | /Volumes/XcodeME 22 | potentially-mountable 23 | 24 | unmapped-content-hint 25 | 48465300-0000-11AA-AA11-00306543ECAC 26 | volume-kind 27 | hfs 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /spec/fixtures/mail-verify.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | Verify Your Email Address - Apple Developer 28 | 29 | 30 | 38 | 39 | 40 | 92 | 93 |
    94 | 95 | 99 | 100 | 101 | 107 | 108 |
    109 | 116 | 117 |
    118 |
    119 |
    120 | 121 |

    Verify Your Email Address

    122 |

    Your Apple ID cannot be used until your email address has been verified.
    To verify your email address, please visit 123 | My Apple ID. 124 |

    125 | 126 |

    Verify Now

    127 | 128 |
    129 |
    130 |
    131 | 132 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /spec/fixtures/not_registered_as_developer.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseId": "1030f934-1897-4308-a0e4-fb44a4355ae9", 3 | "resultCode": 2100, 4 | "resultString": "You are not registered as an Apple Developer. Please visit Apple Developer Registration. https://developer.apple.com/register/", 5 | "userString": "You are not registered as an Apple Developer. Please visit Apple Developer Registration. https://developer.apple.com/register/", 6 | "creationTimestamp": "2017-07-06T12:33:03Z", 7 | "protocolVersion": "QH65B2", 8 | "userLocale": "en_US", 9 | "requestUrl": "https://developer.apple.com/services-account/QH65B2/downloadws/listDownloads.action", 10 | "httpCode": 200 11 | } 12 | -------------------------------------------------------------------------------- /spec/fixtures/xcode.json: -------------------------------------------------------------------------------- 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 | } -------------------------------------------------------------------------------- /spec/fixtures/xcode_63.json: -------------------------------------------------------------------------------- 1 | { 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 | ] 30 | } -------------------------------------------------------------------------------- /spec/install_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../spec_helper', __FILE__) 2 | 3 | module XcodeInstall 4 | describe Command::Install do 5 | describe 'when invoked' do 6 | before do 7 | Installer.any_instance.stubs(:exists).returns(true) 8 | Installer.any_instance.stubs(:installed).returns([]) 9 | fixture = Pathname.new('spec/fixtures/xcode_63.json').read 10 | xcode = Xcode.new(JSON.parse(fixture)) 11 | Installer.any_instance.stubs(:seedlist).returns([xcode]) 12 | end 13 | 14 | it 'downloads and installs' do 15 | Installer.any_instance.expects(:download).with('6.3', true, nil, nil, 3).returns('/some/path') 16 | Installer.any_instance.expects(:install_dmg).with('/some/path', '-6.3', true, true) 17 | Command::Install.run(['6.3']) 18 | end 19 | 20 | it 'downloads and installs with custom HTTP URL' do 21 | url = 'http://yolo.com/xcode.dmg' 22 | Installer.any_instance.expects(:download).with('6.3', true, url, nil, 3).returns('/some/path') 23 | Installer.any_instance.expects(:install_dmg).with('/some/path', '-6.3', true, true) 24 | Command::Install.run(['6.3', "--url=#{url}"]) 25 | end 26 | 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, nil, 3).returns('/some/path') 29 | Installer.any_instance.expects(:install_dmg).with('/some/path', '-6.3', false, true) 30 | Command::Install.run(['6.3', '--no-switch']) 31 | end 32 | 33 | it 'downloads without progress if switch --no-progress is given' do 34 | Installer.any_instance.expects(:download).with('6.3', false, nil, nil, 3).returns('/some/path') 35 | Installer.any_instance.expects(:install_dmg).with('/some/path', '-6.3', true, true) 36 | Command::Install.run(['6.3', '--no-progress']) 37 | end 38 | 39 | it 'reads .xcode-version' do 40 | Installer.any_instance.expects(:download).with('6.3', true, nil, nil, 3).returns('/some/path') 41 | Installer.any_instance.expects(:install_dmg).with('/some/path', '-6.3', true, true) 42 | File.expects(:exist?).with('.xcode-version').returns(true) 43 | File.expects(:read).returns('6.3') 44 | Command::Install.run([]) 45 | end 46 | end 47 | 48 | it 'parses hdiutil output' do 49 | installer = Installer.new 50 | fixture = Pathname.new('spec/fixtures/hdiutil.plist').read 51 | installer.expects(:hdiutil).with('mount', '-plist', '-nobrowse', '-noverify', '/some/path').returns(fixture) 52 | location = installer.send(:mount, Pathname.new('/some/path')) 53 | location.should == '/Volumes/XcodeME' 54 | end 55 | 56 | it 'gives more helpful error when downloaded DMG turns out to be HTML' do 57 | installer = Installer.new 58 | should.raise(Informative) { installer.mount('spec/fixtures/mail-verify.html') }.message 59 | .should.include 'logging into your account from a browser' 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/installed_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../spec_helper', __FILE__) 2 | 3 | module XcodeInstall 4 | xcode_path = '/Volumes/Macintosh HD/Applications/Xcode Beta' 5 | 6 | describe InstalledXcode do 7 | it 'finds the current Xcode version with whitespace chars' do 8 | InstalledXcode.any_instance.expects(:`).with("/usr/libexec/PlistBuddy -c \"Print :CFBundleShortVersionString\" \"#{xcode_path}/Contents/version.plist\"").returns('6.3.1') 9 | installed = InstalledXcode.new(xcode_path) 10 | installed.version.should == '6.3.1' 11 | end 12 | 13 | it 'is robust against broken Xcode installations' do 14 | InstalledXcode.any_instance.expects(:`).with("/usr/libexec/PlistBuddy -c \"Print :CFBundleShortVersionString\" \"#{xcode_path}/Contents/version.plist\"").returns(nil) 15 | installed = InstalledXcode.new(xcode_path) 16 | installed.version.should == '0.0' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/installer_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../spec_helper', __FILE__) 2 | 3 | module XcodeInstall 4 | # 5 | # FIXME: This test randomely fail on GitHub Actions. 6 | # 7 | # describe '#fetch' do 8 | # before do 9 | # client = mock 10 | # client.stubs(:cookie).returns('customCookie') 11 | # Spaceship::PortalClient.stubs(:login).returns(client) 12 | # end 13 | 14 | # it 'downloads the file and calls the `progress_block` with the percentage' do 15 | # installer = Installer.new 16 | 17 | # xcode = XcodeInstall::Xcode.new('name' => 'Xcode 9.3', 18 | # 'files' => [{ 19 | # 'remotePath' => '/Developer_Tools/Xcode_9.3/Xcode_9.3.xip' 20 | # }]) 21 | 22 | # installer.stubs(:fetch_seedlist).returns([xcode]) 23 | 24 | # stdin = 'stdin' 25 | # stdout = 'stdout' 26 | # stderr = 'stderr' 27 | # wait_thr = 'wait_thr' 28 | 29 | # stdin.stubs(:close) 30 | # stdout.stubs(:close) 31 | # stderr.stubs(:close) 32 | 33 | # current_time = '123123' 34 | # Time.stubs(:now).returns(current_time) 35 | 36 | # xip_path = File.join(File.expand_path('~'), '/Library/Caches/XcodeInstall/Xcode_9.3.xip') 37 | # progress_log_file = File.join(File.expand_path('~'), "/Library/Caches/XcodeInstall/progress.#{current_time}.progress") 38 | 39 | # command = [ 40 | # 'curl', 41 | # '--disable', 42 | # '--cookie customCookie', 43 | # '--cookie-jar /tmp/curl-cookies.txt', 44 | # '--retry 3', 45 | # '--location', 46 | # '--continue-at -', 47 | # "--output #{xip_path}", 48 | # 'https://developer.apple.com/devcenter/download.action\\?path\\=/Developer_Tools/Xcode_9.3/Xcode_9.3.xip', 49 | # "2> #{progress_log_file}" 50 | # ] 51 | # Open3.stubs(:popen3).with(command.join(' ')).returns([stdin, stdout, stderr, wait_thr]) 52 | 53 | # wait_thr.stubs(:alive?).returns(true) 54 | 55 | # thr_value = 'thr_value' 56 | # wait_thr.stubs(:value).returns(thr_value) 57 | # thr_value.stubs(:success?).returns(true) 58 | 59 | # installer.stubs(:install_dmg).with(Pathname.new(xip_path), '-9.3', false, false) 60 | 61 | # Thread.new do 62 | # sleep(1) 63 | # File.write(progress_log_file, ' 0 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, ' 5 4766M 0 6835k 0 0 573k 0 2:21:58 0:00:11 2:21:47 902k') 66 | # sleep(1) 67 | # File.write(progress_log_file, '50 4766M 0 6835k 0 0 573k 0 2:21:58 0:00:11 2:21:47 902k') 68 | # sleep(1) 69 | # File.write(progress_log_file, '100 4766M 0 6835k 0 0 573k 0 2:21:58 0:00:11 2:21:47 902k') 70 | # sleep(0.5) 71 | # wait_thr.stubs(:alive?).returns(false) 72 | # end 73 | 74 | # percentages = [] 75 | # installer.install_version( 76 | # # version: the version to install 77 | # '9.3', 78 | # # `should_switch 79 | # false, 80 | # # `should_clean` 81 | # false, # false for now for faster debugging 82 | # # `should_install` 83 | # true, 84 | # # `progress` 85 | # false, 86 | # # `url` is nil, as we don't have a custom source 87 | # nil, 88 | # # `show_release_notes` is `false`, as this is a non-interactive machine 89 | # false, 90 | # # `progress_block` be updated on the download progress 91 | # proc do |percent| 92 | # percentages << percent 93 | # end 94 | # ) 95 | 96 | # percentages.each do |current_percent| 97 | # # Verify all reported percentages are between 0 and 100 98 | # current_percent.should.be.close(50, 50) 99 | # end 100 | # # Verify we got a good amount of percentages reported 101 | # percentages.count.should.be.close(8, 4) 102 | # end 103 | # end 104 | 105 | describe '#find_xcode_version' do 106 | it 'should find the one with the matching name' do 107 | installer = Installer.new 108 | 109 | xcodes = [ 110 | XcodeInstall::Xcode.new('name' => '11.4 beta 2', 111 | 'dateModified' => '12/11/19 14:28', 112 | 'files' => [{ 113 | 'remotePath' => '/Developer_Tools/Xcode_11.4_beta_2/Xcode_11.4_beta_2.xip' 114 | }]), 115 | XcodeInstall::Xcode.new('name' => '11.4', 116 | 'dateModified' => '12/15/19 11:28', 117 | 'files' => [{ 118 | 'remotePath' => '/Developer_Tools/Xcode_11.4/Xcode_11.4.xip' 119 | }]) 120 | ] 121 | 122 | installer.stubs(:fetch_seedlist).returns(xcodes) 123 | installer.find_xcode_version('11.4').name.should.be.equal('11.4') 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /spec/json_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../spec_helper', __FILE__) 2 | 3 | module XcodeInstall 4 | describe JSON do 5 | it 'can parse Xcode JSON' do 6 | fixture = Pathname.new('spec/fixtures/xcode.json').read 7 | xcode = Xcode.new(JSON.parse(fixture)) 8 | 9 | xcode.date_modified.should == 1_572_613_080 10 | xcode.name.should == '9.3' 11 | xcode.url.should == 'https://developer.apple.com/devcenter/download.action?path=/Developer_Tools/Xcode_9.3/Xcode_9.3.xip' 12 | end 13 | 14 | it 'can parse list of all Xcodes' do 15 | fixture = Pathname.new('spec/fixtures/yolo.json').read 16 | installer = Installer.new 17 | 18 | seedlist = installer.send(:parse_seedlist, JSON.parse(fixture)) 19 | installer.stubs(:installed_versions).returns([]) 20 | installer.stubs(:xcodes).returns(seedlist) 21 | 22 | versions = [ 23 | '4.3 for Lion', '4.3.1 for Lion', '4.3.2 for Lion', '4.3.3 for Lion', '4.4.1', '4.5', '4.6', '4.6.1', '4.6.2', '4.6.3', 24 | '5', '5.0.1', '5.0.2', '5.1', '5.1.1', 25 | '6.0.1', '6.1', '6.1.1', '6.2', '6.3', '6.3.1', '6.3.2', '6.4', 26 | '7', '7.0.1', '7.1', '7.1.1', '7.2', '7.2.1', '7.3', '7.3.1', 27 | '8', '8.1', '8.2', '8.2.1', '8.3', '8.3.2', '8.3.3', 28 | '9', '9.0.1', '9.1', '9.2', '9.3', '9.3.1', '9.4', '9.4.1', 29 | '10', '10.1', '10.2', '10.2.1', '10.3', 30 | '11', '11.1', '11.2', '11.2.1', '11.3 beta', '11.3', '11.3.1', '11.4 beta', '11.4 beta 2', '11.4 beta 3', '11.4', '11.4.1', '11.5 beta', '11.5 beta 2', '11.5 GM Seed', '11.5' 31 | ] 32 | installer.list.split("\n").should == versions 33 | end 34 | 35 | it 'raises informative error when account is not registered as a developer' do 36 | installer = Installer.new 37 | fixture = Pathname.new('spec/fixtures/not_registered_as_developer.json').read 38 | should.raise(Informative) { installer.send(:parse_seedlist, JSON.parse(fixture)) }.message 39 | .should.include fixture['You are not registered as an Apple Developer'] 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/list_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../spec_helper', __FILE__) 2 | 3 | module XcodeInstall 4 | describe Command::List do 5 | before do 6 | installer.stubs(:exists).returns(true) 7 | end 8 | 9 | def installer 10 | @installer ||= Installer.new 11 | end 12 | 13 | def fake_xcode(name) 14 | fixture = Pathname.new('spec/fixtures/xcode_63.json').read 15 | xcode = Xcode.new(JSON.parse(fixture)) 16 | xcode.stubs(:name).returns(name) 17 | xcode 18 | end 19 | 20 | def fake_xcodes(*names) 21 | xcodes = names.map { |name| fake_xcode(name) } 22 | installer.stubs(:xcodes).returns(xcodes) 23 | end 24 | 25 | def fake_installed_xcode(name) 26 | installed_name = name.split(' ').join('.') 27 | xcode_path = "/Applications/Xcode-#{installed_name}.app" 28 | xcode_version = name.split(' ').first 29 | xcode_version << '.0' unless name.include? '.' 30 | 31 | installed_xcode = InstalledXcode.new(xcode_path) 32 | installed_xcode.stubs(:version).returns(xcode_version) 33 | installed_xcode.stubs(:bundle_version).returns(Gem::Version.new(xcode_version)) 34 | installed_xcode 35 | end 36 | 37 | def fake_installed_xcodes(*names) 38 | xcodes = names.map { |name| fake_installed_xcode(name) } 39 | installer.stubs(:installed_versions).returns(xcodes) 40 | end 41 | 42 | describe '#list' do 43 | it 'lists all versions' do 44 | fake_xcodes '1', '2.3', '2.3.1', '2.3.2', '3 some', '4 beta', '10 beta' 45 | fake_installed_xcodes 46 | installer.list.should == "1\n2.3\n2.3.1\n2.3.2\n3 some\n4 beta\n10 beta" 47 | end 48 | 49 | it 'lists all versions in the correct order' do 50 | fake_xcodes( 51 | '12 beta 4', '12 beta 3', '12 beta 2', '12 for macOS Universal Apps beta 2', 52 | '12 beta', '12 for macOS Universal Apps beta', '12.0.1', '12', '12 beta 6', 53 | '12 beta 5', '12.1 GM seed', '12.2 beta 3', '12.2 beta', '12.2 beta 2' 54 | ) 55 | fake_installed_xcodes 56 | 57 | versions = [ 58 | '12 beta', '12 beta 2', '12 beta 3', '12 beta 4', '12 beta 5', '12 beta 6', 59 | '12 for macOS Universal Apps beta', '12 for macOS Universal Apps beta 2', 60 | '12', '12.0.1', '12.1 GM seed', '12.2 beta', '12.2 beta 2', '12.2 beta 3' 61 | ] 62 | installer.list.split("\n").should == versions 63 | end 64 | end 65 | 66 | describe '#list_annotated' do 67 | it 'lists all versions with annotations' do 68 | fake_xcodes '1', '2.3', '2.3.1', '2.3.2', '3 some', '4.3.1 for Lion', '9.4.1', '10 beta' 69 | fake_installed_xcodes '2.3', '4.3.1 for Lion', '10 beta' 70 | installer.list.should == "1\n2.3 (installed)\n2.3.1\n2.3.2\n3 some\n4.3.1 for Lion (installed)\n9.4.1\n10 beta (installed)" 71 | end 72 | 73 | it 'distinguish between beta and official_version' do 74 | fake_xcodes '11.4', '11.4 beta' 75 | fake_installed_xcodes '11.4' 76 | installer.list.should == "11.4 beta\n11.4 (installed)" 77 | end 78 | 79 | it 'distinguish each beta versions' do 80 | fake_xcodes '11.4 beta 3', '11.4 beta' 81 | fake_installed_xcodes '11.4 beta' 82 | installer.list.should == "11.4 beta (installed)\n11.4 beta 3" 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /spec/prerelease_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../spec_helper', __FILE__) 2 | 3 | module XcodeInstall 4 | describe Installer do 5 | def fixture(date) 6 | File.read("spec/fixtures/devcenter/xcode-#{date}.html") 7 | end 8 | 9 | def parse_prereleases(date) 10 | fixture = fixture(date) 11 | @result.stubs(:body).returns(fixture) 12 | 13 | installer = Installer.new 14 | installer.send(:prereleases) 15 | end 16 | 17 | before do 18 | devcenter = mock 19 | devcenter.stubs(:download_file).returns(nil) 20 | Installer.any_instance.stubs(:devcenter).returns(devcenter) 21 | 22 | @result = mock 23 | @result.stubs(:body).returns(nil) 24 | client = mock 25 | client.stubs(:request).returns(@result) 26 | Spaceship::PortalClient.stubs(:login).returns(client) 27 | end 28 | 29 | it 'can parse prereleases from 20150414' do 30 | prereleases = parse_prereleases('20150414') 31 | 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 | end 34 | 35 | it 'can parse prereleases from 20150427' do 36 | prereleases = parse_prereleases('20150427') 37 | 38 | prereleases.should == [Xcode.new_prerelease('6.4 beta 2', '/Developer_Tools/Xcode_6.4_beta_2/Xcode_6.4_beta_2.dmg', '/Developer_Tools/Xcode_6.4_beta_2/Xcode_6.4_beta_2_Release_Notes.pdf')] 39 | end 40 | 41 | it 'can parse prereleases from 20150508' do 42 | prereleases = parse_prereleases('20150508') 43 | 44 | prereleases.count.should == 2 45 | prereleases.first.should == Xcode.new_prerelease('6.3.2 GM seed', '/Developer_Tools/Xcode_6.3.2_GM_seed/Xcode_6.3.2_GM_seed.dmg', '/Developer_Tools/Xcode_6.3.2_GM_seed/Xcode_6.3.2_GM_Seed_Release_Notes.pdf') 46 | prereleases.last.should == Xcode.new_prerelease('6.4 beta 2', '/Developer_Tools/Xcode_6.4_beta_2/Xcode_6.4_beta_2.dmg', '/Developer_Tools/Xcode_6.4_beta_2/Xcode_6.4_beta_2_Release_Notes.pdf') 47 | end 48 | 49 | it 'can parse prereleases from 20150608' do 50 | prereleases = parse_prereleases('20150608') 51 | 52 | prereleases.count.should == 2 53 | prereleases.first.should == Xcode.new_prerelease('7 beta', '/WWDC_2015/Xcode_7_beta/Xcode_7_beta.dmg', '/WWDC_2015/Xcode_7_beta/Xcode_7_beta_Release_Notes.pdf') 54 | prereleases.last.should == Xcode.new_prerelease('6.4 beta 3', '/Developer_Tools/Xcode_6.4_beta_3/Xcode_6.4_beta_3.dmg', '/Developer_Tools/Xcode_6.4_beta_3/Xcode_6.4_beta_3_Release_Notes.pdf') 55 | end 56 | 57 | it 'can parse prereleases from 20150624' do 58 | prereleases = parse_prereleases('20150624') 59 | 60 | prereleases.count.should == 2 61 | prereleases.first.should == Xcode.new_prerelease('7 beta 2', '/Developer_Tools/Xcode_7_beta_2/Xcode_7_beta_2.dmg', '/Developer_Tools/Xcode_7_beta_2/Xcode_7_beta_2_Release_Notes.pdf') 62 | prereleases.last.should == Xcode.new_prerelease('6.4 beta 4', '/WWDC_2015/Xcode_6.4_beta_4/Xcode_6.4_beta_4.dmg', '/WWDC_2015/Xcode_6.4_beta_4/Xcode_6.4_beta_4_Release_Notes.pdf') 63 | end 64 | 65 | it 'can parse prereleases from 20150909' do 66 | prereleases = parse_prereleases('20150909') 67 | 68 | prereleases.count.should == 2 69 | prereleases.first.should == Xcode.new_prerelease('7.1 beta', '/Developer_Tools/Xcode_7.1_beta/Xcode_7.1_beta.dmg', '/Developer_Tools/Xcode_7.1_beta/Xcode_7.1_beta_Release_Notes.pdf') 70 | prereleases.last.should == Xcode.new_prerelease('7 GM seed', '/Developer_Tools/Xcode_7_GM_seed/Xcode_7_GM_seed.dmg', nil) 71 | end 72 | 73 | it 'can parse prereleases from 20160601' do 74 | prereleases = parse_prereleases('20160601') 75 | 76 | prereleases.count.should == 1 77 | prereleases.first.should == Xcode.new('8 beta', '/services-account/download?path=/WWDC_2016/Xcode_8_beta/Xcode_8_beta.xip', '/go/?id=xcode-8-beta-rn') 78 | end 79 | 80 | it 'can parse prereleases from 20160705' do 81 | prereleases = parse_prereleases('20160705') 82 | 83 | prereleases.count.should == 1 84 | prereleases.first.should == Xcode.new('8 beta 2', '/services-account/download?path=/Developer_Tools/Xcode_8_beta_2/Xcode_8_beta_2.xip', '/go/?id=xcode-8-beta-rn') 85 | end 86 | 87 | it 'can parse prereleases from 20160705 (alternative page)' do 88 | prereleases = parse_prereleases('20160705-alt') 89 | 90 | prereleases.count.should == 1 91 | prereleases.first.should == Xcode.new_prerelease('8 beta 2', '/devcenter/download.action?path=/Developer_Tools/Xcode_8_beta_2/Xcode_8_beta_2.xip', nil) 92 | end 93 | 94 | it 'can parse prereleases from 20160922' do 95 | prereleases = parse_prereleases('20160922') 96 | 97 | prereleases.count.should == 1 98 | prereleases.first.should == Xcode.new('8.1 beta', '/services-account/download?path=/Developer_Tools/Xcode_8.1_beta/Xcode_8.1_beta.xip', '/go/?id=xcode-8.1-beta-rn') 99 | end 100 | 101 | it 'can parse prereleases from 20161024' do 102 | prereleases = parse_prereleases('20161024') 103 | 104 | prereleases.count.should == 1 105 | prereleases.first.should == Xcode.new('8.1 GM seed', '/services-account/download?path=/Developer_Tools/Xcode_8.1_GM_Seed/Xcode_8.1_GM_Seed.xip', '/go/?id=xcode-8.1-beta-rn') 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | Coveralls.wear! 3 | 4 | require 'pathname' 5 | ROOT = Pathname.new(File.expand_path('../../', __FILE__)) 6 | $LOAD_PATH.unshift((ROOT + 'lib').to_s) 7 | $LOAD_PATH.unshift((ROOT + 'spec').to_s) 8 | 9 | ENV['DELIVER_USER'] = 'xcversion' 10 | ENV['DELIVER_PASSWORD'] = '12345password' 11 | 12 | require 'bundler/setup' 13 | require 'bacon' 14 | require 'mocha-on-bacon' 15 | require 'pretty_bacon' 16 | require 'xcode/install' 17 | -------------------------------------------------------------------------------- /spec/uninstall_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../spec_helper', __FILE__) 2 | 3 | module XcodeInstall 4 | describe Command::Uninstall do 5 | describe 'when invoked' do 6 | it 'raise error when the version is not specified' do 7 | Command::Uninstall.any_instance.expects(:help!) 8 | -> { Command::Uninstall.run([]) }.should.raise(Exception) 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /xcode-install.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | lib = File.expand_path('../lib', __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'xcode/install/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'xcode-install' 9 | spec.version = XcodeInstall::VERSION 10 | spec.authors = ['Boris Bügling'] 11 | spec.email = ['boris@icculus.org'] 12 | spec.summary = 'Xcode installation manager.' 13 | spec.description = 'Download, install and upgrade Xcodes with ease.' 14 | spec.homepage = 'https://github.com/neonichu/xcode-install' 15 | spec.license = 'MIT' 16 | 17 | spec.required_ruby_version = '>= 2.0.0' 18 | 19 | spec.files = `git ls-files -z`.split("\x0") 20 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 21 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 22 | spec.require_paths = ['lib'] 23 | 24 | # CLI parsing 25 | spec.add_dependency 'claide', '>= 0.9.1' 26 | 27 | # contains spaceship, which is used for auth and dev portal interactions 28 | spec.add_dependency 'fastlane', '>= 2.1.0', '< 3.0.0' 29 | 30 | spec.add_development_dependency 'bundler', '>= 2.0.0', '< 3.0.0' 31 | spec.add_development_dependency 'rake', '>= 12.3.3' 32 | end 33 | --------------------------------------------------------------------------------