class XcodeInstall::Installer
rubocop:disable Metrics/ClassLength
Constants
- LIST_FILE
- MINIMUM_VERSION
- SYMLINK_PATH
Attributes
xcodes[R]
Public Class Methods
new()
click to toggle source
# File lib/xcode/install.rb, line 124 def initialize FileUtils.mkdir_p(CACHE_DIR) end
Public Instance Methods
cache_dir()
click to toggle source
# File lib/xcode/install.rb, line 128 def cache_dir CACHE_DIR end
current_symlink()
click to toggle source
# File lib/xcode/install.rb, line 132 def current_symlink File.symlink?(SYMLINK_PATH) ? SYMLINK_PATH : nil end
download(version, progress, url = nil, progress_block = nil)
click to toggle source
# File lib/xcode/install.rb, line 136 def download(version, progress, url = nil, progress_block = nil) xcode = find_xcode_version(version) if url.nil? return if url.nil? && xcode.nil? dmg_file = Pathname.new(File.basename(url || xcode.path)) result = Curl.new.fetch( url: url || xcode.url, directory: CACHE_DIR, cookies: url ? nil : spaceship.cookie, output: dmg_file, progress: progress, progress_block: progress_block ) result ? CACHE_DIR + dmg_file : nil end
exist?(version)
click to toggle source
# File lib/xcode/install.rb, line 174 def exist?(version) return true if find_xcode_version(version) false end
find_xcode_version(version)
click to toggle source
# File lib/xcode/install.rb, line 153 def find_xcode_version(version) # By checking for the name and the version we have the best success rate # Sometimes the user might pass # "4.3 for Lion" # or they might pass an actual Gem::Version # Gem::Version.new("8.0.0") # which should automatically match with "Xcode 8" begin parsed_version = Gem::Version.new(version) rescue ArgumentError nil end seedlist.each do |current_seed| return current_seed if current_seed.name == version return current_seed if parsed_version && current_seed.version == parsed_version end nil end
install_dmg(dmg_path, suffix = '', switch = true, clean = true)
click to toggle source
# File lib/xcode/install.rb, line 214 def install_dmg(dmg_path, suffix = '', switch = true, clean = true) prompt = "Please authenticate for Xcode installation.\nPassword: " xcode_path = "/Applications/Xcode#{suffix}.app" if dmg_path.extname == '.xip' `xip -x #{dmg_path}` xcode_orig_path = File.join(Dir.pwd, 'Xcode.app') xcode_beta_path = File.join(Dir.pwd, 'Xcode-beta.app') if Pathname.new(xcode_orig_path).exist? `sudo -p "#{prompt}" mv "#{xcode_orig_path}" "#{xcode_path}"` elsif Pathname.new(xcode_beta_path).exist? `sudo -p "#{prompt}" mv "#{xcode_beta_path}" "#{xcode_path}"` else out = <<-HELP No `Xcode.app(or Xcode-beta.app)` found in XIP. Please remove #{dmg_path} if you suspect a corrupted download or run `xcversion update` to see if the version you tried to install has been pulled by Apple. If none of this is true, please open a new GH issue. HELP $stderr.puts out.tr("\n", ' ') return end else mount_dir = mount(dmg_path) source = Dir.glob(File.join(mount_dir, 'Xcode*.app')).first if source.nil? out = <<-HELP No `Xcode.app` found in DMG. Please remove #{dmg_path} if you suspect a corrupted download or run `xcversion update` to see if the version you tried to install has been pulled by Apple. If none of this is true, please open a new GH issue. HELP $stderr.puts out.tr("\n", ' ') return end `sudo -p "#{prompt}" ditto "#{source}" "#{xcode_path}"` `umount "/Volumes/Xcode"` end xcode = InstalledXcode.new(xcode_path) unless xcode.verify_integrity `sudo rm -rf #{xcode_path}` return end enable_developer_mode xcode.approve_license xcode.install_components if switch `sudo rm -f #{SYMLINK_PATH}` unless current_symlink.nil? `sudo ln -sf #{xcode_path} #{SYMLINK_PATH}` unless SYMLINK_PATH.exist? `sudo xcode-select --switch #{xcode_path}` puts `xcodebuild -version` end FileUtils.rm_f(dmg_path) if clean end
install_version(version, switch = true, clean = true, install = true, progress = true, url = nil, show_release_notes = true, progress_block = nil)
click to toggle source
rubocop:disable Metrics/ParameterLists
# File lib/xcode/install.rb, line 277 def install_version(version, switch = true, clean = true, install = true, progress = true, url = nil, show_release_notes = true, progress_block = nil) dmg_path = get_dmg(version, progress, url, progress_block) fail Informative, "Failed to download Xcode #{version}." if dmg_path.nil? if install install_dmg(dmg_path, "-#{version.to_s.split(' ').join('.')}", switch, clean) else puts "Downloaded Xcode #{version} to '#{dmg_path}'" end open_release_notes_url(version) if show_release_notes && !url end
installed?(version)
click to toggle source
# File lib/xcode/install.rb, line 179 def installed?(version) installed_versions.map(&:version).include?(version) end
installed_versions()
click to toggle source
# File lib/xcode/install.rb, line 183 def installed_versions installed.map { |x| InstalledXcode.new(x) }.sort do |a, b| Gem::Version.new(a.version) <=> Gem::Version.new(b.version) end end
list()
click to toggle source
# File lib/xcode/install.rb, line 306 def list list_annotated(list_versions.sort_by(&:to_f)) end
list_annotated(xcodes_list)
click to toggle source
# File lib/xcode/install.rb, line 296 def list_annotated(xcodes_list) installed = installed_versions.map(&:version) xcodes_list.map do |x| xcode_version = x.split(' ').first # exclude "beta N", "for Lion". xcode_version << '.0' unless xcode_version.include?('.') installed.include?(xcode_version) ? "#{x} (installed)" : x end.join("\n") end
mount(dmg_path)
click to toggle source
# File lib/xcode/install.rb, line 324 def mount(dmg_path) plist = hdiutil('mount', '-plist', '-nobrowse', '-noverify', dmg_path.to_s) document = REXML::Document.new(plist) node = REXML::XPath.first(document, "//key[.='mount-point']/following-sibling::*[1]") fail Informative, 'Failed to mount image.' unless node node.text end
open_release_notes_url(version)
click to toggle source
# File lib/xcode/install.rb, line 290 def open_release_notes_url(version) return if version.nil? xcode = seedlist.find { |x| x.name == version } `open #{xcode.release_notes_url}` unless xcode.nil? || xcode.release_notes_url.nil? end
rm_list_cache()
click to toggle source
# File lib/xcode/install.rb, line 310 def rm_list_cache FileUtils.rm_f(LIST_FILE) end
seedlist()
click to toggle source
Returns an array of `XcodeInstall::Xcode`
<XcodeInstall::Xcode:0x007fa1d451c390 @date_modified=2015, @name="6.4", @path="/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg", @url= "https://developer.apple.com/devcenter/download.action?path=/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg", @version=Gem::Version.new("6.4")>,
the resulting list is sorted with the most recent release as first element
# File lib/xcode/install.rb, line 199 def seedlist @xcodes = Marshal.load(File.read(LIST_FILE)) if LIST_FILE.exist? && xcodes.nil? all_xcodes = (xcodes || fetch_seedlist) # We have to set the `installed` value here, as we might still use # the cached list of available Xcode versions, but have a new Xcode # installed in the mean-time cached_installed_versions = installed_versions.map(&:bundle_version) all_xcodes.each do |current_xcode| current_xcode.installed = cached_installed_versions.include?(current_xcode.version) end all_xcodes.sort_by(&:version) end
symlink(version)
click to toggle source
# File lib/xcode/install.rb, line 314 def symlink(version) xcode = installed_versions.find { |x| x.version == version } `sudo rm -f #{SYMLINK_PATH}` unless current_symlink.nil? `sudo ln -sf #{xcode.path} #{SYMLINK_PATH}` unless xcode.nil? || SYMLINK_PATH.exist? end
symlinks_to()
click to toggle source
# File lib/xcode/install.rb, line 320 def symlinks_to File.absolute_path(File.readlink(current_symlink), SYMLINK_PATH.dirname) if current_symlink end
Private Instance Methods
enable_developer_mode()
click to toggle source
# File lib/xcode/install.rb, line 358 def enable_developer_mode `sudo /usr/sbin/DevToolsSecurity -enable` `sudo /usr/sbin/dseditgroup -o edit -t group -a staff _developer` end
fetch_seedlist()
click to toggle source
# File lib/xcode/install.rb, line 377 def fetch_seedlist @xcodes = parse_seedlist(spaceship.send(:request, :post, '/services-account/QH65B2/downloadws/listDownloads.action').body) names = @xcodes.map(&:name) @xcodes += prereleases.reject { |pre| names.include?(pre.name) } File.open(LIST_FILE, 'wb') do |f| f << Marshal.dump(xcodes) end xcodes end
get_dmg(version, progress = true, url = nil, progress_block = nil)
click to toggle source
# File lib/xcode/install.rb, line 363 def get_dmg(version, progress = true, url = nil, progress_block = nil) if url path = Pathname.new(url) return path if path.exist? end if ENV.key?('XCODE_INSTALL_CACHE_DIR') Pathname.glob(ENV['XCODE_INSTALL_CACHE_DIR'] + '/*').each do |fpath| return fpath if /^xcode_#{version}\.dmg|xip$/ =~ fpath.basename.to_s end end download(version, progress, url, progress_block) end
hdiutil(*args)
click to toggle source
# File lib/xcode/install.rb, line 454 def hdiutil(*args) io = IO.popen(['hdiutil', *args]) result = io.read io.close unless $?.exitstatus.zero? file_path = args[-1] if `file -b #{file_path}`.start_with?('HTML') fail Informative, "Failed to mount #{file_path}, logging into your account from a browser should tell you what is going wrong." end fail Informative, 'Failed to invoke hdiutil.' end result end
installed()
click to toggle source
# File lib/xcode/install.rb, line 391 def installed result = `mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'" 2>/dev/null`.split("\n") if result.empty? result = `find /Applications -maxdepth 1 -name '*.app' -type d -exec sh -c \ 'if [ "$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" \ "{}/Contents/Info.plist" 2>/dev/null)" == "com.apple.dt.Xcode" ]; then echo "{}"; fi' ';'`.split("\n") end result end
list_versions()
click to toggle source
# File lib/xcode/install.rb, line 415 def list_versions seedlist.map(&:name) end
parse_seedlist(seedlist)
click to toggle source
# File lib/xcode/install.rb, line 401 def parse_seedlist(seedlist) fail Informative, seedlist['resultString'] unless seedlist['resultCode'].eql? 0 seeds = Array(seedlist['downloads']).select do |t| /^Xcode [0-9]/.match(t['name']) end xcodes = seeds.map { |x| Xcode.new(x) }.reject { |x| x.version < MINIMUM_VERSION }.sort do |a, b| a.date_modified <=> b.date_modified end xcodes.select { |x| x.url.end_with?('.dmg') || x.url.end_with?('.xip') } end
prereleases()
click to toggle source
# File lib/xcode/install.rb, line 419 def prereleases body = spaceship.send(:request, :get, '/download/').body links = body.scan(%r{<a.+?href="(.+?/Xcode.+?/Xcode_(.+?)\.(dmg|xip))".*>(.*)</a>}) links = links.map do |link| parent = link[0].scan(%r{path=(/.*/.*/)}).first.first match = body.scan(/#{Regexp.quote(parent)}(.+?.pdf)/).first if match link + [parent + match.first] else link + [nil] end end links = links.map { |pre| Xcode.new_prerelease(pre[1].strip.tr('_', ' '), pre[0], pre[4]) } if links.count.zero? rg = %r{platform-title.*Xcode.* beta.*<\/p>} scan = body.scan(rg) if scan.count.zero? rg = %r{Xcode.* GM.*<\/p>} scan = body.scan(rg) end return [] if scan.empty? version = scan.first.gsub(/<.*?>/, '').gsub(/.*Xcode /, '') link = body.scan(%r{<button .*"(.+?.(dmg|xip))".*</button>}).first.first notes = body.scan(%r{<a.+?href="(/go/\?id=xcode-.+?)".*>(.*)</a>}).first.first links << Xcode.new(version, link, notes) end links end
spaceship()
click to toggle source
# File lib/xcode/install.rb, line 334 def spaceship @spaceship ||= begin begin Spaceship.login(ENV['XCODE_INSTALL_USER'], ENV['XCODE_INSTALL_PASSWORD']) rescue Spaceship::Client::InvalidUserCredentialsError raise 'The specified Apple developer account credentials are incorrect.' rescue Spaceship::Client::NoUserCredentialsError raise <<-HELP Please provide your Apple developer account credentials via the XCODE_INSTALL_USER and XCODE_INSTALL_PASSWORD environment variables. HELP end if ENV.key?('XCODE_INSTALL_TEAM_ID') Spaceship.client.team_id = ENV['XCODE_INSTALL_TEAM_ID'] end Spaceship.client end end