#!/usr/bin/env bash RUBY_BUILD_VERSION="20121110" set -E exec 3<&2 # preserve original stderr at fd 3 lib() { parse_options() { OPTIONS=() ARGUMENTS=() local arg option index for arg in "$@"; do if [ "${arg:0:1}" = "-" ]; then if [ "${arg:1:1}" = "-" ]; then OPTIONS[${#OPTIONS[*]}]="${arg:2}" else index=1 while option="${arg:$index:1}"; do [ -n "$option" ] || break OPTIONS[${#OPTIONS[*]}]="$option" index=$(($index+1)) done fi else ARGUMENTS[${#ARGUMENTS[*]}]="$arg" fi done } if [ "$1" == "--$FUNCNAME" ]; then declare -f "$FUNCNAME" echo "$FUNCNAME \"\$1\";" exit fi } lib "$1" resolve_link() { $(type -p greadlink readlink | head -1) "$1" } abs_dirname() { local cwd="$(pwd)" local path="$1" while [ -n "$path" ]; do cd "${path%/*}" local name="${path##*/}" path="$(resolve_link "$name" || true)" done pwd cd "$cwd" } build_failed() { { echo echo "BUILD FAILED" echo if ! rmdir "${BUILD_PATH}" 2>/dev/null; then echo "Inspect or clean up the working tree at ${BUILD_PATH}" if file_is_not_empty "$LOG_PATH"; then echo "Results logged to ${LOG_PATH}" echo echo "Last 10 log lines:" tail -n 10 "$LOG_PATH" fi fi } >&3 exit 1 } file_is_not_empty() { local filename="$1" local line_count="$(wc -l "$filename" 2>/dev/null || true)" if [ -n "$line_count" ]; then words=( $line_count ) [ "${words[0]}" -gt 0 ] else return 1 fi } install_package() { install_package_using "tarball" 1 $* } install_git() { install_package_using "git" 2 $* } install_svn() { install_package_using "svn" 2 $* } install_package_using() { local package_type="$1" local package_type_nargs="$2" local package_name="$3" shift 3 pushd "$BUILD_PATH" >&4 "fetch_${package_type}" "$package_name" $* shift $(($package_type_nargs)) make_package "$package_name" $* popd >&4 { echo "Installed ${package_name} to ${PREFIX_PATH}" echo } >&2 } make_package() { local package_name="$1" shift pushd "$package_name" >&4 before_install_package "$package_name" build_package "$package_name" $* after_install_package "$package_name" fix_directory_permissions popd >&4 } compute_md5() { if type md5 &>/dev/null; then md5 -q elif type openssl &>/dev/null; then local output="$(openssl md5)" echo "${output##* }" elif type md5sum &>/dev/null; then local output="$(md5sum -b)" echo "${output% *}" else return 1 fi } verify_checksum() { # If there's no MD5 support, return success [ -n "$HAS_MD5_SUPPORT" ] || return 0 # If the specified filename doesn't exist, return success local filename="$1" [ -e "$filename" ] || return 0 # If there's no expected checksum, return success local expected_checksum="$2" [ -n "$expected_checksum" ] || return 0 # If the computed checksum is empty, return failure local computed_checksum="$(compute_md5 < "$filename")" [ -n "$computed_checksum" ] || return 1 if [ "$expected_checksum" != "$computed_checksum" ]; then { echo echo "checksum mismatch: ${filename} (file is corrupt)" echo "expected $expected_checksum, got $computed_checksum" echo } >&4 return 1 fi } http() { local method="$1" local url="$2" [ -n "$url" ] || return 1 if type curl &>/dev/null; then "http_${method}_curl" "$url" elif type wget &>/dev/null; then "http_${method}_wget" "$url" else echo "error: please install \`curl\` or \`wget\` and try again" >&2 exit 1 fi } http_head_curl() { curl -sILf "$1" >&4 2>&1 } http_get_curl() { curl -sSLf "$1" } http_head_wget() { wget -q --spider "$1" >&4 2>&1 } http_get_wget() { wget -nv -O- "$1" } fetch_tarball() { local package_name="$1" local package_url="$2" local mirror_url local checksum if [ "$package_url" != "${package_url/\#}" ]; then checksum="${package_url#*#}" package_url="${package_url%%#*}" if [ -n "$RUBY_BUILD_MIRROR_URL" ]; then mirror_url="${RUBY_BUILD_MIRROR_URL}/$checksum" fi fi local package_filename="${package_name}.tar.gz" symlink_tarball_from_cache "$package_filename" "$checksum" || { echo "Downloading ${package_filename}..." >&2 { http head "$mirror_url" && download_tarball "$mirror_url" "$package_filename" "$checksum" } || download_tarball "$package_url" "$package_filename" "$checksum" } { tar xzvf "$package_filename" rm -f "$package_filename" } >&4 2>&1 } symlink_tarball_from_cache() { [ -n "$RUBY_BUILD_CACHE_PATH" ] || return 1 local package_filename="$1" local cached_package_filename="${RUBY_BUILD_CACHE_PATH}/$package_filename" local checksum="$2" [ -e "$cached_package_filename" ] || return 1 verify_checksum "$cached_package_filename" "$checksum" >&4 2>&1 || return 1 ln -s "$cached_package_filename" "$package_filename" >&4 2>&1 || return 1 } download_tarball() { local package_url="$1" [ -n "$package_url" ] || return 1 local package_filename="$2" local checksum="$3" echo "-> $package_url" >&2 { http get "$package_url" > "$package_filename" verify_checksum "$package_filename" "$checksum" } >&4 2>&1 || return 1 if [ -n "$RUBY_BUILD_CACHE_PATH" ]; then local cached_package_filename="${RUBY_BUILD_CACHE_PATH}/$package_filename" { mv "$package_filename" "$cached_package_filename" ln -s "$cached_package_filename" "$package_filename" } >&4 2>&1 || return 1 fi } fetch_git() { local package_name="$1" local git_url="$2" local git_ref="$3" echo "Cloning ${git_url}..." >&2 if type git &>/dev/null; then git clone --depth 1 --branch "$git_ref" "$git_url" "${package_name}" >&4 2>&1 else echo "error: please install \`git\` and try again" >&2 exit 1 fi } fetch_svn() { local package_name="$1" local svn_url="$2" local svn_rev="$3" echo "Checking out ${svn_url}..." >&2 if type svn &>/dev/null; then svn co -r "$svn_rev" "$svn_url" "${package_name}" >&4 2>&1 else echo "error: please install \`svn\` and try again" >&2 exit 1 fi } build_package() { local package_name="$1" shift if [ "$#" -eq 0 ]; then local commands="standard" else local commands="$*" fi echo "Installing ${package_name}..." >&2 for command in $commands; do "build_package_${command}" done } build_package_standard() { local package_name="$1" if [ "${MAKEOPTS+defined}" ]; then MAKE_OPTS="$MAKEOPTS" elif [ -z "${MAKE_OPTS+defined}" ]; then MAKE_OPTS="-j 2" fi { ./configure --prefix="$PREFIX_PATH" $CONFIGURE_OPTS make $MAKE_OPTS make install } >&4 2>&1 } build_package_autoconf() { { autoconf } >&4 2>&1 } build_package_ruby() { local package_name="$1" { "$RUBY_BIN" setup.rb } >&4 2>&1 } build_package_ree_installer() { local options="" if [[ "Darwin" = "$(uname)" ]]; then options="--no-tcmalloc" fi # Work around install_useful_libraries crash with --dont-install-useful-gems mkdir -p "$PREFIX_PATH/lib/ruby/gems/1.8/gems" { ./installer --auto "$PREFIX_PATH" --dont-install-useful-gems $options $CONFIGURE_OPTS } >&4 2>&1 } build_package_rbx() { local package_name="$1" { ./configure --prefix="$PREFIX_PATH" --gemsdir="$PREFIX_PATH" rake install } >&4 2>&1 } build_package_maglev() { build_package_copy { cd "${PREFIX_PATH}" ./install.sh cd "${PREFIX_PATH}/bin" echo "Creating symlink for ruby*" ln -fs maglev-ruby ruby echo "Creating symlink for irb*" ln -fs maglev-irb irb } >&4 2>&1 echo echo "Run 'maglev start' to start up the stone before using 'ruby' or 'irb'" } build_package_jruby() { build_package_copy cd "${PREFIX_PATH}/bin" ln -fs jruby ruby install_jruby_launcher remove_windows_files } install_jruby_launcher() { cd "${PREFIX_PATH}/bin" { ./ruby gem install jruby-launcher } >&4 2>&1 } remove_windows_files() { cd "$PREFIX_PATH" rm -f bin/*.exe bin/*.dll bin/*.bat bin/jruby.sh } build_package_copy() { mkdir -p "$PREFIX_PATH" cp -R . "$PREFIX_PATH" } before_install_package() { local stub=1 } after_install_package() { local stub=1 } fix_directory_permissions() { # Ensure installed directories are not world-writable to avoid Bundler warnings find "$PREFIX_PATH" -type d -exec chmod go-w {} \; } require_gcc() { local gcc="$(locate_gcc || true)" if [ -z "$gcc" ]; then local esc=$'\033' { echo echo "${esc}[1mERROR${esc}[0m: This package must be compiled with GCC, but ruby-build couldn't" echo "find a suitable \`gcc\` executable on your system. Please install GCC" echo "and try again." echo if [ "$(uname -s)" = "Darwin" ]; then echo "${esc}[1mDETAILS${esc}[0m: Apple no longer includes the official GCC compiler with Xcode" echo "as of version 4.2. Instead, the \`gcc\` executable is a symlink to" echo "\`llvm-gcc\`, a modified version of GCC which outputs LLVM bytecode." echo echo "For most programs the \`llvm-gcc\` compiler works fine. However," echo "versions of Ruby older than 1.9.3-p125 are incompatible with" echo "\`llvm-gcc\`. To build older versions of Ruby you must have the official" echo "GCC compiler installed on your system." echo echo "${esc}[1mTO FIX THE PROBLEM${esc}[0m: Install the official GCC compiler using these" echo "packages: ${esc}[4mhttps://github.com/kennethreitz/osx-gcc-installer/downloads${esc}[0m" echo echo "You will need to install the official GCC compiler to build older" echo "versions of Ruby even if you have installed Apple's Command Line Tools" echo "for Xcode package. The Command Line Tools for Xcode package only" echo "includes \`llvm-gcc\`." fi } >&3 return 1 fi export CC="$gcc" } locate_gcc() { local gcc gccs IFS=: gccs=($(gccs_in_path)) verify_gcc "$CC" || verify_gcc "$(command -v gcc || true)" || { for gcc in "${gccs[@]}"; do verify_gcc "$gcc" && break || true done } return 1 } gccs_in_path() { local gcc path paths local gccs=() IFS=: paths=($PATH) shopt -s nullglob for path in "${paths[@]}"; do for gcc in "$path"/gcc-*; do gccs["${#gccs[@]}"]="$gcc" done done shopt -u nullglob printf :%s "${gccs[@]}" } verify_gcc() { local gcc="$1" if [ -z "$gcc" ]; then return 1 fi local version="$("$gcc" --version || true)" if [ -z "$version" ]; then return 1 fi if echo "$version" | grep LLVM >/dev/null; then return 1 fi echo "$gcc" } version() { echo "ruby-build ${RUBY_BUILD_VERSION}" } usage() { { version echo "usage: ruby-build [-k|--keep] [-v|--verbose] definition prefix" echo " ruby-build --definitions" } >&2 if [ -z "$1" ]; then exit 1 fi } list_definitions() { { for definition in "${RUBY_BUILD_ROOT}/share/ruby-build/"*; do echo "${definition##*/}" done } | sort } unset VERBOSE unset KEEP_BUILD_PATH RUBY_BUILD_ROOT="$(abs_dirname "$0")/.." parse_options "$@" for option in "${OPTIONS[@]}"; do case "$option" in "h" | "help" ) usage without_exiting { echo echo " -k/--keep Do not remove source tree after installation" echo " -v/--verbose Verbose mode: print compilation status to stdout" echo " --definitions List all built-in definitions" echo } >&2 exit 0 ;; "definitions" ) list_definitions exit 0 ;; "k" | "keep" ) KEEP_BUILD_PATH=true ;; "v" | "verbose" ) VERBOSE=true ;; "version" ) version exit 0 ;; esac done DEFINITION_PATH="${ARGUMENTS[0]}" if [ -z "$DEFINITION_PATH" ]; then usage elif [ ! -e "$DEFINITION_PATH" ]; then BUILTIN_DEFINITION_PATH="${RUBY_BUILD_ROOT}/share/ruby-build/${DEFINITION_PATH}" if [ -e "$BUILTIN_DEFINITION_PATH" ]; then DEFINITION_PATH="$BUILTIN_DEFINITION_PATH" else echo "ruby-build: definition not found: ${DEFINITION_PATH}" >&2 exit 1 fi fi PREFIX_PATH="${ARGUMENTS[1]}" if [ -z "$PREFIX_PATH" ]; then usage fi if [ -z "$TMPDIR" ]; then TMP="/tmp" else TMP="${TMPDIR%/}" fi if [ -n "$RUBY_BUILD_CACHE_PATH" ] && [ -d "$RUBY_BUILD_CACHE_PATH" ]; then RUBY_BUILD_CACHE_PATH="${RUBY_BUILD_CACHE_PATH%/}" else unset RUBY_BUILD_CACHE_PATH fi if [ -z "$RUBY_BUILD_MIRROR_URL" ]; then RUBY_BUILD_MIRROR_URL="http://cloud.github.com/downloads/sstephenson/ruby-build-download-mirror" else RUBY_BUILD_MIRROR_URL="${RUBY_BUILD_MIRROR_URL%/}" fi if [ -n "$RUBY_BUILD_SKIP_MIRROR" ]; then unset RUBY_BUILD_MIRROR_URL fi if echo test | compute_md5 >/dev/null; then HAS_MD5_SUPPORT=1 else unset HAS_MD5_SUPPORT unset RUBY_BUILD_MIRROR_URL fi SEED="$(date "+%Y%m%d%H%M%S").$$" LOG_PATH="${TMP}/ruby-build.${SEED}.log" RUBY_BIN="${PREFIX_PATH}/bin/ruby" CWD="$(pwd)" if [ -z "$RUBY_BUILD_BUILD_PATH" ]; then BUILD_PATH="${TMP}/ruby-build.${SEED}" else BUILD_PATH="$RUBY_BUILD_BUILD_PATH" fi exec 4<> "$LOG_PATH" # open the log file at fd 4 if [ -n "$VERBOSE" ]; then tail -f "$LOG_PATH" & trap "kill 0" SIGINT SIGTERM EXIT fi export LDFLAGS="-L'${PREFIX_PATH}/lib' ${LDFLAGS}" export CPPFLAGS="-I'${PREFIX_PATH}/include' ${CPPFLAGS}" unset RUBYOPT unset RUBYLIB trap build_failed ERR mkdir -p "$BUILD_PATH" source "$DEFINITION_PATH" [ -z "${KEEP_BUILD_PATH}" ] && rm -fr "$BUILD_PATH" trap - ERR