Skip to content

Commit 339310a

Browse files
committed
Support building with version prefix
Commonly in CI pipelines and other scripts, users may want to install the latest from a specific series, e.g. `3.4` or `jruby-10.0`. ```bash $ bin/ruby-build 3.4 /tmp/3.4 ... Downloading ruby-3.4.9.tar.gz... $ bin/ruby-build 4.0 /tmp/4.0 ... Downloading ruby-4.0.2.tar.gz... ``` Also support `--resolve`: ```bash $ bin/ruby-build --resolve 3.4 3.4.9 ```
1 parent f723aa7 commit 339310a

6 files changed

Lines changed: 213 additions & 23 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ $ ruby-build --list # lists latest stable releases for ea
4444
$ ruby-build --definitions # lists all definitions, including outdated ones
4545
$ ruby-build 3.2.2 ~/.rubies/ruby-3.2.2 # installs Ruby 3.2.2
4646
$ ruby-build -d ruby-3.2.2 ~/.rubies # alternate form for the previous example
47+
$ ruby-build -d 3.2 ~/.rubies/ # installs latest "ruby-3.2" (3.2.10)
4748

4849
# As an rbenv plugin
4950
$ rbenv install 3.2.2 # installs Ruby 3.2.2 to ~/.rbenv/versions/3.2.2

bin/rbenv-install

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,12 @@ DEFINITION="${ARGUMENTS[0]}"
144144
[ -n "$DEFINITION" ] || DEFINITION="$(rbenv-local 2>/dev/null || true)"
145145
[ -n "$DEFINITION" ] || usage 1 >&2
146146

147+
[ -n "$DEFINITION" ] && RESOLVED_DEFINITION="$(ruby-build --resolve "${DEFINITION}" 2>/dev/null || true)"
148+
149+
if [ -n "$RESOLVED_DEFINITION" ]; then
150+
DEFINITION="$RESOLVED_DEFINITION"
151+
fi
152+
147153
# Define `before_install` and `after_install` functions that allow
148154
# plugin hooks to register a string of code for execution before or
149155
# after the installation process.

bin/ruby-build

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
#
33
# Usage: ruby-build [-dvpk] <definition> <prefix> [-- <configure-args...>]
44
# ruby-build {--list|--definitions}
5+
# ruby-build {--resolve} <version>
56
# ruby-build --version
67
#
78
# -l, --list List latest stable releases for each Ruby
9+
# --resolve Find the most recent version matching a prefix.
810
# --definitions List all local definitions, including outdated ones
911
# --version Show version of ruby-build
1012
#
@@ -1411,6 +1413,38 @@ list_maintained_versions() {
14111413
} | extract_latest_versions | sort_versions | uniq
14121414
}
14131415

1416+
filter_version_prefix()
1417+
{
1418+
pattern="${1//./\.}"
1419+
pattern="${pattern//+/\+}"
1420+
grep -e "^${pattern}[-.$]"
1421+
}
1422+
1423+
resolve() {
1424+
prefix="${1#ruby-}"
1425+
1426+
# Look for an exact match first.
1427+
for DEFINITION_DIR in "${RUBY_BUILD_DEFINITIONS[@]}"; do
1428+
if [ -f "${DEFINITION_DIR}/${prefix}" ]; then
1429+
echo "${1}"
1430+
return 0
1431+
fi
1432+
done
1433+
1434+
prefix="${prefix%.}"
1435+
1436+
version=$(list_definitions | \
1437+
grep -v -e '-rc[0-9]*$' -e '-preview[0-9]*$' -e '-dev$' | \
1438+
filter_version_prefix "${prefix}" | \
1439+
LC_ALL=C sort -t. -k 1,1 -k 2,2n -k 3,3n -k 4,4n -k 5,5n | \
1440+
tail -n 1)
1441+
1442+
[[ -z "${version}" ]] && return 1
1443+
[[ "${1}" = "ruby-"* ]] && version="ruby-${version}"
1444+
1445+
echo "${version}"
1446+
}
1447+
14141448
extract_latest_versions() {
14151449
# sort in this function looks redundunt but it is necessary
14161450
# rbx-3.99 appears latest unless the sort
@@ -1453,6 +1487,9 @@ for option in "${OPTIONS[@]}"; do
14531487
"l" | "list")
14541488
EARLY_EXIT=list_maintained_versions
14551489
;;
1490+
"resolve")
1491+
EARLY_EXIT=resolve
1492+
;;
14561493
"d" | "dir")
14571494
APPEND_DEFINITION_TO_PREFIX=true
14581495
;;
@@ -1503,23 +1540,19 @@ if [ "${#EXTRA_ARGUMENTS[@]}" -gt 0 ]; then
15031540
RUBY_CONFIGURE_OPTS_ARRAY=("${EXTRA_ARGUMENTS[@]}")
15041541
fi
15051542

1506-
if [ "$APPEND_DEFINITION_TO_PREFIX" = "true" ]; then
1507-
if [ -p "$DEFINITION_PATH" ]; then
1508-
echo "ruby-build: using named pipes in combination with \`--dir' is not possible" >&2
1509-
EARLY_EXIT=usage_error
1510-
fi
1511-
PREFIX_PATH="$PREFIX_PATH/$(basename "$DEFINITION_PATH")"
1512-
fi
1513-
15141543
case "$EARLY_EXIT" in
15151544
help )
15161545
version
15171546
echo
15181547
usage 0
15191548
;;
1520-
version | list_definitions | list_maintained_versions )
1521-
"$EARLY_EXIT"
1522-
exit 0
1549+
version | list_definitions | list_maintained_versions | resolve )
1550+
shift
1551+
if "$EARLY_EXIT" "$@"; then
1552+
exit 0
1553+
else
1554+
exit 1
1555+
fi
15231556
;;
15241557
usage_error )
15251558
echo >&2
@@ -1533,6 +1566,8 @@ usage_error )
15331566
;;
15341567
esac
15351568

1569+
DEFINITION_PREFIX=""
1570+
15361571
# expand the <definition> argument to full path of the definition file
15371572
if [[ ! -f "$DEFINITION_PATH" && ! -p "$DEFINITION_PATH" ]]; then
15381573
for DEFINITION_DIR in "${RUBY_BUILD_DEFINITIONS[@]}"; do
@@ -1544,10 +1579,27 @@ if [[ ! -f "$DEFINITION_PATH" && ! -p "$DEFINITION_PATH" ]]; then
15441579

15451580
# If the given definition is like ruby-X.Y.Z, search again with X.Y.Z
15461581
if [[ "$DEFINITION_PATH" =~ ^ruby-[0-9] ]]; then
1547-
DEFINITION_PATH="${DEFINITION_PATH#ruby-}"
1582+
UNPREFIXED_DEFINITION_PATH="${DEFINITION_PATH#ruby-}"
15481583
for DEFINITION_DIR in "${RUBY_BUILD_DEFINITIONS[@]}"; do
1549-
if [ -f "${DEFINITION_DIR}/${DEFINITION_PATH}" ]; then
1550-
DEFINITION_PATH="${DEFINITION_DIR}/${DEFINITION_PATH}"
1584+
if [ -f "${DEFINITION_DIR}/${UNPREFIXED_DEFINITION_PATH}" ]; then
1585+
DEFINITION_PREFIX="ruby-"
1586+
DEFINITION_PATH="${DEFINITION_DIR}/${UNPREFIXED_DEFINITION_PATH}"
1587+
break
1588+
fi
1589+
done
1590+
fi
1591+
1592+
# If the given definition isn't a perfect match, we do a prefix search.
1593+
if [ ! -f "$DEFINITION_PATH" ]; then
1594+
FULLY_QUALIFIED_VERSION=$(resolve "$DEFINITION_PATH")
1595+
if [[ "$FULLY_QUALIFIED_VERSION" =~ ^ruby-[0-9] ]]; then
1596+
DEFINITION_PREFIX="ruby-"
1597+
FULLY_QUALIFIED_VERSION="${FULLY_QUALIFIED_VERSION#ruby-}"
1598+
fi
1599+
1600+
for DEFINITION_DIR in "${RUBY_BUILD_DEFINITIONS[@]}"; do
1601+
if [ -f "${DEFINITION_DIR}/${FULLY_QUALIFIED_VERSION}" ]; then
1602+
DEFINITION_PATH="${DEFINITION_DIR}/${FULLY_QUALIFIED_VERSION}"
15511603
break
15521604
fi
15531605
done
@@ -1559,6 +1611,14 @@ if [[ ! -f "$DEFINITION_PATH" && ! -p "$DEFINITION_PATH" ]]; then
15591611
fi
15601612
fi
15611613

1614+
if [ "$APPEND_DEFINITION_TO_PREFIX" = "true" ]; then
1615+
if [ -p "$DEFINITION_PATH" ]; then
1616+
echo "ruby-build: using named pipes in combination with \`--dir' is not possible" >&2
1617+
EARLY_EXIT=usage_error
1618+
fi
1619+
PREFIX_PATH="$PREFIX_PATH/${DEFINITION_PREFIX}$(basename "$DEFINITION_PATH")"
1620+
fi
1621+
15621622
# normalize the <prefix> argument
15631623
if [ "${PREFIX_PATH#/}" = "$PREFIX_PATH" ]; then
15641624
PREFIX_PATH="${PWD}/${PREFIX_PATH}"

test/build.bats

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,30 @@ make install
787787
OUT
788788
}
789789

790+
@test "nested install destination with ruby prefix and version prefix" {
791+
cached_tarball "ruby-3.2.45" configure
792+
793+
stub_repeated brew false
794+
stub_make_install
795+
796+
mkdir -p "$TMP"/definitions
797+
cat > "$TMP"/definitions/3.2.45 <<DEF
798+
install_package "ruby-3.2.45" "http://ruby-lang.org/ruby/2.0/ruby-3.2.45.tar.gz"
799+
DEF
800+
801+
RUBY_BUILD_DEFINITIONS="$TMP"/definitions run ruby-build --dir ruby-3.2 "$INSTALL_ROOT"
802+
assert_success
803+
804+
unstub brew
805+
unstub make
806+
807+
assert_build_log <<OUT
808+
ruby-3.2.45: [--prefix=$INSTALL_ROOT/ruby-3.2.45,--with-ext=openssl,psych,+]
809+
make -j 2
810+
make install
811+
OUT
812+
}
813+
790814
@test "definition file with ruby prefix" {
791815
export RUBY_BUILD_CACHE_PATH="$FIXTURE_ROOT"
792816

test/definitions.bats

100644100755
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,101 @@ NUM_DEFINITIONS="$(ls "$BATS_TEST_DIRNAME"/../share/ruby-build | wc -l)"
6060
assert_success ""
6161
}
6262

63+
@test "installing definition by prefix" {
64+
export RUBY_BUILD_DEFINITIONS="${TMP}/definitions"
65+
mkdir -p "${TMP}/definitions"
66+
67+
echo false > "${TMP}/definitions/1.8.6"
68+
echo false > "${TMP}/definitions/1.9.3"
69+
echo true > "${TMP}/definitions/1.9.10"
70+
echo false > "${TMP}/definitions/1.90.0"
71+
echo false > "${TMP}/definitions/2.0.0"
72+
73+
run bin/ruby-build "1.9" "${TMP}/install"
74+
assert_success ""
75+
}
76+
77+
@test "resolve failure" {
78+
run bin/ruby-build --resolve "doesntexist"
79+
assert_failure ""
80+
}
81+
82+
@test "resolve definition by version prefix" {
83+
export RUBY_BUILD_DEFINITIONS="${TMP}/definitions"
84+
mkdir -p "${TMP}/definitions"
85+
86+
touch "${TMP}/definitions/1.8.6"
87+
touch "${TMP}/definitions/1.9.3"
88+
touch "${TMP}/definitions/1.9.10"
89+
touch "${TMP}/definitions/1.90.0"
90+
touch "${TMP}/definitions/2.0.0"
91+
92+
run bin/ruby-build --resolve "1.9" "${TMP}/install"
93+
assert_success "1.9.10"
94+
}
95+
96+
@test "resolve definition by exact match" {
97+
export RUBY_BUILD_DEFINITIONS="${TMP}/definitions"
98+
mkdir -p "${TMP}/definitions"
99+
100+
touch "${TMP}/definitions/3.2.1"
101+
102+
run bin/ruby-build --resolve "3.2.1" "${TMP}/install"
103+
assert_success "3.2.1"
104+
}
105+
106+
@test "resolve definition by exact match and ruby prefix" {
107+
export RUBY_BUILD_DEFINITIONS="${TMP}/definitions"
108+
mkdir -p "${TMP}/definitions"
109+
110+
touch "${TMP}/definitions/3.2.1"
111+
112+
run bin/ruby-build --resolve "ruby-3.2.1" "${TMP}/install"
113+
assert_success "ruby-3.2.1"
114+
}
115+
116+
@test "resolve definition with ruby prefix" {
117+
export RUBY_BUILD_DEFINITIONS="${TMP}/definitions"
118+
mkdir -p "${TMP}/definitions"
119+
120+
touch "${TMP}/definitions/1.8.6"
121+
touch "${TMP}/definitions/1.9.3"
122+
touch "${TMP}/definitions/1.9.10"
123+
touch "${TMP}/definitions/1.90.0"
124+
touch "${TMP}/definitions/2.0.0"
125+
126+
run bin/ruby-build --resolve "ruby-1.9" "${TMP}/install"
127+
assert_success "ruby-1.9.10"
128+
}
129+
130+
@test "resolve definition by implementation name" {
131+
export RUBY_BUILD_DEFINITIONS="${TMP}/definitions"
132+
mkdir -p "${TMP}/definitions"
133+
134+
touch "${TMP}/definitions/foo-1.8.6"
135+
touch "${TMP}/definitions/foo-1.9.3"
136+
touch "${TMP}/definitions/1.9.10"
137+
touch "${TMP}/definitions/1.90.0"
138+
touch "${TMP}/definitions/2.0.0"
139+
140+
run bin/ruby-build --resolve "foo" "${TMP}/install"
141+
assert_success "foo-1.9.3"
142+
}
143+
144+
@test "resolve definition by implementation name and version" {
145+
export RUBY_BUILD_DEFINITIONS="${TMP}/definitions"
146+
mkdir -p "${TMP}/definitions"
147+
148+
touch "${TMP}/definitions/foo-1.8.6"
149+
touch "${TMP}/definitions/foo-1.9.3"
150+
touch "${TMP}/definitions/1.9.10"
151+
touch "${TMP}/definitions/1.90.0"
152+
touch "${TMP}/definitions/2.0.0"
153+
154+
run bin/ruby-build --resolve "foo-1.8" "${TMP}/install"
155+
assert_success "foo-1.8.6"
156+
}
157+
63158
@test "installing nonexistent definition" {
64159
run ruby-build "nonexistent" "${TMP}/install"
65160
assert [ "$status" -eq 2 ]

test/rbenv.bats

100644100755
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ stub_ruby_build() {
1616
}
1717

1818
@test "install proper" {
19-
stub_ruby_build 'echo ruby-build "$@"'
19+
stub_ruby_build 'echo 2.1.2' 'echo ruby-build "$@"'
2020

2121
run rbenv-install 2.1.2
2222
assert_success "ruby-build 2.1.2 ${RBENV_ROOT}/versions/2.1.2"
@@ -27,7 +27,7 @@ stub_ruby_build() {
2727
}
2828

2929
@test "install with flags" {
30-
stub_ruby_build 'echo "ruby-build $(inspect_args "$@")"'
30+
stub_ruby_build 'echo 2.1.2' 'echo "ruby-build $(inspect_args "$@")"'
3131

3232
run rbenv-install -kpv 2.1.2 -- --with-configure-opt="hello world"
3333
assert_success "ruby-build --keep --verbose --patch 2.1.2 ${RBENV_ROOT}/versions/2.1.2 -- \"--with-configure-opt=hello world\""
@@ -39,7 +39,7 @@ stub_ruby_build() {
3939

4040
@test "suggest running rbenv global after install" {
4141
rm -rf "$RBENV_ROOT/version"
42-
stub_ruby_build 'echo ruby-build "$@"'
42+
stub_ruby_build 'echo 2.1.2' 'echo ruby-build "$@"'
4343

4444
run rbenv-install 2.1.2
4545
assert_success <<OUT
@@ -52,7 +52,7 @@ OUT
5252
}
5353

5454
@test "install rbenv local version by default" {
55-
stub_ruby_build 'echo ruby-build "$1"'
55+
stub_ruby_build 'echo 2.1.2' 'echo ruby-build "$1"'
5656
stub rbenv-local 'echo 2.1.2'
5757

5858
run rbenv-install
@@ -100,7 +100,8 @@ OUT
100100
fi
101101

102102
stub_repeated brew false
103-
stub_ruby_build 'echo ERROR >&2 && exit 2' \
103+
stub_ruby_build 'exit 1' \
104+
'echo ERROR >&2 && exit 2' \
104105
"--definitions : echo 1.8.7 1.9.3-p0 1.9.3-p194 2.1.2 | tr ' ' $'\\n'"
105106

106107
run rbenv-install 1.9.3
@@ -127,10 +128,11 @@ OUT
127128

128129
@test "Homebrew upgrade instructions" {
129130
stub brew "--prefix : echo '${BATS_TEST_DIRNAME%/*}'"
130-
stub_ruby_build 'echo ERROR >&2 && exit 2' \
131+
stub_ruby_build 'exit 1' \
132+
'echo ERROR >&2 && exit 2' \
131133
"--definitions : true"
132134

133-
run rbenv-install 1.9.3
135+
run rbenv-install 1.9.10
134136
assert_failure
135137
assert_output <<OUT
136138
ERROR
@@ -148,7 +150,8 @@ OUT
148150

149151
@test "no build definitions from plugins" {
150152
refute [ -e "${RBENV_ROOT}/plugins" ]
151-
stub_ruby_build 'echo $RUBY_BUILD_DEFINITIONS'
153+
stub_ruby_build 'echo 2.1.2' \
154+
'echo $RUBY_BUILD_DEFINITIONS'
152155

153156
run rbenv-install 2.1.2
154157
assert_success ""
@@ -157,7 +160,8 @@ OUT
157160
@test "some build definitions from plugins" {
158161
mkdir -p "${RBENV_ROOT}/plugins/foo/share/ruby-build"
159162
mkdir -p "${RBENV_ROOT}/plugins/bar/share/ruby-build"
160-
stub_ruby_build "echo \$RUBY_BUILD_DEFINITIONS | tr ':' $'\\n'"
163+
stub_ruby_build 'echo 2.1.2' \
164+
"echo \$RUBY_BUILD_DEFINITIONS | tr ':' $'\\n'"
161165

162166
run rbenv-install 2.1.2
163167
assert_success

0 commit comments

Comments
 (0)