diff --git a/TODO b/TODO new file mode 100644 index 0000000000..9c5c29d441 --- /dev/null +++ b/TODO @@ -0,0 +1,8 @@ +Binary packages +--------------- + +X Better error messages +X Show 'Downloading' and 'Installing' messages a la ruby-build +* Rewrite RbConfig::CONFIG['CC'] on install +* Patch rbx since the rewriter doesn't work there +X rbenv-install should check for binary packages before building diff --git a/bin/rbenv-install b/bin/rbenv-install index 486e5c9444..fc70f2b8e8 100755 --- a/bin/rbenv-install +++ b/bin/rbenv-install @@ -2,19 +2,26 @@ set -e [ -n "$RBENV_DEBUG" ] && set -x -# Provide rbenv completions -if [ "$1" = "--complete" ]; then - exec ruby-build --definitions -fi +resolve_link() { + $(type -p greadlink readlink | head -1) $1 +} -if [ -z "$RBENV_ROOT" ]; then - RBENV_ROOT="${HOME}/.rbenv" -fi +abs_dirname() { + local cwd="$(pwd)" + local path="$1" -DEFINITION="$1" -case "$DEFINITION" in -"" | -* ) - { echo "usage: rbenv install VERSION" + while [ -n "$path" ]; do + cd "${path%/*}" + local name="${path##*/}" + path="$(resolve_link "$name" || true)" + done + + pwd + cd "$cwd" +} + +usage() { + { echo "usage: rbenv install [--build | --package] VERSION" echo " rbenv install /path/to/definition" echo echo "Available versions:" @@ -22,11 +29,54 @@ case "$DEFINITION" in echo } >&2 exit 1 +} + +bin_dir="$(abs_dirname "$0")" +export PATH="${bin_dir}:$PATH" + +# Provide rbenv completions +if [ "$1" = "--complete" ]; then + exec ruby-build --definitions +fi + +force_build="" +force_package="" +if [ "$1" = "--build" ]; then + force_build=1 + shift +elif [ "$1" = "--package" ]; then + force_package=1 + shift +fi + +definition="$1" +case "$definition" in +"" | -* ) + usage ;; esac -VERSION_NAME="${DEFINITION##*/}" -PREFIX="${RBENV_ROOT}/versions/${VERSION_NAME}" +if [ -z "$RBENV_ROOT" ]; then + RBENV_ROOT="${HOME}/.rbenv" +fi + +version_name="${definition##*/}" +prefix="${RBENV_ROOT}/versions/${version_name}" + +package() { + ruby-package install $* "$definition" "$prefix" +} + +build() { + ruby-build "$definition" "$prefix" +} + +if [ -n "$force_build" ]; then + build +elif [ -n "$force_package" ]; then + package +else + package --fail-silently || build +fi -ruby-build "$DEFINITION" "$PREFIX" -rbenv rehash \ No newline at end of file +rbenv rehash diff --git a/bin/ruby-package b/bin/ruby-package new file mode 100755 index 0000000000..cbfc74bd23 --- /dev/null +++ b/bin/ruby-package @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -e + +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" +} + +usage() { + echo "usage: ruby-package []" >&2 + exit 1 +} + +bin_dir="$(abs_dirname "$0")" +export PATH="${bin_dir}/../libexec:${bin_dir}:$PATH" + +command="$1" +command_path="$(command -v ruby-package-"$1" || true)" + +if [ -z "$TMPDIR" ]; then + export TMPDIR="/tmp" +else + export TMPDIR="${TMPDIR%/}" +fi + +if [ -z "$command" ]; then + usage +elif [ -z "$command_path" ]; then + echo "ruby-package: ${command}: command not found" >&2 + usage +fi + +shift +exec "$command_path" "$@" diff --git a/libexec/ruby-package-build b/libexec/ruby-package-build new file mode 100755 index 0000000000..4c8111222a --- /dev/null +++ b/libexec/ruby-package-build @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -e + +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" +} + +usage() { + echo "usage: ruby-package build [-v | --verbose] [--resume] DEFINITION" + exit 1 +} + +bin_root="$(abs_dirname "$0")" + +verbose="" +resume="" + +while :; do + case "$1" in + "-v" | "--verbose" ) + verbose="-v" + shift + ;; + "--resume" ) + resume=1 + shift + ;; + "-"* ) + usage + ;; + * ) + break + ;; + esac +done + +definition="$1" +if [ -z "$definition" ]; then + usage +fi + +package="${definition##*/}" +prefix="/tmp/ruby-build/-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------/$package" + +package_dir="${package}.$(ruby-package platform)" + +if [ -z "$resume" ]; then + ruby-build $verbose "$definition" "$prefix" +fi + +cwd="$(pwd)" +cd "${prefix}/.." + +mkdir -p "${package_dir}/bin" +cp "${bin_root}/../share/ruby-package/ruby-package-install" "${package_dir}/bin" +cp "${bin_root}/../share/ruby-package/ruby-package-rewrite-text" "${package_dir}/bin" +cc -Wall "${bin_root}/../share/ruby-package/ruby-package-rewrite-binary.c" -o "${package_dir}/bin/ruby-package-rewrite-binary" + +mkdir -p "${package_dir}/metadata" +echo -n "$prefix" > "${package_dir}/metadata/prefix" +echo -n "$package" > "${package_dir}/metadata/package" + +cd "$package" + +while read line; do + binary="${line#Binary file }" + if [ "$line" = "$binary" ]; then + # plain text match + text="${line%%:*}" + echo "$text" >> "../${package_dir}/metadata/text-files" + else + # binary match + binary="${binary% matches}" + echo "$binary" >> "../${package_dir}/metadata/binary-files" + fi +done < <( grep -m 1 -R "$prefix" * ) + +tar cf "../${package_dir}/package.tar" * + +cd .. + +tar czf "${cwd}/${package_dir}.rubypackage" "$package_dir" +rm -fr "$package_dir" + +echo "${package_dir}.rubypackage" diff --git a/libexec/ruby-package-fetch b/libexec/ruby-package-fetch new file mode 100755 index 0000000000..4e8d7b4937 --- /dev/null +++ b/libexec/ruby-package-fetch @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -e + +usage() { + echo "usage: ruby-package fetch [--check] PACKAGE" >&2 + exit 1 +} + +check() { + local url="$1" + + set +e + curl -ILfs "$url" >/dev/null + local result="$?" + set -e + + if [ "$result" -ne 0 ]; then + return 1 + fi +} + +download() { + local url="$1" + local filename="$2" + + set +e + curl -Lfs "$url" > "$filename" + local result="$?" + set -e + + if [ "$result" -ne 0 ]; then + return 1 + elif [ ! -f "$filename" ]; then + return 1 + else + echo "$filename" + fi +} + +check="" +if [ "$1" = "--check" ]; then + check=1 + shift +fi + +package="$1" +if [ -z "$package" ]; then + usage +fi + +package_name="${package}.$(ruby-package platform).rubypackage" + +package_repo="${RUBY_PACKAGE_REPO%/}" +if [ -z "$ruby_package_repo" ]; then + package_repo="https://github.com/downloads/sstephenson/ruby-packages" +fi + +package_url="${package_repo}/${package_name}" + +if [ -n "$check" ]; then + check "$package_url" || { + echo "error: package \`$package' not found ($package_url)" + exit 1 + } >&2 +else + package_filename="${TMPDIR}/${package_name}.$$" + echo "Downloading $package_url..." >&2 + download "$package_url" "$package_filename" || { + echo "error: couldn't download package \`$package' ($package_url)" + exit 1 + } >&2 +fi diff --git a/libexec/ruby-package-install b/libexec/ruby-package-install new file mode 100755 index 0000000000..d06bd7e10d --- /dev/null +++ b/libexec/ruby-package-install @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -e + +usage() { + echo "usage: ruby-package install [--fail-silently] PACKAGE DESTINATION" >&2 + exit 1 +} + +cleanup_packages() { + if [ -n "$package_root" ]; then + rm -rf "${package_root%/*}" + fi + + if [ -n "$package_file" ]; then + if [ "$package" != "$package_file" ]; then + rm -rf "$package_file" + fi + fi +} + +fail_silently="" +if [ "$1" = "--fail-silently" ]; then + fail_silently=1 + shift +fi + +package="$1" +if [ -z "$package" ]; then + usage +fi + +destination="$2" +if [ -z "$destination" ]; then + usage +fi + +if [ -f "$package" ]; then + package_file="$package" +else + if [ -n "$fail_silently" ]; then + ruby-package fetch --check "$package" 2>/dev/null + fi + + package_file="$(ruby-package fetch "$package")" +fi + +trap cleanup_packages SIGINT SIGTERM EXIT + +package_root="$(ruby-package unpack "$package_file")" +package_name="$(cat "${package_root}/metadata/package")" + +echo "Installing ${package_name}..." >&2 +"${package_root}/bin/ruby-package-install" "$destination" +echo "Installed ${package_name} to ${destination}" >&2 diff --git a/libexec/ruby-package-platform b/libexec/ruby-package-platform new file mode 100755 index 0000000000..8abebc215f --- /dev/null +++ b/libexec/ruby-package-platform @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e + +echo "$(uname -m)-$(uname -s)" | tr '[A-Z] ' '[a-z]_' diff --git a/libexec/ruby-package-unpack b/libexec/ruby-package-unpack new file mode 100755 index 0000000000..ab1920d47c --- /dev/null +++ b/libexec/ruby-package-unpack @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -e + +resolve_link() { + $(type -p greadlink readlink | head -1) $1 +} + +abs_dirname() { + local cwd="$(pwd)" + local path="$1" + + while [ -n "$path" ]; do + cd "${path%/*}" 2>/dev/null + local name="${path##*/}" + path="$(resolve_link "$name" || true)" + done + + pwd + cd "$cwd" +} + +usage() { + echo "usage: ruby-package unpack PACKAGE_FILE" >&2 + exit 1 +} + +package_file="$1" +if [ -z "$package_file" ]; then + usage +fi + +package_path="$(abs_dirname "$package_file")/${package_file##*/}" +if [ ! -f "$package_path" ]; then + echo "error: file not found: $1" >&2 + exit 1 +fi + +root="${TMPDIR}/ruby-package.$$" +rm -fr "$root" +mkdir -p "$root" + +cd "$root" +tar xzf "$package_path" + +cd * +package_root="$(pwd)" +package_name="$(cat "$package_root"/metadata/package)" +package_name_with_platform="${package_root##*/}" + +if [ "${package_name}.$(ruby-package platform)" != "$package_name_with_platform" ]; then + echo "error: invalid package format" >&2 + exit 2 +fi + +echo "$package_root" diff --git a/share/ruby-package/ruby-package-install b/share/ruby-package/ruby-package-install new file mode 100755 index 0000000000..6be5b6f827 --- /dev/null +++ b/share/ruby-package/ruby-package-install @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -e + +prefix="$1" +if [ -z "$prefix" ]; then + echo "usage: ruby-package-install PREFIX" >&2 + exit 1 +fi + +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" +} + +bin_root="$(abs_dirname "$0")" +package_root="${bin_root}/.." +package_prefix="$(cat "${package_root}/metadata/prefix")" +package="$(cat "${package_root}/metadata/package")" + +mkdir -p "$prefix" +cd "$prefix" +prefix="$(pwd)" + +rewrite() { + kind="$1" + while read file; do + path="${prefix}/$file" + cp "$path" "${path}.orig" + "${bin_root}/ruby-package-rewrite-$kind" "$package_prefix" "$prefix" < "${path}.orig" > "$path" + rm "${path}.orig" + done < "${package_root}/metadata/${kind}-files" +} + +tar xzf "${package_root}/package.tar" +rewrite binary +rewrite text diff --git a/share/ruby-package/ruby-package-rewrite-binary.c b/share/ruby-package/ruby-package-rewrite-binary.c new file mode 100644 index 0000000000..f1496fc9cd --- /dev/null +++ b/share/ruby-package/ruby-package-rewrite-binary.c @@ -0,0 +1,174 @@ +#include +#include +#include + +#define BUF_SIZE 4096 + +typedef struct { + unsigned int initial_size; + unsigned int size; + unsigned int pos; + char *data; +} buf_t; + +void *buf_malloc(size_t size) { + void *result = malloc(size); + + if (result == NULL) { + fprintf(stderr, "error allocating memory, aborting\n"); + exit(1); + } + + return result; +} + +void buf_init(buf_t *this, unsigned int size) { + this->initial_size = size; + this->size = size; + this->pos = 0; + this->data = buf_malloc(size); +} + +void buf_grow(buf_t *this) { + unsigned int size = this->size + this->initial_size; + char *data = buf_malloc(size); + + memcpy(data, this->data, this->size); + free(this->data); + this->data = data; + this->size = size; +} + +int buf_putc(buf_t *this, int chr) { + if (this->pos == this->size) { + buf_grow(this); + } + + this->data[this->pos] = chr; + this->pos++; + return chr; +} + +void buf_append(buf_t *this, char *src, unsigned int length) { + unsigned int i; + for (i = 0; i < length; i++) { + buf_putc(this, src[i]); + } +} + +void buf_concat(buf_t *this, buf_t *src) { + buf_append(this, src->data, src->pos); +} + +void buf_print(buf_t *this, FILE *out) { + unsigned int i; + for (i = 0; i < this->pos; i++) { + fputc(this->data[i], out); + } +} + +void buf_reset(buf_t *this) { + this->pos = 0; +} + +void buf_destroy(buf_t *this) { + buf_reset(this); + free(this->data); + this->data = NULL; +} + +void rewrite_chunk(buf_t *in, FILE *out, char *src_prefix, char *dst_prefix) { + unsigned int src_prefix_len, dst_prefix_len, chr, i; + buf_t result; + + buf_init(&result, BUF_SIZE); + src_prefix_len = strlen(src_prefix); + dst_prefix_len = strlen(dst_prefix); + i = 0; + + while (i < in->pos) { + buf_append(&result, dst_prefix, dst_prefix_len); + i += src_prefix_len; + + while (i < in->pos && (chr = in->data[i++])) { + buf_putc(&result, chr); + } + + buf_putc(&result, 0); + } + + for (i = result.pos; i < in->pos; i++) { + buf_putc(&result, 0); + } + + buf_print(&result, out); + buf_destroy(&result); +} + +void rewrite(FILE *in, FILE *out, char *src_prefix, char *dst_prefix) { + unsigned int src_prefix_len, chr; + buf_t chunk, buf; + + buf_init(&chunk, BUF_SIZE); + buf_init(&buf, BUF_SIZE); + src_prefix_len = strlen(src_prefix); + + while ((chr = fgetc(in)) != EOF) { + if (buf.pos < src_prefix_len && chr == src_prefix[buf.pos]) { + buf_putc(&buf, chr); + continue; + + } else if (buf.pos >= src_prefix_len && chr != 0) { + buf_putc(&buf, chr); + continue; + + } else if (buf.pos > 0 && chr == 0) { + buf_concat(&chunk, &buf); + buf_putc(&chunk, 0); + buf_reset(&buf); + continue; + } + + if (chunk.pos > 0) { + if (chunk.pos > src_prefix_len) { + rewrite_chunk(&chunk, out, src_prefix, dst_prefix); + } else { + buf_print(&chunk, out); + } + buf_reset(&chunk); + } + + if (buf.pos > 0) { + buf_print(&buf, out); + buf_reset(&buf); + } + + fputc(chr, out); + } + + buf_destroy(&buf); + buf_destroy(&chunk); +} + +int main(int argc, char **argv) { + char *src_prefix, *dst_prefix; + int src_prefix_length; + + if (argc != 3) { + fprintf(stderr, "usage: %s SRC_PREFIX DST_PREFIX\n", argv[0]); + return 1; + } + + src_prefix = argv[1]; + src_prefix_length = strlen(src_prefix); + dst_prefix = argv[2]; + + if (strlen(dst_prefix) > strlen(src_prefix)) { + fprintf(stderr, "error: destination prefix must be %d bytes or less\n", src_prefix_length); + return 2; + } + + rewrite(stdin, stdout, src_prefix, dst_prefix); + + return 0; +} diff --git a/share/ruby-package/ruby-package-rewrite-text b/share/ruby-package/ruby-package-rewrite-text new file mode 100755 index 0000000000..8d9d87bf09 --- /dev/null +++ b/share/ruby-package/ruby-package-rewrite-text @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e + +src_prefix="$1" +dst_prefix="$2" + +if [ -z "$src_prefix" ] || [ -z "$dst_prefix" ]; then + echo "usage: ruby-package-install-text SRC_PREFIX DST_PREFIX" >&2 + exit 1 +fi + +sed "s:${src_prefix}:${dst_prefix}:g"