xcode-install 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +29 -0
- data/lib/xcode/install/command.rb +1 -0
- data/lib/xcode/install/simulators.rb +58 -0
- data/lib/xcode/install/version.rb +1 -1
- data/lib/xcode/install.rb +147 -6
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6400b83c001ff5429ba066cb709c7c48a3ee52d3
|
|
4
|
+
data.tar.gz: d03b8a2ca220319f47e47883cff2b240bcdd6697
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7f670c8560447440ae2b048c481a91addf64752bbc083c0f849a92bc46a403eb09f7d58c05eaaa2714a9d7b005287821d01340bf78a4cc91f89be8e0875687b1
|
|
7
|
+
data.tar.gz: 1c116c302f03f011a924f6bd764967703fcc00af5578554318be11bc96045e28ec210a6e310e8059d04c20473d26af21e763e3b1e57a28986077a92f76f14549
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -69,6 +69,35 @@ they have to be installed using the full name, e.g. `xcversion install '7 GM see
|
|
|
69
69
|
|
|
70
70
|
XcodeInstall can also install Xcode's Command Line Tools by calling `xcversion install-cli-tools`.
|
|
71
71
|
|
|
72
|
+
### Simulators
|
|
73
|
+
|
|
74
|
+
XcodeInstall can also manage your local simulators using the `simulators` command.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
$ xcode-install simulators
|
|
78
|
+
Xcode 6.4.0 (/Applications/Xcode.app)
|
|
79
|
+
iOS 7.1 Simulator (installed)
|
|
80
|
+
iOS 8.1 Simulator (not installed)
|
|
81
|
+
iOS 8.2 Simulator (not installed)
|
|
82
|
+
iOS 8.3 Simulator (installed)
|
|
83
|
+
Xcode 7.0.0 (/Applications/Xcode-beta.app)
|
|
84
|
+
iOS 8.1 Simulator (not installed)
|
|
85
|
+
iOS 8.2 Simulator (not installed)
|
|
86
|
+
iOS 8.3 Simulator (installed)
|
|
87
|
+
iOS 8.4 Simulator (installed)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
To install a simulator, simply:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
$ xcode-install simulators --install=8.4
|
|
94
|
+
########################################################### 82.1%
|
|
95
|
+
######################################################################## 100.0%
|
|
96
|
+
Please authenticate to install iOS 8.4 Simulator...
|
|
97
|
+
|
|
98
|
+
Successfully installed iOS 8.4 Simulator
|
|
99
|
+
```
|
|
100
|
+
|
|
72
101
|
## Limitations
|
|
73
102
|
|
|
74
103
|
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. 📡
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'claide'
|
|
2
|
+
|
|
3
|
+
module XcodeInstall
|
|
4
|
+
class Command
|
|
5
|
+
class Simulators < Command
|
|
6
|
+
self.command = 'simulators'
|
|
7
|
+
self.summary = 'List or install iOS simulators.'
|
|
8
|
+
|
|
9
|
+
def self.options
|
|
10
|
+
[['--install=version', 'Install simulator with the given version.']].concat(super)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(argv)
|
|
14
|
+
@installed_xcodes = Installer.new.installed_versions
|
|
15
|
+
@install = argv.option('install')
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run
|
|
20
|
+
@install ? install : list
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
:private
|
|
25
|
+
|
|
26
|
+
def install
|
|
27
|
+
filtered_simulators = @installed_xcodes.map { |x| x.available_simulators }.flatten.select do |sim|
|
|
28
|
+
sim.version.to_s.start_with?(@install)
|
|
29
|
+
end
|
|
30
|
+
if filtered_simulators.count == 0
|
|
31
|
+
puts "[!] No simulator matching #{@install} was found. Please specify a version from the following available simulators:".ansi.red
|
|
32
|
+
list
|
|
33
|
+
exit 1
|
|
34
|
+
elsif filtered_simulators.count == 1
|
|
35
|
+
simulator = filtered_simulators.first
|
|
36
|
+
fail Informative, "#{simulator.name} is already installed." if simulator.installed?
|
|
37
|
+
puts "Installing #{simulator.name} for Xcode #{simulator.xcode.bundle_version}..."
|
|
38
|
+
simulator.install
|
|
39
|
+
else
|
|
40
|
+
puts "[!] More than one simulator matching #{@install} was found. Please specify the full version.".ansi.red
|
|
41
|
+
filtered_simulators.each do |simulator|
|
|
42
|
+
puts "Xcode #{simulator.xcode.bundle_version} (#{simulator.xcode.path})".ansi.green
|
|
43
|
+
puts "xcode-install simulator --install=#{simulator.version}"
|
|
44
|
+
end
|
|
45
|
+
exit 1
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def list
|
|
50
|
+
@installed_xcodes.each do |xcode|
|
|
51
|
+
puts "Xcode #{xcode.version} (#{xcode.path})".ansi.green
|
|
52
|
+
xcode.available_simulators.each do |simulator|
|
|
53
|
+
puts simulator.to_s
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/xcode/install.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
require 'fileutils'
|
|
2
2
|
require 'pathname'
|
|
3
3
|
require 'spaceship'
|
|
4
|
+
require 'json'
|
|
4
5
|
require 'rubygems/version'
|
|
5
6
|
require 'xcode/install/command'
|
|
6
7
|
require 'xcode/install/version'
|
|
7
8
|
|
|
8
9
|
module XcodeInstall
|
|
10
|
+
CACHE_DIR = Pathname.new("#{ENV['HOME']}/Library/Caches/XcodeInstall")
|
|
9
11
|
class Curl
|
|
10
12
|
COOKIES_PATH = Pathname.new('/tmp/curl-cookies.txt')
|
|
11
13
|
|
|
@@ -62,7 +64,7 @@ module XcodeInstall
|
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
def installed_versions
|
|
65
|
-
|
|
67
|
+
installed.map { |x| InstalledXcode.new(x) }.sort do |a, b|
|
|
66
68
|
Gem::Version.new(a.version) <=> Gem::Version.new(b.version)
|
|
67
69
|
end
|
|
68
70
|
end
|
|
@@ -93,7 +95,9 @@ HELP
|
|
|
93
95
|
end
|
|
94
96
|
|
|
95
97
|
enable_developer_mode
|
|
96
|
-
InstalledXcode.new(xcode_path)
|
|
98
|
+
xcode = InstalledXcode.new(xcode_path)
|
|
99
|
+
xcode.approve_license
|
|
100
|
+
xcode.install_components
|
|
97
101
|
|
|
98
102
|
if switch
|
|
99
103
|
`sudo rm -f #{SYMLINK_PATH}` unless current_symlink.nil?
|
|
@@ -169,7 +173,6 @@ HELP
|
|
|
169
173
|
end
|
|
170
174
|
end
|
|
171
175
|
|
|
172
|
-
CACHE_DIR = Pathname.new("#{ENV['HOME']}/Library/Caches/XcodeInstall")
|
|
173
176
|
LIST_FILE = CACHE_DIR + Pathname.new('xcodes.bin')
|
|
174
177
|
MINIMUM_VERSION = Gem::Version.new('4.3')
|
|
175
178
|
SYMLINK_PATH = Pathname.new('/Applications/Xcode.app')
|
|
@@ -265,13 +268,133 @@ HELP
|
|
|
265
268
|
end
|
|
266
269
|
end
|
|
267
270
|
|
|
271
|
+
class Simulator
|
|
272
|
+
attr_reader :version
|
|
273
|
+
attr_reader :name
|
|
274
|
+
attr_reader :identifier
|
|
275
|
+
attr_reader :source
|
|
276
|
+
attr_reader :xcode
|
|
277
|
+
|
|
278
|
+
def initialize(downloadable)
|
|
279
|
+
@version = Gem::Version.new(downloadable['version'])
|
|
280
|
+
@install_prefix = apply_variables(downloadable['userInfo']['InstallPrefix'])
|
|
281
|
+
@name = apply_variables(downloadable['name'])
|
|
282
|
+
@identifier = apply_variables(downloadable['identifier'])
|
|
283
|
+
@source = apply_variables(downloadable['source'])
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def installed?
|
|
287
|
+
# FIXME: use downloadables' `InstalledIfAllReceiptsArePresentOrNewer` key
|
|
288
|
+
File.directory?(@install_prefix)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def installed_string
|
|
292
|
+
installed? ? 'installed' : 'not installed'
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def to_s
|
|
296
|
+
"#{name} (#{installed_string})"
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def xcode
|
|
300
|
+
Installer.new.installed_versions.find do |x|
|
|
301
|
+
x.available_simulators.find do |s|
|
|
302
|
+
s.version == version
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def download
|
|
308
|
+
result = Curl.new.fetch(source, CACHE_DIR)
|
|
309
|
+
result ? dmg_path : nil
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def install
|
|
313
|
+
download unless dmg_path.exist?
|
|
314
|
+
prepare_package unless pkg_path.exist?
|
|
315
|
+
puts "Please authenticate to install #{name}..."
|
|
316
|
+
`sudo installer -pkg #{pkg_path} -target /`
|
|
317
|
+
fail Informative, "Could not install #{name}, please try again" unless installed?
|
|
318
|
+
source_receipts_dir = '/private/var/db/receipts'
|
|
319
|
+
target_receipts_dir = "#{@install_prefix}/System/Library/Receipts"
|
|
320
|
+
FileUtils.mkdir_p(target_receipts_dir)
|
|
321
|
+
FileUtils.cp("#{source_receipts_dir}/#{@identifier}.bom", target_receipts_dir)
|
|
322
|
+
FileUtils.cp("#{source_receipts_dir}/#{@identifier}.plist", target_receipts_dir)
|
|
323
|
+
puts "Successfully installed #{name}"
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
:private
|
|
327
|
+
|
|
328
|
+
def prepare_package
|
|
329
|
+
puts 'Mounting DMG'
|
|
330
|
+
mount_location = `hdiutil mount -nobrowse -noverify #{dmg_path}`.scan(/\/Volumes.*\n/).first.chomp
|
|
331
|
+
puts 'Expanding pkg'
|
|
332
|
+
expanded_pkg_path = CACHE_DIR + identifier
|
|
333
|
+
FileUtils.rm_rf(expanded_pkg_path)
|
|
334
|
+
`pkgutil --expand #{mount_location}/*.pkg #{expanded_pkg_path}`
|
|
335
|
+
puts "Expanded pkg into #{expanded_pkg_path}"
|
|
336
|
+
puts 'Unmounting DMG'
|
|
337
|
+
`umount #{mount_location}`
|
|
338
|
+
puts 'Setting package installation location'
|
|
339
|
+
package_info_path = expanded_pkg_path + 'PackageInfo'
|
|
340
|
+
package_info_contents = File.read(package_info_path)
|
|
341
|
+
File.open(package_info_path, 'w') do |f|
|
|
342
|
+
f << package_info_contents.sub('pkg-info', %(pkg-info install-location="#{@install_prefix}"))
|
|
343
|
+
end
|
|
344
|
+
puts 'Rebuilding package'
|
|
345
|
+
`pkgutil --flatten #{expanded_pkg_path} #{pkg_path}`
|
|
346
|
+
FileUtils.rm_rf(expanded_pkg_path)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def dmg_path
|
|
350
|
+
CACHE_DIR + Pathname.new(source).basename
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def pkg_path
|
|
354
|
+
CACHE_DIR + "#{identifier}.pkg"
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def apply_variables(template)
|
|
358
|
+
variable_map = {
|
|
359
|
+
'$(DOWNLOADABLE_VERSION_MAJOR)' => version.to_s.split('.')[0],
|
|
360
|
+
'$(DOWNLOADABLE_VERSION_MINOR)' => version.to_s.split('.')[1],
|
|
361
|
+
'$(DOWNLOADABLE_IDENTIFIER)' => identifier,
|
|
362
|
+
'$(DOWNLOADABLE_VERSION)' => version.to_s
|
|
363
|
+
}.freeze
|
|
364
|
+
variable_map.each do |key, value|
|
|
365
|
+
next unless template.include?(key)
|
|
366
|
+
template.sub!(key, value)
|
|
367
|
+
end
|
|
368
|
+
template
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
268
372
|
class InstalledXcode
|
|
269
373
|
attr_reader :path
|
|
270
374
|
attr_reader :version
|
|
375
|
+
attr_reader :bundle_version
|
|
376
|
+
attr_reader :uuid
|
|
377
|
+
attr_reader :downloadable_index_url
|
|
378
|
+
attr_reader :available_simulators
|
|
271
379
|
|
|
272
380
|
def initialize(path)
|
|
273
381
|
@path = Pathname.new(path)
|
|
274
|
-
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def version
|
|
385
|
+
@version ||= get_version
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def bundle_version
|
|
389
|
+
@bundle_version ||= Gem::Version.new(plist_entry(':DTXcode').to_i.to_s.split(//).join('.'))
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def uuid
|
|
393
|
+
@uuid ||= plist_entry(':DVTPlugInCompatibilityUUID')
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def downloadable_index_url
|
|
397
|
+
@downloadable_index_url ||= "https://devimages.apple.com.edgekey.net/downloads/xcode/simulators/index-#{bundle_version}-#{uuid}.dvtdownloadableindex"
|
|
275
398
|
end
|
|
276
399
|
|
|
277
400
|
def approve_license
|
|
@@ -283,10 +406,28 @@ HELP
|
|
|
283
406
|
`sudo /usr/libexec/PlistBuddy -c "add :IDEXcodeVersionForAgreedToGMLicense string #{@version}" #{license_plist_path}`
|
|
284
407
|
end
|
|
285
408
|
|
|
409
|
+
def available_simulators
|
|
410
|
+
@available_simulators ||= JSON.parse(`curl -Ls #{downloadable_index_url} | plutil -convert json -o - -`)['downloadables'].map do |downloadable|
|
|
411
|
+
Simulator.new(downloadable)
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def install_components
|
|
416
|
+
`sudo installer -pkg #{@path}/Contents/Resources/Packages/MobileDevice.pkg -target /`
|
|
417
|
+
osx_build_version = `sw_vers -buildVersion`.chomp
|
|
418
|
+
tools_version = `/usr/libexec/PlistBuddy -c "Print :ProductBuildVersion" "#{@path}/Contents/version.plist"`.chomp
|
|
419
|
+
cache_dir = `getconf DARWIN_USER_CACHE_DIR`.chomp
|
|
420
|
+
`touch #{cache_dir}com.apple.dt.Xcode.InstallCheckCache_#{osx_build_version}_#{tools_version}`
|
|
421
|
+
end
|
|
422
|
+
|
|
286
423
|
:private
|
|
287
424
|
|
|
288
|
-
def
|
|
289
|
-
|
|
425
|
+
def plist_entry(keypath)
|
|
426
|
+
`/usr/libexec/PlistBuddy -c "Print :#{keypath}" "#{path}/Contents/Info.plist"`.chomp
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def get_version
|
|
430
|
+
output = `DEVELOPER_DIR='' "#{@path}/Contents/Developer/usr/bin/xcodebuild" -version`
|
|
290
431
|
return '0.0' if output.nil? # ¯\_(ツ)_/¯
|
|
291
432
|
output.split("\n").first.split(' ')[1]
|
|
292
433
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: xcode-install
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Boris Bügling
|
|
@@ -102,6 +102,7 @@ files:
|
|
|
102
102
|
- lib/xcode/install/list.rb
|
|
103
103
|
- lib/xcode/install/select.rb
|
|
104
104
|
- lib/xcode/install/selected.rb
|
|
105
|
+
- lib/xcode/install/simulators.rb
|
|
105
106
|
- lib/xcode/install/uninstall.rb
|
|
106
107
|
- lib/xcode/install/update.rb
|
|
107
108
|
- lib/xcode/install/version.rb
|