ruboto/ruboto

View on GitHub
lib/ruboto/util/setup.rb

Summary

Maintainability
F
1 wk
Test Coverage
require 'ruboto/sdk_versions'
require 'ruboto/util/verify'

module Ruboto
  module Util
    module Setup
      include Ruboto::Util::Verify
      REPOSITORY_BASE = 'http://dl-ssl.google.com/android/repository'
      REPOSITORY_URL = "#{REPOSITORY_BASE}/repository-10.xml"
      ADDONS_URL = "#{REPOSITORY_BASE}/extras/intel/addon.xml"
      SDK_DOWNLOAD_PAGE = 'https://developer.android.com/studio/index.html'

      RUBOTO_GEM_ROOT = File.expand_path '../../../..', __FILE__
      WINDOWS_ELEVATE_CMD = "#{RUBOTO_GEM_ROOT}/bin/elevate_32.exe -c -w"

      #########################################
      #
      # Core Set up Method
      #

      def setup_ruboto(accept_all, api_levels = [SdkVersions::DEFAULT_TARGET_SDK], upgrade_haxm = false)
        @platform_sdk_loc = {}
        api_levels = [project_api_level, *api_levels].compact.uniq
        install_all(accept_all, api_levels, upgrade_haxm) unless check_all(api_levels, upgrade_haxm)
        config_path(accept_all)
      end

      #
      # OS independent "which"
      # From: http://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby
      #
      def which(cmd)
        exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
        ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
          exts.each do |ext|
            exe = File.join(path, "#{cmd}#{ext}")
            if File.executable?(exe) and not File.directory?(exe)
              exe.gsub!('\\', '/') if windows?
              return exe
            end
          end
        end
        nil
      end

      private

      #########################################
      #
      # Utility Methods
      #

      MAC_OS_X = 'macosx'
      WINDOWS = 'windows'
      LINUX = 'linux'

      def android_package_os_id
        case RbConfig::CONFIG['host_os']
        when /^darwin(.*)/
          MAC_OS_X
        when /linux/
          LINUX
        when /^mswin32|windows|mingw32/
          WINDOWS
        else
          raise "Unkown operating system: #{RbConfig::CONFIG['host_os'].inspect}"
        end
      end

      def mac_os_x?
        android_package_os_id == MAC_OS_X
      end

      def windows?
        android_package_os_id == WINDOWS
      end

      def android_package_directory
        return ENV['ANDROID_SDK'] if ENV['ANDROID_SDK']
        return ENV['ANDROID_HOME'] if ENV['ANDROID_HOME']
        adb_location = which('adb')
        return File.dirname(File.dirname(Pathname.new(adb_location).realpath)) if adb_location
        search_path = windows? ?
            ['AppData/Local/Android/android-sdk'] :
            %W(Library/Android/sdk android-sdk-#{android_package_os_id} android-sdk)
        sdk_dir = search_path.find{|path| File.exist? File.join(File.expand_path('~'), path)}
        File.join File.expand_path('~'), sdk_dir || search_path[0]
      end

      def android_haxm_directory
        Dir[File.join(android_package_directory, 'extras', 'intel', 'Hardware_Accelerated_Execution_Manager')][0]
      end

      def package_installer
        case android_package_os_id
        when LINUX
          which('apt-get') or which('yum')
        else
          ## Error
          nil
        end
      end

      def path_setup_file
        case android_package_os_id
        when MAC_OS_X
          '.profile'
        when LINUX
          '.bashrc'
        when WINDOWS
          ## Error
          'windows'
        else
          ## Error
          nil
        end
      end

      def get_tools_version(type='tool', repo_url = REPOSITORY_URL, host_os = nil)
        require 'rexml/document'
        require 'open-uri'

        doc = REXML::Document.new(open(repo_url))
        doc.root.elements.to_a("sdk:#{type}").map do |t|
          r = t.elements['sdk:revision']
          major = r.elements['sdk:major']
          minor = r.elements['sdk:minor']
          micro = r.elements['sdk:micro']
          prev = r.elements['sdk:preview']
          next if prev
          url = t.elements['sdk:archives/sdk:archive/sdk:url'].text
          pkg_host_os = t.elements['sdk:archives/sdk:archive/sdk:host-os'].text
          next if host_os && pkg_host_os != host_os
          version = major.text
          version += ".#{minor.text}" if minor
          version += ".#{micro.text}" if micro
          version += "_rc#{prev.text}" if prev
          [version, url]
        end.compact.sort_by { |v| Gem::Version.new(v[0].gsub('_', '.')) }.last
      end

      def get_android_sdk_version
        require 'net/http'
        require 'uri'

        # Get's the Page to Scrape

        # TODO(uwe):  Should use this, but I get certificate verification errors
        # page_content = Net::HTTP.get(URI.parse(SDK_DOWNLOAD_PAGE))
        # ODOT

        # TODO(uwe): Using this instead.  Maybe use this as fallback with user confirmation?
        require 'openssl'
        uri = URI(SDK_DOWNLOAD_PAGE)
        req = Net::HTTP::Get.new(uri.path)
        res = Net::HTTP.start(uri.host, uri.port,
            use_ssl: uri.scheme == 'https',
            verify_mode: OpenSSL::SSL::VERIFY_NONE) do |https|
          https.request(req)
        end
        page_content = res.body
        # ODOT

        links = page_content.scan(/>sdk-tools-#{android_package_os_id}-(\d+).zip</)
        raise "SDK link cannot be found on download page: #{SDK_DOWNLOAD_PAGE}" if links.empty?
        links[0][0]
      end

      #########################################
      #
      # Check Methods
      #

      def check_all(api_levels, update = false)
        @existing_paths ||= []
        @missing_paths ||= []

        @java_loc = check_for('java', 'Java runtime', ENV['JAVA_HOME'] && "#{ENV['JAVA_HOME']}/bin/java")
        @javac_loc = check_for('javac', 'Java Compiler', ENV['JAVA_HOME'] && "#{ENV['JAVA_HOME']}/bin/javac")
        @ant_loc = check_for('ant', 'Apache ANT')
        check_for_android_sdk
        check_for_emulator
        check_for_haxm
        check_for_platform_tools
        check_for_build_tools
        api_levels.each { |api_level| check_for_android_platform(api_level) }

        @existing_paths.uniq!
        @missing_paths.uniq!

        puts
        ok = @java_loc && @javac_loc && @ant_loc && @android_loc && @emulator_loc && haxm_ok?(update) &&
            @adb_loc && @dx_loc && @platform_sdk_loc.all? { |_, path| !path.nil? }
        puts "    #{ok ? '*** Ruboto setup is OK! ***' : '!!! Ruboto setup is NOT OK !!!'}\n\n"
        ok
      end

      def haxm_ok?(update)
        @haxm_kext_loc && !(update && haxm_old?)
      end

      def haxm_old?
        @haxm_kext_version != @haxm_installer_version
      end

      def check_for_emulator
        @emulator_loc = check_for('emulator', 'Android Emulator',
                                  File.join(android_package_directory, 'tools', 'emulator'))
      end

      def check_for_haxm
        case android_package_os_id
        when MAC_OS_X
          @haxm_kext_loc = '/Library/Extensions/intelhaxm.kext'
          found = File.exist?(@haxm_kext_loc)

          # FIXME(uwe): Remove when we stop supporting old HAXM installer versions, like mid 2016.
          unless found
            @haxm_kext_loc = '/System/Library/Extensions/intelhaxm.kext'
            found = File.exist?(@haxm_kext_loc)
          end
          # EMXIF

          if found
            @haxm_kext_version = `kextstat | grep com.intel.kext.intelhaxm`.slice(/\(.*\)/)[1..-2]
          else
            @haxm_kext_loc = nil
          end

          @haxm_installer_loc = Dir[File.join(android_package_directory, 'extras', 'intel', 'Hardware_Accelerated_Execution_Manager', 'IntelHAXM_*.dmg')][0]

          # FIXME(uwe): Remove when we stop supporting old HAXM installer versions, like mid 2016.
          if @haxm_installer_loc.nil?
            os_x_version = `sw_vers -productVersion`
            if Gem::Version.new(os_x_version) > Gem::Version.new('10.9')
              @haxm_installer_loc = Dir[File.join(android_package_directory, 'extras', 'intel', 'Hardware_Accelerated_Execution_Manager', 'IntelHAXM*_above*.dmg')][0]
            else
              @haxm_installer_loc = Dir[File.join(android_package_directory, 'extras', 'intel', 'Hardware_Accelerated_Execution_Manager', 'IntelHAXM*_below*.dmg')][0]
            end
          end
          # EMXIF

          @haxm_installer_version = File.basename(@haxm_installer_loc).scan(/\d+/).join('.') unless @haxm_installer_loc.nil? || @haxm_installer_loc.empty?
          if haxm_old?
            puts "#{'%-25s' % 'Intel HAXM'}: Old   #{@haxm_kext_version}/#{@haxm_installer_version}"
          else
            puts "#{'%-25s' % 'Intel HAXM'}: #{(found ? 'Found' : 'Not found')}"
          end
        when LINUX
          @haxm_installer_loc = 'Not supported, yet.'
          @haxm_kext_loc = 'Not supported, yet.'
          return
        when WINDOWS
          @haxm_kext_loc = `sc query intelhaxm`
          found = ($? == 0)
          @haxm_kext_loc = nil unless found
          puts "#{'%-25s' % 'Intel HAXM'}: #{(found ? 'Found' : 'Not found')}"
          @haxm_installer_loc = Dir[File.join(android_package_directory, 'extras', 'intel', 'Hardware_Accelerated_Execution_Manager', 'IntelHaxm*.exe')].first
          return
        end
      end

      def check_for_platform_tools
        @adb_loc = check_for('adb', 'Android SDK Command adb',
                             File.join(android_package_directory, 'platform-tools', windows? ? 'adb.exe' : 'adb'))
      end

      def check_for_build_tools
        dx_locations = Dir[File.join android_package_directory, 'build-tools', '*', windows? ? 'dx.bat' : 'dx']
        sorted_dx_locations = dx_locations.sort_by { |f| Gem::Version.new f[%r{build-tools/[^/]+/(\d{1,}[.]){2}\d{1,}(_rc\d)?}]}
        @dx_loc = check_for('dx', 'Android SDK Command dx', sorted_dx_locations[-1])
      end

      def check_for_android_sdk
        @android_loc = check_for('android', 'Android Package Installer',
                                 File.join(android_package_directory, 'tools', windows? ? 'android.bat' : 'android'))
        check_for('sdkmanager', 'Android SDK Manager',
                                 File.join(android_package_directory, 'tools', 'bin', windows? ? 'sdkmanager.bat' : 'sdkmanager'))
      end

      def check_for(cmd, pretty_name=nil, alt_dir=nil)
        rv = which(cmd)
        rv = nil if rv && rv.empty?

        if rv
          @existing_paths << File.dirname(rv)
        elsif alt_dir && File.exist?(alt_dir)
          rv = alt_dir
          if windows?
            ENV['PATH'] = "#{File.dirname(rv).gsub('/', '\\')};#{ENV['PATH']}"
          else
            ENV['PATH'] = "#{File.dirname(rv)}:#{ENV['PATH']}"
          end
          @missing_paths << "#{File.dirname(rv)}"
        end

        puts "#{'%-25s' % (pretty_name || cmd)}: #{(rv ? 'Found' : 'Not found')}"
        rv
      end

      def check_for_android_platform(api_level)
        @platform_sdk_loc[api_level] = File.expand_path "#{android_package_directory}/platforms/#{api_level}"
        found = File.exist? @platform_sdk_loc[api_level]
        @platform_sdk_loc[api_level] = nil unless found
        puts "#{'%-25s' % "Platform SDK #{api_level}"}: #{(found ? 'Found' : 'Not found')}"
      rescue
        @platform_sdk_loc[api_level] = nil
      end

      #########################################
      #
      # Install Methods
      #

      def install_all(accept_all, api_levels, upgrade_haxm)
        install_java(accept_all) unless @java_loc && @javac_loc
        install_ant(accept_all) unless @ant_loc
        install_android_sdk(accept_all) unless @android_loc

        # build-tools, platform-tools, tools, and haxm
        install_android_tools(accept_all) unless @dx_loc && @adb_loc && @emulator_loc && @haxm_installer_loc
        install_haxm(accept_all) unless haxm_ok?(upgrade_haxm)
        # download_and_upgrade_haxm(true) if upgrade_haxm

        if @android_loc
          api_levels.each do |api_level|
            install_platform(accept_all, api_level) unless @platform_sdk_loc[api_level]
          end
        end
        check_all(api_levels)
      end

      def install_package(accept_all, package_name, pretty_name)
        case android_package_os_id
        when LINUX
          puts "#{pretty_name} was not found."
          installer = package_installer
          if installer
            unless accept_all
              print 'Would you like to and install it? (Y/n): '
              a = STDIN.gets.chomp.upcase
            end
            if accept_all || a == 'Y' || a.empty?
              puts "sudo #{installer} install -y #{package_name}"
              IO.popen("sudo #{installer} install -y #{package_name}") do |io|
                while (l = io.gets)
                  puts l
                end
              end
            else
              puts
              puts "You can install #{pretty_name} manually by:"
              puts "sudo #{installer} install #{package_name}"
              puts
            end
          else
            puts "Package installer not found. You'll need to install #{pretty_name} manually."
          end
        else
          raise "Unknown host os for package install: #{RbConfig::CONFIG['host_os']}"
        end
      end

      def install_java(accept_all)
        case android_package_os_id
        when MAC_OS_X
        when LINUX
          install_package(accept_all, 'default-jdk', 'Default Java Development Kit')
        when WINDOWS
          # FIXME(uwe):  Detect and warn if we are not "elevated" with adminstrator rights.
          #set IS_ELEVATED=0
          #whoami /groups | findstr /b /c:"Mandatory Label\High Mandatory Level" | findstr /c:"Enabled group" > nul: && set IS_ELEVATED=1
          #if %IS_ELEVATED%==0 (
          #    echo You must run the command prompt as administrator to install.
          #    exit /b 1
          #)

          puts 'Java JDK was not found.'
          unless accept_all
            print 'Would you like to download and install it? (Y/n): '
            a = STDIN.gets.chomp.upcase
          end
          if accept_all || a == 'Y' || a.empty?
            java_installer_file_name = 'jdk-7-windows-x64.exe'
            require 'net/http'
            require 'net/https'
            resp = nil
            @cookies = %w(gpw_e24=http%3A%2F%2Fwww.oracle.com)
            puts 'Downloading...'
            Net::HTTP.start('download.oracle.com') do |http|
              resp, _ = http.get("/otn-pub/java/jdk/7/#{java_installer_file_name}", cookie_header)
              resp.body
            end
            resp = process_response(resp)
            open(java_installer_file_name, 'wb') { |file| file.write(resp.body) }
            puts "Installing #{java_installer_file_name}..."
            system java_installer_file_name
            raise "Unexpected exit code while installing Java: #{$?.exitstatus}" unless $? == 0
            FileUtils.rm_f java_installer_file_name
          else
            puts
            puts 'You can download and install the Java JDK manually from'
            puts 'http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html'
            puts
          end
          unless check_for('javac')
            ENV['JAVA_HOME'] = 'c:\\Program Files\\Java\\jdk1.7.0'
            if Dir.exist?(ENV['JAVA_HOME'])
              @javac_loc = "#{ENV['JAVA_HOME'].gsub('\\', '/')}/bin/javac"
              puts "Setting the JAVA_HOME environment variable to #{ENV['JAVA_HOME']}"
              system %Q{setx JAVA_HOME "#{ENV['JAVA_HOME']}"}
              @missing_paths << "#{File.dirname(@javac_loc)}"
            end
          end
        else
          raise "Unknown host os: #{android_package_os_id}"
        end
      end

      def install_ant(accept_all)
        case android_package_os_id
        when MAC_OS_X
        when LINUX
          install_package(accept_all, 'ant', 'Apache ANT')
        when WINDOWS
          # FIXME(uwe):  Detect and warn if we are not "elevated" with adminstrator rights.
          #set IS_ELEVATED=0
          #whoami /groups | findstr /b /c:"Mandatory Label\High Mandatory Level" | findstr /c:"Enabled group" > nul: && set IS_ELEVATED=1
          #if %IS_ELEVATED%==0 (
          #    echo You must run the command prompt as administrator to install.
          #    exit /b 1
          #)

          puts 'Apache ANT was not found.'
          unless accept_all
            print 'Would you like to download and install it? (Y/n): '
            a = STDIN.gets.chomp.upcase
          end
          if accept_all || a == 'Y' || a.empty?
            Dir.chdir Dir.home do
              ant_package_file_name = 'apache-ant-1.9.0-bin.tar.gz'
              require 'net/http'
              require 'net/https'
              puts 'Downloading...'
              Net::HTTP.start('apache.vianett.no') do |http|
                resp, _ = http.get("/ant/binaries/#{ant_package_file_name}")
                open(ant_package_file_name, 'wb') { |file| file.write(resp.body) }
              end
              puts "Installing #{ant_package_file_name}..."
              require 'rubygems/package'
              require 'zlib'
              Gem::Package::TarReader.new(Zlib::GzipReader.open(ant_package_file_name)).each do |entry|
                puts entry.full_name
                if entry.directory?
                  FileUtils.mkdir_p entry.full_name
                elsif entry.file?
                  FileUtils.mkdir_p File.dirname(entry.full_name)
                  File.open(entry.full_name, 'wb') { |f| f << entry.read }
                end
              end
              FileUtils.rm_f ant_package_file_name
            end
          else
            puts
            puts 'You can download and install Apache ANT manually from'
            puts 'http://ant.apache.org/bindownload.cgi'
            puts
          end
          unless check_for('ant')
            ENV['ANT_HOME'] = File.expand_path(File.join('~', 'apache-ant-1.9.0')).gsub('/', '\\')
            if Dir.exist?(ENV['ANT_HOME'])
              @ant_loc = "#{ENV['ANT_HOME'].gsub('\\', '/')}/bin/ant"
              puts "Setting the ANT_HOME environment variable to #{ENV['ANT_HOME']}"
              system %Q{setx ANT_HOME "#{ENV['ANT_HOME']}"}
              @missing_paths << "#{File.dirname(@ant_loc)}"
            end
          end
        else
          raise "Unknown host os: #{RbConfig::CONFIG['host_os']}"
        end
      end

      def cookie_header
        return {} if @cookies.empty?
        {'Cookie' => @cookies.join(';')}
      end

      def store_cookie(response)
        return unless response['set-cookie']
        header = response['set-cookie']
        header.gsub! /expires=.{3},/, ''
        header.split(',').each do |cookie|
          cookie_value = cookie.strip.slice(/^.*?;/).chomp(';')
          if cookie_value =~ /^(.*?)=(.*)$/
            name = $1
            @cookies.delete_if { |c| c =~ /^#{name}=/ }
          end
          @cookies << cookie_value unless cookie_value =~ /^.*?=$/
        end
        @cookies.uniq!
      end

      def process_response(response)
        store_cookie(response)
        if response.code == '302'
          redirect_url = response['location']
          puts "Following redirect to #{redirect_url}"
          url = URI.parse(redirect_url)
          if redirect_url =~ /^http:\/\//
            Net::HTTP.start(url.host, url.port) do |http|
              response = http.get(redirect_url, cookie_header)
              response.body
            end
          else
            http = Net::HTTP.new(url.host, url.port)
            http.use_ssl = true
            http.verify_mode = OpenSSL::SSL::VERIFY_NONE
            response = http.get(redirect_url, cookie_header)
            response.body
          end
          return process_response(response)
        elsif response.code != '200'
          raise "Got response code #{response.code}"
        end
        response
      end


      def install_android_sdk(accept_all)
        unless @android_loc
          puts 'Android package installer not found.'
          unless accept_all
            print 'Would you like to download and install it? (Y/n): '
            a = STDIN.gets.chomp.upcase
          end
          if accept_all || a == 'Y' || a.empty?
            Dir.chdir File.expand_path('~/') do
              FileUtils.mkdir_p android_package_directory
              Dir.chdir android_package_directory do
                asdk_file_name = "sdk-tools-#{android_package_os_id}-#{get_android_sdk_version}.zip"
                download(asdk_file_name)
                unzip(accept_all, asdk_file_name)
                FileUtils.rm_f asdk_file_name
              end
            end
          end
          check_for_android_sdk
          unless @android_loc.nil?
            ENV['ANDROID_HOME'] = (File.expand_path File.dirname(@android_loc)+'/..').gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
            puts "Setting the ANDROID_HOME environment variable to #{ENV['ANDROID_HOME']}"
            if windows?
              system %Q{setx ANDROID_HOME "#{ENV['ANDROID_HOME']}"}
            end
            @missing_paths << "#{File.dirname(@android_loc)}"
          end
        end
      end

      def download_third_party(filename, uri)
        print "Downloading #{uri}/#{filename} \r"
        uri = URI("#{uri}/#{filename}")
        puts "File will be saved to #{android_haxm_directory}/#{filename}"
        process_download("#{android_haxm_directory}/#{filename}", uri)
      end

      def process_download(filename, uri)
        body = ''
        Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', verify_mode: OpenSSL::SSL::VERIFY_NONE).request_get(uri.path) do |response|
          unless response.is_a?(Net::HTTPSuccess)
            raise "Download failed: #{response.code} #{response.message}: #{response.body if response.response_body_permitted?}"
          end
          length = response['Content-Length'].to_i
          response.read_body do |fragment|
            body << fragment
            print "Downloading #{filename}: #{body.length / 1024**2}MB/#{length / 1024**2}MB #{(body.length * 100) / length}%\r"
          end
        end
        puts
        File.open(filename, 'wb') { |f| f << body }
      end


      def download(file_name)
        print "Downloading #{file_name}: \r"
        uri = URI("https://dl.google.com/android/repository/#{file_name}")
        process_download(file_name, uri)
      end


      def unzip(accept_all, asdk_file_name, extract_to='.')
        require 'zip'
        puts "Unzipping #{asdk_file_name.inspect}"
        Zip::File.open(asdk_file_name) do |zipfile|
          zipfile.each do |f|
            f.restore_permissions = true
            f.extract("#{extract_to}/#{f.name}") { accept_all }
          end
        end
      end

      def install_android_tools(accept_all)
        if @android_loc and (@dx_loc.nil? || @adb_loc.nil? || @emulator_loc.nil? || @haxm_installer_loc.nil?)
          puts 'Android tools not found.'
          unless accept_all
            print 'Would you like to download and install them? (Y/n): '
            a = STDIN.gets.chomp.upcase
          end
          if accept_all || a == 'Y' || a.empty?

            # TODO(uwe): Remove this when switching to `sdkmanager`
            ENV['USE_SDK_WRAPPER'] = '1'
            # silent_opt = '--silent'
            silent_opt = nil
            # haxm_filter = ',extra-intel-Hardware_Accelerated_Execution_Manager'
            haxm_filter = nil
            # ODOT

            update_cmd = "#{android_cmd} #{silent_opt} update sdk --no-ui --filter build-tools-#{get_tools_version('build-tool')[0]}#{haxm_filter},platform-tool,tool -a"
            update_sdk(update_cmd, accept_all)
            check_for_build_tools
            check_for_platform_tools
            check_for_emulator
            check_for_haxm
          end
        end
      end

      # TODO(uwe): Remove when switching to sdkmanager
      def android_cmd
        windows? ? 'android.bat' : 'android'
      end
      # ODOT

      def sdkmanager_cmd
        windows? ? 'sdkmanager.bat' : 'sdkmanager'
      end

      def get_new_haxm_filename
        case android_package_os_id
        when MAC_OS_X, WINDOWS
          version, file_name = get_tools_version('extra', ADDONS_URL, android_package_os_id)
        when LINUX
          puts 'HAXM installation on Linux is not supported, yet.'
          file_name = version = ''
        else
          raise "Unknown host os: #{RbConfig::CONFIG['host_os']}"
        end

        return file_name, version
      end

      def download_haxm(accept_all, haxm_file_name)
        download_third_party(haxm_file_name, ADDONS_URL)
        unzip(accept_all, "#{android_haxm_directory}/#{haxm_file_name}", "#{android_haxm_directory}")
        FileUtils.rm_f "#{android_haxm_directory}/#{haxm_file_name}"
      end

      def download_and_upgrade_haxm(accept_all)
        print "Downloading Intel HAXM... \r"
        filename, version = get_new_haxm_filename
        download_haxm(accept_all, filename)
        install_haxm(accept_all, version)
      end

      def install_haxm(accept_all, custom_version=nil)
        haxm_file_override =  "IntelHAXM_#{custom_version}.dmg" unless custom_version.nil?
        if @haxm_installer_loc && (@haxm_kext_loc.nil? || haxm_old?)
          if @haxm_kext_loc.nil?
            puts 'HAXM not installed.'
          else
            puts "HAXM is old: #{@haxm_kext_version} / #{@haxm_installer_version}"
          end

          unless accept_all
            if @haxm_kext_loc.nil?
              print 'Would you like to install HAXM? (Y/n): '
            else
              print 'Would you like to update HAXM? (Y/n): '
            end
            a = STDIN.gets.chomp.upcase
          end
          if accept_all || a == 'Y' || a.empty?
            case android_package_os_id
            when MAC_OS_X
              puts 'Mounting the HAXM install image'
              if custom_version.nil?
                system "hdiutil attach #{@haxm_installer_loc}"
                fileName = Dir['/Volumes/IntelHAXM*/IntelHAXM*.mpkg'][0]
              else
                system "hdiutil attach #{android_haxm_directory}/#{haxm_file_override}"
                fileName = Dir["/Volumes/IntelHAXM_#{custom_version}/IntelHAXM_#{custom_version}.mpkg"][0]
              end
              puts 'Starting the HAXM installer.  Sudo password required.'
              system "sudo -S installer -pkg #{fileName} -target /"
            when LINUX
              puts '    HAXM installation on Linux is not supported, yet.'
              return
            when WINDOWS
              cmd = @haxm_installer_loc.gsub('/', "\\")
              puts 'Running the HAXM installer'
              system %Q{#{WINDOWS_ELEVATE_CMD} "#{cmd}"}
              raise "Unexpected return code: #{$?.exitstatus}" unless $? == 0
              return
            end
          end
        end
      end

      def install_platform(accept_all, api_level)
        puts "Android platform SDK for #{api_level} not found."
        unless accept_all
          print 'Would you like to download and install it? (Y/n): '
          a = STDIN.gets.chomp.upcase
        end
        if accept_all || a == 'Y' || a.empty?
          level = api_level[/\d+/]
          packages = `#{sdkmanager_cmd} --list`.scan(/\b(?:system-images|platforms);android-#{level}(?:;.*?;[a-zA-z0-9-]+)?/)
          puts "Installing #{packages}"
          update_cmd = "#{sdkmanager_cmd} '#{packages.join("' '")}'"
          update_sdk(update_cmd, accept_all)
          check_for_android_platform(api_level)
        end
      end

      def update_sdk(update_cmd, accept_all)
        if accept_all
          IO.popen(update_cmd, 'r+', external_encoding: Encoding::BINARY) do |cmd_io|
            begin
              output = ''.encode(Encoding::BINARY)
              question_pattern = /.*Accept\?\s*\(y\/N\): /m
              STDOUT.sync = true
              cmd_io.each_char do |text|
                print text
                output << text
                if output =~ question_pattern
                  cmd_io.puts 'y'
                  output.sub! question_pattern, ''
                end
              end
            rescue Errno::EIO
              # This probably just means that the process has finished giving output.
            end
          end
        else
          system update_cmd
        end
      end

      #########################################
      #
      # Path Config Method
      #

      def config_path(accept_all)
        unless @missing_paths.empty?
          puts "\nYou are missing some paths.  Execute these lines to add them:\n\n"
          if windows?
            @missing_paths.each do |path|
              puts %Q{    set PATH="#{path.gsub '/', '\\'};%PATH%"}
            end
            old_path = ENV['PATH'].split(';')
            new_path = (@missing_paths.map { |path| path.gsub '/', '\\' } + old_path).uniq.join(';')
            if new_path.size <= 1024
              system %Q{setx PATH "#{new_path}"}
            else
              puts "\nYour path is HUGE:  #{new_path.size} characters.  It cannot be saved permanently:\n\n"
              puts new_path.gsub(';', "\n")
              puts
            end
          else
            @missing_paths.each do |path|
              puts %Q{    export PATH="#{path}:$PATH"}
            end
            puts
            unless accept_all
              print "\nWould you like to append these lines to your configuration script? (Y/n): "
              a = STDIN.gets.chomp.upcase
            end
            if accept_all || a == 'Y' || a.empty?
              unless accept_all
                print "What script do you use to configure your PATH? (#{path_setup_file}): "
                a = STDIN.gets.chomp.downcase
              end
              rubotorc = '~/.rubotorc'
              File.open(File.expand_path(rubotorc), 'w') do |f|
                (@existing_paths + @missing_paths - %w(/usr/bin)).uniq.sort.each do |path|
                  f << %Q{PATH="#{path}:$PATH"\n}
                end
              end
              config_file_name = File.expand_path("~/#{a.nil? || a.empty? ? path_setup_file : a}")
              unless File.exist? config_file_name
                puts "Your path configuration script (#{config_file_name}) does not exist, Ruboto will create a new one."
                system "touch #{config_file_name}"
              end

              old_config = File.read(config_file_name)
              new_config = old_config.dup
              new_config.gsub! /\n*# BEGIN Ruboto setup\n.*?\n# END Ruboto setup\n*/m, "\n\n"
              new_config << "\n\n# BEGIN Ruboto setup\n"
              new_config << "source #{rubotorc}\n"
              new_config << "# END Ruboto setup\n\n"
              File.open(config_file_name, 'wb') { |f| f << new_config }
              puts "Updated #{config_file_name} to load the #{rubotorc} config file."
              puts 'Please close your command window and reopen, or run'
              puts
              puts "    source #{rubotorc}"
              puts
            end
          end
        end
      end
    end
  end
end