xcode-installer 0.1.9 → 0.2.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 +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
|