#!/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}" >&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 md5sum &>/dev/null; then local output="$(md5sum -b)" echo "${output% *}" else echo "error: please install \`md5sum\` and try again" >&2 exit 1 fi } verify_checksum() { local filename="$1" if [ ! -e "$filename" ]; then return 1 fi local expected_checksum="$2" if [ -z "$expected_checksum" ]; then return 0 fi local computed_checksum="$(compute_md5 < "$filename")" if [ -z "$computed_checksum" ]; then return 1 fi 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 } retrieve_url() { if type curl &>/dev/null; then curl -f "$@" elif type wget &>/dev/null; then wget -O- "$@" else echo "error: please install \`curl\` or \`wget\` and try again" >&2 exit 1 fi } fetch_tarball() { local package_name="$1" local package_url="$2" local checksum="${package_url#*\#}" if [ -n "$checksum" ]; then package_url="${package_url%%#*}" fi local package_filename="${package_name}.tar.gz" symlink_tarball_from_cache "$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() { if [ -n "$RUBY_BUILD_CACHE_PATH" ]; then local package_filename="$1" local cached_package_filename="${RUBY_BUILD_CACHE_PATH}/$package_filename" local checksum="$2" if verify_checksum "$cached_package_filename" "$checksum"; then ln -s "$cached_package_filename" "$package_filename" >&4 2>&1 return 0 fi fi return 1 } download_tarball() { local package_url="$1" local package_filename="$2" local checksum="$3" echo "Downloading ${package_url}..." >&2 { retrieve_url "$package_url" > "$package_filename" verify_checksum "$package_filename" "$checksum" } >&4 2>&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 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 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