# Copyright (C) 2011-2012 RightScale, Inc, All Rights Reserved Worldwide. # # THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE # AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use, # reproduction, modification, or disclosure of this program is # strictly prohibited. Any use of this program by an authorized # licensee is strictly subject to the terms and conditions, # including confidentiality obligations, set forth in the applicable # License Agreement between RightScale.com, Inc. and # the licensee module RightConf # Specify rbenv and ruby-build git revision for installation RBENV_GIT_REPO = 'git://github.com/sstephenson/rbenv.git' RBENV_GIT_REVISION = "9375e99f921f428849f19efe2a2e500b3295d1a7" RUBYBUILD_GIT_REPO = 'git://github.com/sstephenson/ruby-build.git' RUBY_BUILD_GIT_REVISION = "28b9bcb6df1bc18fb2b06e25ee65f397ac3d4632" # Local installation target folders RBENV_INSTALL_TARGET = "#{ENV['HOME']}/.rbenv" RUBYBUILD_TARGET = "#{RBENV_INSTALL_TARGET}/plugins/ruby-build" class RubyConfigurator include Configurator register :ruby description "Installs ruby interpreter and rubygems.\n" + 'Installs and uses rbenv on supported (i.e. non-Windows) platforms' setting 'version', 'Ruby version using rbenv notation (see "rbenv versions")', :required => true updater lambda { update_rbenv_installation } # Message to display to the user when rbenv isn't in the path. PATH_ADVICE = "You should add rbenv to your path and 'eval \"$(rbenv init -)\"' to your .bash_profile / .bashrc etc. " + "so rbenv is properly initialized in new shells.\n\n" + "If you are a bash user (e.g. Mac OS X), you can add the following to ~/.bash_profile:\n\n" + " echo 'export PATH=\"$HOME/.rbenv/bin:$PATH\"' >> ~/.bash_profile\n" + " echo 'eval \"$(rbenv init -)\"' >> ~/.bash_profile\n\n" # Let configurator run, it is idempotent # # === Return # false:: Always return false def check_linux false end alias :check_darwin :check_linux alias :check_windows :check_linux # Switch to ruby version defined in settings # Use rbenv and install it if needed # # === Return # true:: Always return true def run_linux if Command.execute('rvm').success? report_fatal "rconf detected an installation of RVM, rconf now uses rbenv to configure ruby versions.\n" + "Unfortunately rbenv and RVM cannot be installed together on the same machine (see https://github.com/sstephenson/rbenv/).\n" + "Please uninstall RVM and try again ('rvm implode' will delete RVM and all installed rubies and gems).\n" + "Once RVM is uninstalled please start a new shell (you may also need to remove references to RVM from your ~/.bash_profile)" return true end check_rbenv return true if aborting check_ruby Command.set_ruby(ruby_version) true end alias :run_darwin :run_linux # Switch to ruby version defined in settings # TBD # # === Return # true:: Always return true def run_windows end protected # Make our version specifier compatible with RVM-style specifiers (override base class accessor) def ruby_version @ruby_version ||= version.start_with?('ruby-') ? version[5..-1] : version end # Check whether rbenv and ruby-build are installed and installs it/them if not # # === Return # true:: Always return true def check_rbenv rbenv_present = File.exist?(File.join(ENV['HOME'], '.rbenv', 'bin', 'rbenv')) res = Command.execute('rbenv') rbenv_in_path = res.success? if rbenv_present && !rbenv_in_path # Bail if rbenv isn't correctly initialized (we can't use it in this state) post_note "rconf detected rbenv is installed in #{rbenv_path} but it is not in the PATH.\n" + PATH_ADVICE aborting(true) elsif rbenv_in_path # rbenv is installed and initialized; make sure we have a suitable version res.output =~ /^rbenv ([0-9]+)\.([0-9]+)\.([0-9]+)$/ maj, min, build = [$1.to_i, $2.to_i, $3.to_i] if maj == 0 && min < 4 report_fatal("rconf requires rbenv version 0.4.0 or greater, you have #{maj}.#{min}.#{build} installed, "+ "please upgrade (e.g. via 'brew upgrade rbenv') and try again") end else # No sign of rbenv; try to install it install_rbenv_from_git end true end # Overwrite ruby-build definition file to use our own, patched version of REE and to set certain compile options # so that it REE works with newer versions of GCC as well. # # === Return # true:: Always return true def self.add_custom_ree_definition curr_dir=File.dirname(__FILE__) patched_ree = "#{curr_dir}/../../patches/ree-1.8.7-2012.02-rs-custom" FileUtils.cp patched_ree, "#{RUBYBUILD_TARGET}/share/ruby-build/ree-1.8.7-2012.02" true end # Update rbenv and ruby-build to the version specified through the git commit version constant # # === Return # true:: Always return true def self.update_rbenv_installation puts "Updating rbenv ..." if File.directory?("#{RBENV_INSTALL_TARGET}/.git") # Perform the pull only if rbenv has been installed by using a git pull (and not through a package manager) system("cd #{RBENV_INSTALL_TARGET}; git fetch -q; git checkout -q #{RBENV_GIT_REVISION}") end puts "Updating ruby-build ..." if File.directory?("#{RUBYBUILD_TARGET}/.git") # Perform the pull only if ruby-build has been installed by using a git pull (and not through a package manager) # Before checking out the new branch, reset the repository to avoid conflicts system("cd #{RUBYBUILD_TARGET}; git reset -q --hard; git fetch -q; git checkout -q #{RUBY_BUILD_GIT_REVISION}") # Add our ree definition again because the repository has been reset RubyConfigurator.add_custom_ree_definition end true end # Install rbenv # 1. Fetch rbenv from repository and install it # 2. Fetch ruby-build from repository and install it # 3. Install custom ree installation definition into ruby-build so that our patched REE gets installed # # === Return # true:: Always return true def install_rbenv_from_git # Require: git res = Command.execute('git', '--version').success? if !res # Install git opts = { :report => true, :post_install => update_msg }.merge(abort_option('Failed to install git, required for rbenv')) PackageInstaller.install('git', opts) end # Fetch and install rbenv # *********************** # Fetch rbenv from git repo Command.execute('git', 'clone', RBENV_GIT_REPO, RBENV_INSTALL_TARGET, {:abort_on_failure => 'Git clone of rbnev failed - server down?'}) Kernel.system("cd #{RBENV_INSTALL_TARGET}; git checkout #{RBENV_GIT_REVISION}") # Fetch, install and patch ruby-build # *********************************** # Create plugins directory (target for ruby-build) ret = FileUtils.mkdir_p(RUBYBUILD_TARGET) if !ret report_fatal("Could not create directory: #{RUBYBUILD_TARGET}"); end Command.execute('git', 'clone', RUBYBUILD_GIT_REPO, RUBYBUILD_TARGET, {:abort_on_failure => 'Git clone of ruby-build failed - server down?'}) Kernel.system("cd #{RUBYBUILD_TARGET}; git checkout #{RUBY_BUILD_GIT_REVISION}") RubyConfigurator.add_custom_ree_definition end # Check .ruby-version and its content # # === Return # true:: Always return true def check_ruby unless Command.execute('rbenv', 'local', ruby_version).success? report_check("Installing ruby #{ruby_version} (this will take a while, please be patient)") Platform.dispatch(ruby_version) { :install_ruby } end which = Command.execute('which', 'ruby').output.strip if (which =~ %r(^(/usr)?/bin.*ruby$)) post_note "Your PATH is not setup correctly for rbenv; ('which ruby' => #{which}).\n" + PATH_ADVICE aborting(true) end true end # Install given ruby version using rbenv # Install any prerequesites first # # === Parameters # ruby(String):: Ruby version compatible with rbenv # # === Return # true:: Always return true def install_ruby(ruby) Platform.dispatch(ruby) { :install_ruby_prerequisites } # Can't abort on failure rbenv install seems to exist with a non zero error code even when successful :( Command.execute('rbenv', 'install', ruby) report_success post_note "rconf installed ruby #{ruby} and needs to be restarted so the right ruby tools get activated\nPlease run 'rconf' again." aborting(true) true end # Install given ruby version using rbenv # On Lion, need to setup CC env var before running rbenv install # # === Parameters # ruby(String):: Ruby version compatible with rbenv # # === Return # true:: Always return true def install_ruby_darwin(ruby) Platform.dispatch(ruby) { :install_ruby_prerequisites } c_version = `system_profiler SPDeveloperToolsDataType -xml | xpath "//*[text()='_items']/following-sibling::array/dict/child::key[text()='spdevtools_version']/following-sibling::string/text()" 2> /dev/null` env = {} # Ensure we're using a sane GCC 4.2, not Apple's LLVM-based lookalike. gcc42 = ['/usr/local/bin/gcc-4.2', '/usr/bin/gcc-4.2'].detect { |p| File.executable?(p) } if c_version =~ /^4\.2\.[0-9]+/ if gcc42.nil? report_fatal("The C compiler included with Xcode #{c_version} produces buggy ruby interpreters, please install the C compilers from https://github.com/downloads/kennethreitz/osx-gcc-installer/GCC-10.7-v2.pkg or update your version of Xcode and re-run rconf") else env['CC'] = gcc42 end end # Ruby 1.8 force-installs the Tk stdlib extension, no way to opt out. # Ensure XQuartz is installed and its headers are on the path. if ruby =~ /1\.8/ if File.directory?('/opt/X11/include') env['CC'] = gcc42 env['CPPFLAGS'] = '-I/opt/X11/include' else report_fatal("Installing ruby 1.8 (or ree 1.8) on Mac OS X requires that XQuartz be installed on the machine first, please go to http://xquartz.macosforge.org/landing/, install XQuartz and try again") end end Command.execute('rbenv', 'install', ruby, :abort_on_failure => 'Failed to install ruby', :env => env) report_success end # Make sure to install all required linux packages first # # === Return # true:: Always return true def install_ruby_prerequisites_linux_ubuntu(ruby) report_check("Installing required packages, this could take a while") packages = [] packages = %w(build-essential bison openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev) Command.sudo('apt-get', 'install', '-y', *packages) report_success end alias install_ruby_prerequisites_linux_debian install_ruby_prerequisites_linux_ubuntu # Make sure to install all required CentOS / RedHhat packages first # NOTE: For centos 5.4 final iconv-devel might not be available :( # # === Return # true:: Always return true def install_ruby_prerequisites_linux_centos(ruby) report_check("Installing required packages, this could take a while") packages = [] if ruby =~ /^ree-|^ruby-/ packages = %w(gcc-c++ patch readline readline-devel zlib zlib-devel libyaml-devel libffi-devel openssl-devel iconv-devel) end # TBD Define packages needed for other rubies Command.sudo('yum', 'install', '-y', *packages) report_success end alias :install_ruby_prerequisites_linux_redhat :install_ruby_prerequisites_linux_centos # No pre-requesites to install ree or Matz ruby on Mac (TBD others) # # === Return # true:: Always return true def install_ruby_prerequisites_darwin(ruby) version_output = `sw_vers` if m = /ProductVersion:\s*([0-9.]+)/m.match(version_output) osx_version = m[1].split(".").map { |c| c.to_i } else raise RuntimeError, "Can't determine OS X version from sw_vers output (#{version_output})" end if (osx_version[0] > 10 || osx_version[1] > 6) report_check("installing non-LLVM GCC 4.2") PackageInstaller.install('https://raw.github.com/Homebrew/homebrew-dupes/master/apple-gcc42.rb', :abort_on_failure => "Could not install gcc 4.2") report_success end true end # Produce abort on failure option # # === Parameters # message(String):: Abort message to be used in case abort option should be set # # === Return # {}:: Empty hash if 'abort_on_failure' is notset # opts(Hash):: Abort option with give message otherwise def abort_option(message) opts = abort_on_failure && { :abort_on_failure => message } || {} end end end