xcode-installer 0.1.9 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/HISTORY.md +6 -1
- data/bin/xcode-installer +1 -0
- data/lib/xcode-installer/commands/download.rb +3 -31
- data/lib/xcode-installer/commands/install.rb +6 -27
- data/lib/xcode-installer/commands/list.rb +3 -48
- data/lib/xcode-installer/commands/login.rb +3 -10
- data/lib/xcode-installer/commands/logout.rb +3 -7
- data/lib/xcode-installer/download.rb +34 -0
- data/lib/xcode-installer/install.rb +530 -0
- data/lib/xcode-installer/list.rb +54 -0
- data/lib/xcode-installer/login.rb +20 -0
- data/lib/xcode-installer/logout.rb +17 -0
- data/lib/xcode-installer/release-manager.rb +37 -0
- data/lib/xcode-installer/xcode-versions.rb +1 -36
- data/lib/xcode-installer/xcode-versions.yml +7 -0
- data/lib/xcode-installer.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
Y2VmNDYyM2JlYWVmMGM0NGQyNTY5Zjc1NmE1ZmJjZjEyM2VkY2YwZA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NmY3NzQyZDExOTU5OTIyY2I1NDRhNjYyYTJmMDk5NmM3ZWEyYjE5NQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MDE0ODU0YThhMjZhZDQxMDg1MzA5ZGYwNGM2NmU0ZWQ1ZGU5NmIyYzg2OGM0
|
10
|
+
ZWEzNDNhNDk1YTA0OTkxNGYxZjg1ZWM2MTliNGRlZGUzZTg5ZjI5N2MyYzNm
|
11
|
+
YjMxMDgxNWFjY2UyZWEzMDRmN2JiZTk5Yzc4YTE4NmViOTgzOTI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MmVmY2Q2NmZkZDBkNjIwMDE5NTQxZmE0ZDYwMGY0ODljYjMzZTA2MGI2Yjc3
|
14
|
+
ODhhOThhZGJjYmUyMWU0ZjA0ZGQ1OWYwMDY2NmFlYWM3ODNmYjNjYWE4ODEw
|
15
|
+
NDYxNjg4NTdlMjU0ZDVkNjE4MzU2MmVkYWE2YjViZTUyMjU2OWE=
|
data/HISTORY.md
CHANGED
data/bin/xcode-installer
CHANGED
@@ -24,5 +24,6 @@ global_option '--verbose'
|
|
24
24
|
require 'xcode-installer/apple-developer-center'
|
25
25
|
require 'xcode-installer/xcode-versions'
|
26
26
|
require 'xcode-installer/helpers'
|
27
|
+
require 'xcode-installer/release-manager'
|
27
28
|
require 'xcode-installer/agent'
|
28
29
|
require 'xcode-installer/commands'
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'xcode-installer/download'
|
2
|
+
|
1
3
|
command :'download' do |c|
|
2
4
|
c.syntax = 'xcode-installer download [options]'
|
3
5
|
c.option '--dry-run', 'Enables a HEAD request instead of downloading the file'
|
@@ -6,35 +8,5 @@ command :'download' do |c|
|
|
6
8
|
c.summary = 'Initiates the download'
|
7
9
|
c.description = ''
|
8
10
|
|
9
|
-
c.action
|
10
|
-
if options.release
|
11
|
-
xcode_version = options.release
|
12
|
-
else
|
13
|
-
if options.pre_release
|
14
|
-
xcode_version = XcodeInstaller::XcodeVersions::LATEST_DP
|
15
|
-
else
|
16
|
-
xcode_version = XcodeInstaller::XcodeVersions::LATEST_GA
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
mgr = XcodeInstaller::XcodeVersions::ReleaseManager.new
|
21
|
-
release = mgr.get_release(xcode_version, options.pre_release, 'gui')
|
22
|
-
|
23
|
-
if release
|
24
|
-
xcode_url = release['download_url']
|
25
|
-
else
|
26
|
-
puts "No Xcode release with number #{xcode_version}. Use the 'list' command to see a list of known releases."
|
27
|
-
exit
|
28
|
-
end
|
29
|
-
|
30
|
-
puts "Downloading Xcode #{xcode_version}"
|
31
|
-
puts xcode_url
|
32
|
-
|
33
|
-
agent.verbose = options.verbose
|
34
|
-
agent.dry_run = options.dry_run
|
35
|
-
try {
|
36
|
-
filename = agent.download(xcode_url)
|
37
|
-
puts "File saved to: #{Dir.pwd}/#{filename}" if filename
|
38
|
-
}
|
39
|
-
end
|
11
|
+
c.action XcodeInstaller::Download, :action
|
40
12
|
end
|
@@ -1,33 +1,12 @@
|
|
1
|
+
require 'xcode-installer/install'
|
2
|
+
|
1
3
|
command :'install' do |c|
|
2
4
|
c.syntax = 'xcode-installer install [options]'
|
3
5
|
c.option '--no-trash', 'Prevents trashing .dmg after install'
|
4
|
-
c.
|
6
|
+
c.option '--release STRING', 'Used to specify an old or pre-release version of Xcode. Otherwise, latest GA release of Xcode is downloaded.'
|
7
|
+
c.option '--pre-release', 'Specifies to download the latest pre-release version of Xcode.'
|
8
|
+
c.summary = 'Installs xcode from a .dmg file, downloading it if not already present'
|
5
9
|
c.description = 'NEEDS SOME WORK - Use download and mount .dmg manually for now'
|
6
10
|
|
7
|
-
c.action
|
8
|
-
files = Dir.glob('*.dmg')
|
9
|
-
if files.length == 0
|
10
|
-
puts 'No .dmg files found in current directory. Run the download command first.'
|
11
|
-
return
|
12
|
-
elsif files.length > 1
|
13
|
-
puts 'Multiple .dmg files found in the current directory. Currently no support for specifying file.'
|
14
|
-
return
|
15
|
-
end
|
16
|
-
dmg_file = files[0]
|
17
|
-
|
18
|
-
# Mount disk image
|
19
|
-
mountpoint = '/Volumes/Xcode'
|
20
|
-
# system "hdid '#{dmg_file}' -mountpoint #{mountpoint}"
|
21
|
-
system 'hdiutil attach -quiet xcode4620419895a.dmg'
|
22
|
-
|
23
|
-
# Trash existing install (so command is rerunnable)
|
24
|
-
destination = '/Applications/Xcode.app'
|
25
|
-
Trash.new.throw_out(destination)
|
26
|
-
|
27
|
-
# Copy into /Applications
|
28
|
-
puts 'Copying Xcode.app into Applications directory (this can take a little while)'
|
29
|
-
system "cp -R #{mountpoint}/Xcode.app #{destination}"
|
30
|
-
|
31
|
-
system 'hdiutil detach -quiet #{mountpoint}'
|
32
|
-
end
|
11
|
+
c.action XcodeInstaller::Install, :action
|
33
12
|
end
|
@@ -1,54 +1,9 @@
|
|
1
|
+
require 'xcode-installer/list'
|
2
|
+
|
1
3
|
command :'list' do |c|
|
2
4
|
c.syntax = 'xcode-installer list [all|gui|cli]'
|
3
5
|
c.summary = 'Lists the versions of Xcode available for downloading'
|
4
6
|
c.description = 'Shows only the Xcode GUI versions by default. Specify "all" or "cli" to show command-line tools'
|
5
7
|
|
6
|
-
c.action
|
7
|
-
# puts "args: #{args}"
|
8
|
-
|
9
|
-
show_all = args.include? 'all'
|
10
|
-
show_gui = args.include? 'gui'
|
11
|
-
show_cli = args.include? 'cli'
|
12
|
-
# Show GUI when no args given
|
13
|
-
show_gui = true if !show_all && !show_gui && !show_cli
|
14
|
-
|
15
|
-
# latest = XcodeInstaller::XcodeVersions::LATEST
|
16
|
-
mgr = XcodeInstaller::XcodeVersions::ReleaseManager.new
|
17
|
-
|
18
|
-
gui_versions = mgr.get_all('gui')
|
19
|
-
cli_versions = mgr.get_all('cli')
|
20
|
-
puts cli_versions
|
21
|
-
|
22
|
-
if show_all || show_gui
|
23
|
-
title = 'Xcode GUI'
|
24
|
-
table = Terminal::Table.new :title => title do |t|
|
25
|
-
t << ['Version', 'Download URL']
|
26
|
-
gui_versions.each do |release|
|
27
|
-
t << :separator
|
28
|
-
|
29
|
-
row = [release['version'], release['download_url']]
|
30
|
-
t << row
|
31
|
-
end
|
32
|
-
end
|
33
|
-
puts table
|
34
|
-
end
|
35
|
-
|
36
|
-
# Extra line between tables
|
37
|
-
puts if show_all
|
38
|
-
|
39
|
-
if show_all || show_cli
|
40
|
-
title = 'Xcode Command-Line'
|
41
|
-
table = Terminal::Table.new :title => title do |t|
|
42
|
-
t << ['Version', 'Download URL']
|
43
|
-
cli_versions.each do |release|
|
44
|
-
t << :separator
|
45
|
-
|
46
|
-
row = [release['version'], release['download_url']]
|
47
|
-
t << row
|
48
|
-
end
|
49
|
-
end
|
50
|
-
puts table
|
51
|
-
end
|
52
|
-
|
53
|
-
end
|
8
|
+
c.action XcodeInstaller::List, :action
|
54
9
|
end
|
@@ -1,16 +1,9 @@
|
|
1
|
+
require 'xcode-installer/login'
|
2
|
+
|
1
3
|
command :login do |c|
|
2
4
|
c.syntax = 'xcode-installer login'
|
3
5
|
c.summary = 'Save account credentials'
|
4
6
|
c.description = ''
|
5
7
|
|
6
|
-
c.action
|
7
|
-
say_warning "You are already authenticated" if Security::InternetPassword.find(:server => XcodeInstaller::AppleDeveloperCenter::HOST)
|
8
|
-
|
9
|
-
user = ask "Username:"
|
10
|
-
pass = password "Password:"
|
11
|
-
|
12
|
-
Security::InternetPassword.add(XcodeInstaller::AppleDeveloperCenter::HOST, user, pass)
|
13
|
-
|
14
|
-
say_ok "Account credentials saved"
|
15
|
-
end
|
8
|
+
c.action XcodeInstaller::Login, :action
|
16
9
|
end
|
@@ -1,13 +1,9 @@
|
|
1
|
+
require 'xcode-installer/logout'
|
2
|
+
|
1
3
|
command :logout do |c|
|
2
4
|
c.syntax = 'xcode-installer logout'
|
3
5
|
c.summary = 'Remove account credentials'
|
4
6
|
c.description = ''
|
5
7
|
|
6
|
-
c.action
|
7
|
-
say_error "You are not authenticated" and abort unless Security::InternetPassword.find(:server => XcodeInstaller::AppleDeveloperCenter::HOST)
|
8
|
-
|
9
|
-
Security::InternetPassword.delete(:server => XcodeInstaller::AppleDeveloperCenter::HOST)
|
10
|
-
|
11
|
-
say_ok "Account credentials removed"
|
12
|
-
end
|
8
|
+
c.action XcodeInstaller::Logout, :action
|
13
9
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#
|
2
|
+
# XcodeInstaller::Download (Command Class)
|
3
|
+
#
|
4
|
+
# Determines the requested Xcode release (with the help of the ReleaseManager class) and handles the download, showing a progress bar with ETA.
|
5
|
+
#
|
6
|
+
|
7
|
+
module XcodeInstaller
|
8
|
+
class Download
|
9
|
+
attr_accessor :release
|
10
|
+
|
11
|
+
def action(args, options)
|
12
|
+
mgr = XcodeInstaller::ReleaseManager.new
|
13
|
+
@release = mgr.get_release(options.release, options.pre_release)
|
14
|
+
|
15
|
+
if release
|
16
|
+
xcode_url = @release['download_url']
|
17
|
+
else
|
18
|
+
puts "No Xcode release with number #{options.release}. Use the 'list' command to see a list of known releases."
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
|
22
|
+
puts "Downloading Xcode #{@release['version']}"
|
23
|
+
puts xcode_url
|
24
|
+
|
25
|
+
agent.verbose = options.verbose
|
26
|
+
agent.dry_run = options.dry_run
|
27
|
+
try {
|
28
|
+
filename = agent.download(xcode_url)
|
29
|
+
puts "File saved to: #{Dir.pwd}/#{filename}" if filename
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,530 @@
|
|
1
|
+
#
|
2
|
+
# XcodeInstaller::Install (Command Class)
|
3
|
+
#
|
4
|
+
# First, this command will look for the expected version of the Xcode installation .dmg file in the download (current) dir.
|
5
|
+
# If not found, the download will be initiated, afterwards the expected file will be verified.
|
6
|
+
# Installation will continue once the expected .dmg file is found. The .dmg will be mounted and the .app file copied out.
|
7
|
+
# By default, the version number will be inserted into the Xcode.app name. This will prevent collision with the Mac App Store
|
8
|
+
# installed version. An option may allow for this utility to overwrite the Xcode.app so that there is only a single copy installed.
|
9
|
+
# It would be nice to also set the newly installed Xcode to be the default on the command line using 'xcode-select --switch'.
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'xcode-installer/download'
|
13
|
+
require 'ruby-progressbar'
|
14
|
+
|
15
|
+
module XcodeInstaller
|
16
|
+
class Install
|
17
|
+
attr_accessor :release, :version_suffix, :copied_kb, :copied_file_count, :progress_bar
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@copied_kb = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def action(args, options)
|
24
|
+
mgr = XcodeInstaller::ReleaseManager.new
|
25
|
+
@release = mgr.get_release(options.release, options.pre_release)
|
26
|
+
@version_suffix = "-#{@release['version']}"
|
27
|
+
|
28
|
+
files = Dir.glob(dmg_file_name)
|
29
|
+
if files.length == 0
|
30
|
+
puts '#{dmg_file_name} file not found in current directory. Run the download command first.'
|
31
|
+
return
|
32
|
+
elsif files.length > 1
|
33
|
+
puts 'Multiple #{dmg_file_name} files found in the current directory. Is this partition formatted with a case-insensitive disk format?'
|
34
|
+
return
|
35
|
+
end
|
36
|
+
dmg_file = files[0]
|
37
|
+
# TODO: if verbose...
|
38
|
+
# puts dmg_file
|
39
|
+
|
40
|
+
# Mount disk image
|
41
|
+
mountpoint = '/Volumes/Xcode'
|
42
|
+
# system "hdid '#{dmg_file}' -mountpoint #{mountpoint}"
|
43
|
+
system "hdiutil attach -quiet #{dmg_file}"
|
44
|
+
|
45
|
+
# Trash existing install (so command is rerunnable)
|
46
|
+
destination = "/Applications/Xcode#{version_suffix}.app"
|
47
|
+
Trash.new.throw_out(destination)
|
48
|
+
|
49
|
+
# TODO: Dynamically determine .app file name (DP releases have the version embedded)
|
50
|
+
copy("#{mountpoint}/Xcode.app", destination)
|
51
|
+
|
52
|
+
# system "hdiutil detach -quiet #{mountpoint}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def dmg_file_name
|
56
|
+
return File.basename(@release['download_url'])
|
57
|
+
end
|
58
|
+
|
59
|
+
def copy(source_path, destination_path)
|
60
|
+
# Copy into /Applications
|
61
|
+
# puts 'Copying Xcode.app into Applications directory (this can take a little while)'
|
62
|
+
# TODO: wrap debug output with verbose checks
|
63
|
+
puts "#{source_path} -> #{destination_path}"
|
64
|
+
# system "cp -R #{source_path} #{destination_path}"
|
65
|
+
|
66
|
+
total_kb = dir_size(source_path)
|
67
|
+
# puts total_kb
|
68
|
+
|
69
|
+
# puts File.stat(source_path).size
|
70
|
+
|
71
|
+
@progress_bar = ProgressBar.create(:title => "Copying", :starting_at => 0, :total => @release['app_size_extracted'])
|
72
|
+
@copied_file_count = 0
|
73
|
+
cp_r(source_path, destination_path, {})
|
74
|
+
@progress_bar.finish
|
75
|
+
|
76
|
+
# block_size = File.stat(source_path).blksize
|
77
|
+
# puts @copied_kb
|
78
|
+
|
79
|
+
# in_name = "src_file.txt"
|
80
|
+
# out_name = "dest_file.txt"
|
81
|
+
|
82
|
+
# in_file = File.new(in_name, "r")
|
83
|
+
# out_file = File.new(out_name, "w")
|
84
|
+
|
85
|
+
# in_size = File.size(in_name)
|
86
|
+
# batch_bytes = ( in_size / 100 ).ceil
|
87
|
+
# total = 0
|
88
|
+
# p_bar = ProgressBar.new('Copying', 100)
|
89
|
+
|
90
|
+
# buffer = in_file.sysread(batch_bytes)
|
91
|
+
# while total < in_size do
|
92
|
+
# out_file.syswrite(buffer)
|
93
|
+
# p_bar.inc
|
94
|
+
# total += batch_bytes
|
95
|
+
# if (in_size - total) < batch_bytes
|
96
|
+
# batch_bytes = (in_size - total)
|
97
|
+
# end
|
98
|
+
# buffer = in_file.sysread(batch_bytes)
|
99
|
+
# end
|
100
|
+
# p_bar.finish
|
101
|
+
end
|
102
|
+
|
103
|
+
# Exmaple output of the du command:
|
104
|
+
# 2359828 /Volumes/Xcode/Xcode.app/
|
105
|
+
def dir_size(path)
|
106
|
+
output = `du -sk '#{path}' 2> /dev/null`
|
107
|
+
return output.split(" ").first.to_i * 1024
|
108
|
+
end
|
109
|
+
|
110
|
+
def accumulate_kbytes(path)
|
111
|
+
# @progress_bar.log path
|
112
|
+
# @progress_bar.increment
|
113
|
+
# @copied_kb += File.stat(path).size if File.exists?(path)
|
114
|
+
@copied_file_count++
|
115
|
+
if @copied_file_count.modulo(10000) == 0
|
116
|
+
@progress_bar.progress = dir_size("/Applications/Xcode-5.app")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
##########################################################################
|
121
|
+
# #
|
122
|
+
# The following code was copied out of fileutils.rb from ruby 1.9.3-p392 #
|
123
|
+
# #
|
124
|
+
##########################################################################
|
125
|
+
|
126
|
+
def cp_r(src, dest, options = {})
|
127
|
+
# fu_check_options options, OPT_TABLE['cp_r']
|
128
|
+
# fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
|
129
|
+
return if options[:noop]
|
130
|
+
options = options.dup
|
131
|
+
options[:dereference_root] = true unless options.key?(:dereference_root)
|
132
|
+
fu_each_src_dest(src, dest) do |s, d|
|
133
|
+
copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
|
138
|
+
Entry_.new(src, nil, dereference_root).traverse do |ent|
|
139
|
+
destent = Entry_.new(dest, ent.rel, false)
|
140
|
+
|
141
|
+
# puts "#{dir_size(ent.path)} #{ent.path}"
|
142
|
+
accumulate_kbytes(ent.path)
|
143
|
+
|
144
|
+
File.unlink destent.path if remove_destination && File.file?(destent.path)
|
145
|
+
ent.copy destent.path
|
146
|
+
ent.copy_metadata destent.path if preserve
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def fu_each_src_dest(src, dest)
|
151
|
+
fu_each_src_dest0(src, dest) do |s, d|
|
152
|
+
raise ArgumentError, "same file: #{s} and #{d}" if File.identical?(s, d)
|
153
|
+
yield s, d, File.stat(s)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def fu_each_src_dest0(src, dest)
|
158
|
+
if tmp = Array.try_convert(src)
|
159
|
+
tmp.each do |s|
|
160
|
+
s = File.path(s)
|
161
|
+
yield s, File.join(dest, File.basename(s))
|
162
|
+
end
|
163
|
+
else
|
164
|
+
src = File.path(src)
|
165
|
+
if File.directory?(dest)
|
166
|
+
yield src, File.join(dest, File.basename(src))
|
167
|
+
else
|
168
|
+
yield src, File.path(dest)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end # class Install
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
module StreamUtils_
|
178
|
+
private
|
179
|
+
|
180
|
+
def fu_windows?
|
181
|
+
/mswin|mingw|bccwin|emx/ =~ RUBY_PLATFORM
|
182
|
+
end
|
183
|
+
|
184
|
+
def fu_copy_stream0(src, dest, blksize = nil) #:nodoc:
|
185
|
+
IO.copy_stream(src, dest)
|
186
|
+
end
|
187
|
+
|
188
|
+
def fu_stream_blksize(*streams)
|
189
|
+
streams.each do |s|
|
190
|
+
next unless s.respond_to?(:stat)
|
191
|
+
size = fu_blksize(s.stat)
|
192
|
+
return size if size
|
193
|
+
end
|
194
|
+
fu_default_blksize()
|
195
|
+
end
|
196
|
+
|
197
|
+
def fu_blksize(st)
|
198
|
+
s = st.blksize
|
199
|
+
return nil unless s
|
200
|
+
return nil if s == 0
|
201
|
+
s
|
202
|
+
end
|
203
|
+
|
204
|
+
def fu_default_blksize
|
205
|
+
1024
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
include StreamUtils_
|
210
|
+
extend StreamUtils_
|
211
|
+
|
212
|
+
class Entry_ #:nodoc: internal use only
|
213
|
+
include StreamUtils_
|
214
|
+
|
215
|
+
def initialize(a, b = nil, deref = false)
|
216
|
+
@prefix = @rel = @path = nil
|
217
|
+
if b
|
218
|
+
@prefix = a
|
219
|
+
@rel = b
|
220
|
+
else
|
221
|
+
@path = a
|
222
|
+
end
|
223
|
+
@deref = deref
|
224
|
+
@stat = nil
|
225
|
+
@lstat = nil
|
226
|
+
end
|
227
|
+
|
228
|
+
def inspect
|
229
|
+
"\#<#{self.class} #{path()}>"
|
230
|
+
end
|
231
|
+
|
232
|
+
def path
|
233
|
+
if @path
|
234
|
+
File.path(@path)
|
235
|
+
else
|
236
|
+
join(@prefix, @rel)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def prefix
|
241
|
+
@prefix || @path
|
242
|
+
end
|
243
|
+
|
244
|
+
def rel
|
245
|
+
@rel
|
246
|
+
end
|
247
|
+
|
248
|
+
def dereference?
|
249
|
+
@deref
|
250
|
+
end
|
251
|
+
|
252
|
+
def exist?
|
253
|
+
lstat! ? true : false
|
254
|
+
end
|
255
|
+
|
256
|
+
def file?
|
257
|
+
s = lstat!
|
258
|
+
s and s.file?
|
259
|
+
end
|
260
|
+
|
261
|
+
def directory?
|
262
|
+
s = lstat!
|
263
|
+
s and s.directory?
|
264
|
+
end
|
265
|
+
|
266
|
+
def symlink?
|
267
|
+
s = lstat!
|
268
|
+
s and s.symlink?
|
269
|
+
end
|
270
|
+
|
271
|
+
def chardev?
|
272
|
+
s = lstat!
|
273
|
+
s and s.chardev?
|
274
|
+
end
|
275
|
+
|
276
|
+
def blockdev?
|
277
|
+
s = lstat!
|
278
|
+
s and s.blockdev?
|
279
|
+
end
|
280
|
+
|
281
|
+
def socket?
|
282
|
+
s = lstat!
|
283
|
+
s and s.socket?
|
284
|
+
end
|
285
|
+
|
286
|
+
def pipe?
|
287
|
+
s = lstat!
|
288
|
+
s and s.pipe?
|
289
|
+
end
|
290
|
+
|
291
|
+
S_IF_DOOR = 0xD000
|
292
|
+
|
293
|
+
def door?
|
294
|
+
s = lstat!
|
295
|
+
s and (s.mode & 0xF000 == S_IF_DOOR)
|
296
|
+
end
|
297
|
+
|
298
|
+
def entries
|
299
|
+
opts = {}
|
300
|
+
opts[:encoding] = ::Encoding::UTF_8 if fu_windows?
|
301
|
+
Dir.entries(path(), opts)\
|
302
|
+
.reject {|n| n == '.' or n == '..' }\
|
303
|
+
.map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
|
304
|
+
end
|
305
|
+
|
306
|
+
def stat
|
307
|
+
return @stat if @stat
|
308
|
+
if lstat() and lstat().symlink?
|
309
|
+
@stat = File.stat(path())
|
310
|
+
else
|
311
|
+
@stat = lstat()
|
312
|
+
end
|
313
|
+
@stat
|
314
|
+
end
|
315
|
+
|
316
|
+
def stat!
|
317
|
+
return @stat if @stat
|
318
|
+
if lstat! and lstat!.symlink?
|
319
|
+
@stat = File.stat(path())
|
320
|
+
else
|
321
|
+
@stat = lstat!
|
322
|
+
end
|
323
|
+
@stat
|
324
|
+
rescue SystemCallError
|
325
|
+
nil
|
326
|
+
end
|
327
|
+
|
328
|
+
def lstat
|
329
|
+
if dereference?
|
330
|
+
@lstat ||= File.stat(path())
|
331
|
+
else
|
332
|
+
@lstat ||= File.lstat(path())
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def lstat!
|
337
|
+
lstat()
|
338
|
+
rescue SystemCallError
|
339
|
+
nil
|
340
|
+
end
|
341
|
+
|
342
|
+
def chmod(mode)
|
343
|
+
if symlink?
|
344
|
+
File.lchmod mode, path() if have_lchmod?
|
345
|
+
else
|
346
|
+
File.chmod mode, path()
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def chown(uid, gid)
|
351
|
+
if symlink?
|
352
|
+
File.lchown uid, gid, path() if have_lchown?
|
353
|
+
else
|
354
|
+
File.chown uid, gid, path()
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def copy(dest)
|
359
|
+
case
|
360
|
+
when file?
|
361
|
+
copy_file dest
|
362
|
+
when directory?
|
363
|
+
if !File.exist?(dest) and descendant_diretory?(dest, path)
|
364
|
+
raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest]
|
365
|
+
end
|
366
|
+
begin
|
367
|
+
Dir.mkdir dest
|
368
|
+
rescue
|
369
|
+
raise unless File.directory?(dest)
|
370
|
+
end
|
371
|
+
when symlink?
|
372
|
+
File.symlink File.readlink(path()), dest
|
373
|
+
when chardev?
|
374
|
+
raise "cannot handle device file" unless File.respond_to?(:mknod)
|
375
|
+
mknod dest, ?c, 0666, lstat().rdev
|
376
|
+
when blockdev?
|
377
|
+
raise "cannot handle device file" unless File.respond_to?(:mknod)
|
378
|
+
mknod dest, ?b, 0666, lstat().rdev
|
379
|
+
when socket?
|
380
|
+
raise "cannot handle socket" unless File.respond_to?(:mknod)
|
381
|
+
mknod dest, nil, lstat().mode, 0
|
382
|
+
when pipe?
|
383
|
+
raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
|
384
|
+
mkfifo dest, 0666
|
385
|
+
when door?
|
386
|
+
raise "cannot handle door: #{path()}"
|
387
|
+
else
|
388
|
+
raise "unknown file type: #{path()}"
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def copy_file(dest)
|
393
|
+
File.open(path()) do |s|
|
394
|
+
File.open(dest, 'wb', s.stat.mode) do |f|
|
395
|
+
IO.copy_stream(s, f)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def copy_metadata(path)
|
401
|
+
st = lstat()
|
402
|
+
File.utime st.atime, st.mtime, path
|
403
|
+
begin
|
404
|
+
File.chown st.uid, st.gid, path
|
405
|
+
rescue Errno::EPERM
|
406
|
+
# clear setuid/setgid
|
407
|
+
File.chmod st.mode & 01777, path
|
408
|
+
else
|
409
|
+
File.chmod st.mode, path
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def remove
|
414
|
+
if directory?
|
415
|
+
remove_dir1
|
416
|
+
else
|
417
|
+
remove_file
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def remove_dir1
|
422
|
+
platform_support {
|
423
|
+
Dir.rmdir path().chomp(?/)
|
424
|
+
}
|
425
|
+
end
|
426
|
+
|
427
|
+
def remove_file
|
428
|
+
platform_support {
|
429
|
+
File.unlink path
|
430
|
+
}
|
431
|
+
end
|
432
|
+
|
433
|
+
def platform_support
|
434
|
+
return yield unless fu_windows?
|
435
|
+
first_time_p = true
|
436
|
+
begin
|
437
|
+
yield
|
438
|
+
rescue Errno::ENOENT
|
439
|
+
raise
|
440
|
+
rescue => err
|
441
|
+
if first_time_p
|
442
|
+
first_time_p = false
|
443
|
+
begin
|
444
|
+
File.chmod 0700, path() # Windows does not have symlink
|
445
|
+
retry
|
446
|
+
rescue SystemCallError
|
447
|
+
end
|
448
|
+
end
|
449
|
+
raise err
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
def preorder_traverse
|
454
|
+
stack = [self]
|
455
|
+
while ent = stack.pop
|
456
|
+
yield ent
|
457
|
+
stack.concat ent.entries.reverse if ent.directory?
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
alias traverse preorder_traverse
|
462
|
+
|
463
|
+
def postorder_traverse
|
464
|
+
if directory?
|
465
|
+
entries().each do |ent|
|
466
|
+
ent.postorder_traverse do |e|
|
467
|
+
yield e
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
yield self
|
472
|
+
end
|
473
|
+
|
474
|
+
private
|
475
|
+
|
476
|
+
$fileutils_rb_have_lchmod = nil
|
477
|
+
|
478
|
+
def have_lchmod?
|
479
|
+
# This is not MT-safe, but it does not matter.
|
480
|
+
if $fileutils_rb_have_lchmod == nil
|
481
|
+
$fileutils_rb_have_lchmod = check_have_lchmod?
|
482
|
+
end
|
483
|
+
$fileutils_rb_have_lchmod
|
484
|
+
end
|
485
|
+
|
486
|
+
def check_have_lchmod?
|
487
|
+
return false unless File.respond_to?(:lchmod)
|
488
|
+
File.lchmod 0
|
489
|
+
return true
|
490
|
+
rescue NotImplementedError
|
491
|
+
return false
|
492
|
+
end
|
493
|
+
|
494
|
+
$fileutils_rb_have_lchown = nil
|
495
|
+
|
496
|
+
def have_lchown?
|
497
|
+
# This is not MT-safe, but it does not matter.
|
498
|
+
if $fileutils_rb_have_lchown == nil
|
499
|
+
$fileutils_rb_have_lchown = check_have_lchown?
|
500
|
+
end
|
501
|
+
$fileutils_rb_have_lchown
|
502
|
+
end
|
503
|
+
|
504
|
+
def check_have_lchown?
|
505
|
+
return false unless File.respond_to?(:lchown)
|
506
|
+
File.lchown nil, nil
|
507
|
+
return true
|
508
|
+
rescue NotImplementedError
|
509
|
+
return false
|
510
|
+
end
|
511
|
+
|
512
|
+
def join(dir, base)
|
513
|
+
return File.path(dir) if not base or base == '.'
|
514
|
+
return File.path(base) if not dir or dir == '.'
|
515
|
+
File.join(dir, base)
|
516
|
+
end
|
517
|
+
|
518
|
+
if File::ALT_SEPARATOR
|
519
|
+
DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)".freeze
|
520
|
+
else
|
521
|
+
DIRECTORY_TERM = "(?=/|\\z)".freeze
|
522
|
+
end
|
523
|
+
SYSCASE = File::FNM_SYSCASE.nonzero? ? "-i" : ""
|
524
|
+
|
525
|
+
def descendant_diretory?(descendant, ascendant)
|
526
|
+
/\A(?#{SYSCASE}:#{Regexp.quote(ascendant)})#{DIRECTORY_TERM}/ =~ File.dirname(descendant)
|
527
|
+
end
|
528
|
+
end # class Entry_
|
529
|
+
|
530
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#
|
2
|
+
# XcodeInstaller::List
|
3
|
+
#
|
4
|
+
|
5
|
+
module XcodeInstaller
|
6
|
+
class List
|
7
|
+
|
8
|
+
def action(args, options)
|
9
|
+
|
10
|
+
show_all = args.include? 'all'
|
11
|
+
show_gui = args.include? 'gui'
|
12
|
+
show_cli = args.include? 'cli'
|
13
|
+
# Show GUI when no args given
|
14
|
+
show_gui = true if !show_all && !show_gui && !show_cli
|
15
|
+
|
16
|
+
mgr = XcodeInstaller::ReleaseManager.new
|
17
|
+
|
18
|
+
gui_versions = mgr.get_all('gui')
|
19
|
+
cli_versions = mgr.get_all('cli')
|
20
|
+
|
21
|
+
if show_all || show_gui
|
22
|
+
title = 'Xcode GUI'
|
23
|
+
table = Terminal::Table.new :title => title do |t|
|
24
|
+
t << ['Version', 'Download URL']
|
25
|
+
gui_versions.each do |release|
|
26
|
+
t << :separator
|
27
|
+
|
28
|
+
row = [release['version'], release['download_url']]
|
29
|
+
t << row
|
30
|
+
end
|
31
|
+
end
|
32
|
+
puts table
|
33
|
+
end
|
34
|
+
|
35
|
+
# Extra line between tables
|
36
|
+
puts if show_all
|
37
|
+
|
38
|
+
if show_all || show_cli
|
39
|
+
title = 'Xcode Command-Line'
|
40
|
+
table = Terminal::Table.new :title => title do |t|
|
41
|
+
t << ['Version', 'Download URL']
|
42
|
+
cli_versions.each do |release|
|
43
|
+
t << :separator
|
44
|
+
|
45
|
+
row = [release['version'], release['download_url']]
|
46
|
+
t << row
|
47
|
+
end
|
48
|
+
end
|
49
|
+
puts table
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#
|
2
|
+
# XcodeInstaller::Login
|
3
|
+
#
|
4
|
+
|
5
|
+
module XcodeInstaller
|
6
|
+
class Login
|
7
|
+
|
8
|
+
def action(args, options)
|
9
|
+
say_warning "You are already authenticated" if Security::InternetPassword.find(:server => XcodeInstaller::AppleDeveloperCenter::HOST)
|
10
|
+
|
11
|
+
user = ask "Username:"
|
12
|
+
pass = password "Password:"
|
13
|
+
|
14
|
+
Security::InternetPassword.add(XcodeInstaller::AppleDeveloperCenter::HOST, user, pass)
|
15
|
+
|
16
|
+
say_ok "Account credentials saved"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# XcodeInstaller::Logout
|
3
|
+
#
|
4
|
+
|
5
|
+
module XcodeInstaller
|
6
|
+
class Logout
|
7
|
+
|
8
|
+
def action(args, options)
|
9
|
+
say_error "You are not authenticated" and abort unless Security::InternetPassword.find(:server => XcodeInstaller::AppleDeveloperCenter::HOST)
|
10
|
+
|
11
|
+
Security::InternetPassword.delete(:server => XcodeInstaller::AppleDeveloperCenter::HOST)
|
12
|
+
|
13
|
+
say_ok "Account credentials removed"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module XcodeInstaller
|
4
|
+
class ReleaseManager
|
5
|
+
attr_accessor :data
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
@data = YAML::load_file(File.join(File.dirname(File.expand_path(__FILE__)), 'xcode-versions.yml'))
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_all(interface_type)
|
13
|
+
interface_type ||= 'gui'
|
14
|
+
list = data[interface_type]
|
15
|
+
return list
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_release(version, include_beta)
|
19
|
+
version ||= 'latest'
|
20
|
+
include_beta ||= false
|
21
|
+
interface_type ||= 'gui'
|
22
|
+
|
23
|
+
list = data[interface_type]
|
24
|
+
if version == 'latest' && include_beta
|
25
|
+
version = LATEST_DP
|
26
|
+
elsif version == 'latest'
|
27
|
+
version = LATEST_GA
|
28
|
+
end
|
29
|
+
list.each { |release|
|
30
|
+
if release['version'] == version
|
31
|
+
return release
|
32
|
+
end
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -1,45 +1,10 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
1
|
module XcodeInstaller
|
4
2
|
module XcodeVersions
|
5
3
|
|
6
4
|
# General availability
|
7
|
-
LATEST_GA = '5'
|
5
|
+
LATEST_GA = '5.0.1'
|
8
6
|
# Developer preview
|
9
7
|
LATEST_DP = '5-GM'
|
10
8
|
|
11
|
-
class ReleaseManager
|
12
|
-
attr_accessor :data
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
super
|
16
|
-
@data = YAML::load_file(File.join(File.dirname(File.expand_path(__FILE__)), 'xcode-versions.yml'))
|
17
|
-
end
|
18
|
-
|
19
|
-
def get_all(interface_type)
|
20
|
-
interface_type ||= 'gui'
|
21
|
-
list = data[interface_type]
|
22
|
-
return list
|
23
|
-
end
|
24
|
-
|
25
|
-
def get_release(version, include_beta, interface_type)
|
26
|
-
version ||= 'latest'
|
27
|
-
include_beta ||= false
|
28
|
-
interface_type ||= 'gui'
|
29
|
-
|
30
|
-
list = data[interface_type]
|
31
|
-
if version == 'latest' && include_beta
|
32
|
-
version = LATEST_DP
|
33
|
-
elsif version == 'latest'
|
34
|
-
version = LATEST_GA
|
35
|
-
end
|
36
|
-
list.each { |release|
|
37
|
-
if release['version'] == version
|
38
|
-
return release
|
39
|
-
end
|
40
|
-
}
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
9
|
end
|
45
10
|
end
|
@@ -1,5 +1,12 @@
|
|
1
1
|
---
|
2
2
|
gui:
|
3
|
+
- version: '5.0.1'
|
4
|
+
download_url: https://developer.apple.com/downloads/download.action?path=Developer_Tools/xcode_5.0.1/xcode_5.0.1.dmg
|
5
|
+
release_date: 2013-10-22
|
6
|
+
interface_type: gui
|
7
|
+
size: 2194212232
|
8
|
+
last_modified: 'Tue, 22 Oct 2013 23:43:12 GMT'
|
9
|
+
product_build_version: 5A2053
|
3
10
|
- version: '5'
|
4
11
|
download_url: https://developer.apple.com/downloads/download.action?path=Developer_Tools/xcode_5/xcode_5.dmg
|
5
12
|
release_date: 2013-09-18
|
data/lib/xcode-installer.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xcode-installer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Chatelain
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: commander
|
@@ -146,7 +146,13 @@ files:
|
|
146
146
|
- ./lib/xcode-installer/commands/logout.rb
|
147
147
|
- ./lib/xcode-installer/commands.rb
|
148
148
|
- ./lib/xcode-installer/credentials.rb
|
149
|
+
- ./lib/xcode-installer/download.rb
|
149
150
|
- ./lib/xcode-installer/helpers.rb
|
151
|
+
- ./lib/xcode-installer/install.rb
|
152
|
+
- ./lib/xcode-installer/list.rb
|
153
|
+
- ./lib/xcode-installer/login.rb
|
154
|
+
- ./lib/xcode-installer/logout.rb
|
155
|
+
- ./lib/xcode-installer/release-manager.rb
|
150
156
|
- ./lib/xcode-installer/xcode-versions.rb
|
151
157
|
- ./lib/xcode-installer/xcode-versions.yml
|
152
158
|
- ./lib/xcode-installer.rb
|