Skip to content

Commit a0ff75b

Browse files
authored
Merge pull request #149 from trevordcampbell/fix-bun-feature
fix: Bun Feature Failure on Apple Silicon ARM64 + Feature Hardening
2 parents da70168 + acea2d1 commit a0ff75b

File tree

4 files changed

+250
-53
lines changed

4 files changed

+250
-53
lines changed

src/bun/NOTES.md

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,41 @@ Pin a specific version:
3434
## Support
3535

3636
- Distros: `Debian` / `Ubuntu` and `Alpine`
37-
- Architectures: `x86_64` and `arm64`
37+
- Architectures: `x86_64` and `arm64` (based on available Bun releases)
3838

3939
## Tests
4040

41-
This feature is tested against various Distro:
41+
This feature is tested against various Distros:
4242

43-
- `Ubuntu` (latest)
44-
- `Debian` (latest)
45-
- `Alpine` (latest)
46-
- `Debian` with a pinned version of Bun (e.g., Bun `v1.1.38`)
43+
- `Ubuntu` (glibc)
44+
- `Debian` (glibc)
45+
- `Alpine` (musl)
46+
- `Debian` with a pinned version of Bun
47+
48+
*Note: Only architectures with actual Bun release assets are supported (x86_64 and arm64).* *
49+
50+
## Architecture and Asset Selection
51+
52+
This feature automatically selects the correct Bun release asset based on the detected architecture and libc:
53+
54+
- **x86_64**: Uses "baseline" builds for optimal CPU compatibility (e.g., `bun-linux-x64-baseline.zip`)
55+
- **arm64**: Uses standard builds since "baseline" variants are not published for ARM64 (e.g., `bun-linux-aarch64.zip`)
56+
57+
The installation includes robust fallback logic:
58+
59+
- Primary: Preferred asset pattern for the detected architecture/libc combination
60+
- Fallback: Alternative patterns if the primary asset is not found
61+
- Error handling: Clear error messages if no suitable asset is available
62+
63+
This approach ensures compatibility across different platforms while gracefully handling potential future changes to Bun's release asset naming.
4764

4865
## Considerations / Future Enhancements
4966

50-
- If Bun’s release assets ever require explicit filtering, we can wire an `assetRegex` or pass `additionalFlags` to the `gh-release` helper. The helper already auto-filters by platform/arch, so no extra flags are set currently.
5167
- Coexistence with Node.js: this feature only installs `bun` and a `bunx` shim and does not modify Node.js.
5268
- PATH/profile: installation to `/usr/local/bin` avoids profile changes.
69+
- Asset regex patterns: the fallback chain ensures compatibility even if Bun changes their release asset naming conventions.
70+
71+
## Debugging
72+
73+
- To enable additional debug logs during installation, set the environment variable `BUN_FEATURE_DEBUG=1`.
74+
- Diagnostic logs from detection functions are emitted to stderr to keep return values clean.

src/bun/devcontainer-feature.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"id": "bun",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"name": "Bun",
55
"documentationURL": "http://github.com/devcontainers-extra/features/tree/main/src/bun",
66
"description": "Bun is an all-in-one toolkit for JavaScript and TypeScript apps.",

src/bun/install.sh

Lines changed: 218 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,67 +4,240 @@ set -e
44

55
source ./library_scripts.sh
66

7+
echo "Starting Bun installation"
8+
79
# nanolayer is a cli utility which keeps container layers as small as possible
810
# source code: https://github.com/devcontainers-extra/nanolayer
911
# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations,
1012
# and if missing - will download a temporary copy that automatically get deleted at the end
1113
# of the script
14+
echo "Ensuring nanolayer is available"
15+
# Initialize variable to suppress shellcheck warning (assigned by ensure_nanolayer function)
16+
nanolayer_location=""
1217
ensure_nanolayer nanolayer_location "v0.5.6"
1318

1419
# Canonicalize VERSION for Bun's tag scheme when pinning
15-
version_for_release="$VERSION"
16-
if [ "${VERSION:-latest}" != "latest" ] && [ -n "$VERSION" ]; then
17-
if [[ "$VERSION" =~ ^bun-v ]]; then
18-
version_for_release="$VERSION"
19-
elif [[ "$VERSION" =~ ^v ]]; then
20-
version_for_release="bun-$VERSION"
21-
else
22-
version_for_release="bun-v$VERSION"
20+
canonicalize_version() {
21+
local version="$1"
22+
23+
if [ "$version" = "latest" ] || [ -z "$version" ]; then
24+
echo "latest"
25+
return 0
26+
fi
27+
28+
# Remove any leading/trailing whitespace
29+
version="$(echo "$version" | xargs)"
30+
31+
# Already in correct format
32+
if [[ "$version" =~ ^bun-v[0-9]+\.[0-9]+\.[0-9]+ ]]; then
33+
echo "$version"
34+
return 0
2335
fi
24-
fi
36+
37+
# Remove 'bun-' prefix if present
38+
if [[ "$version" =~ ^bun- ]]; then
39+
version="${version#bun-}"
40+
fi
41+
42+
# Handle versions starting with 'v'
43+
if [[ "$version" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then
44+
echo "bun-$version"
45+
return 0
46+
fi
47+
48+
# Handle plain version numbers
49+
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
50+
echo "bun-v$version"
51+
return 0
52+
fi
53+
54+
# Invalid version format
55+
echo "WARNING: Invalid version format: '$version'. Using 'latest' instead." >&2
56+
echo "latest"
57+
}
58+
59+
# shellcheck disable=SC2153
60+
# VERSION is provided by the devcontainer features framework
61+
version_for_release="$(canonicalize_version "$VERSION")"
62+
echo "Using Bun version: $version_for_release"
2563

2664
# Figure out arch and libc to disambiguate Bun's multiple Linux assets
27-
arch_segment=""
28-
case "$(uname -m)" in
29-
x86_64)
30-
arch_segment="x64"
31-
;;
32-
aarch64|arm64)
33-
arch_segment="aarch64"
34-
;;
35-
*)
36-
# Fallback to uname -m (unlikely used by bun release naming)
37-
arch_segment="$(uname -m)"
38-
;;
39-
esac
40-
41-
# Detect musl (Alpine) vs glibc
42-
libc_suffix=""
43-
if [ -x "/sbin/apk" ]; then
44-
libc_suffix="-musl"
65+
detect_arch() {
66+
local arch
67+
arch="$(uname -m)"
68+
69+
case "$arch" in
70+
x86_64|amd64)
71+
echo "x64"
72+
;;
73+
aarch64|arm64)
74+
echo "aarch64"
75+
;;
76+
*)
77+
# Log warning for unsupported architectures
78+
echo "WARNING: Architecture '$arch' is not supported by Bun" >&2
79+
echo "WARNING: Bun only supports x64 and aarch64 architectures" >&2
80+
echo "WARNING: Available assets can be found at: https://github.com/oven-sh/bun/releases" >&2
81+
echo "$arch"
82+
;;
83+
esac
84+
}
85+
86+
echo "Detecting system architecture"
87+
if ! arch_segment="$(detect_arch)"; then
88+
echo "ERROR: Failed to detect architecture" >&2
89+
exit 1
90+
fi
91+
92+
# Detect musl vs glibc (robust detection across multiple distros)
93+
detect_libc() {
94+
# Method 1: Check for apk (Alpine)
95+
if [ -x "/sbin/apk" ]; then
96+
echo "-musl"
97+
return 0
98+
fi
99+
100+
# Method 2: Check ldd output for musl
101+
if command -v ldd >/dev/null 2>&1; then
102+
if ldd --version 2>&1 | grep -qi musl; then
103+
echo "-musl"
104+
return 0
105+
fi
106+
fi
107+
108+
# Method 3: Check for musl library files
109+
if [ -f "/lib/ld-musl-x86_64.so.1" ] || [ -f "/lib/ld-musl-aarch64.so.1" ]; then
110+
echo "-musl"
111+
return 0
112+
fi
113+
114+
# Method 4: Check /proc/mounts for musl
115+
if grep -qi musl /proc/mounts 2>/dev/null; then
116+
echo "-musl"
117+
return 0
118+
fi
119+
120+
# Default to glibc
121+
echo ""
122+
}
123+
124+
echo "Detecting libc implementation"
125+
if ! libc_suffix="$(detect_libc)"; then
126+
echo "ERROR: Failed to detect libc implementation" >&2
127+
exit 1
45128
fi
46129

47-
# Prefer baseline builds for widest CPU compatibility; exclude profile variants
48-
# Examples matched:
49-
# - bun-linux-x64-baseline.zip
50-
# - bun-linux-x64-musl-baseline.zip
51-
# - bun-linux-aarch64-baseline.zip
52-
# - bun-linux-aarch64-musl-baseline.zip
53-
asset_regex="^bun-linux-${arch_segment}${libc_suffix}-baseline\\.zip$"
130+
# Build asset regex based on architecture and libc
131+
# - x64: Use baseline builds for widest CPU compatibility
132+
# - aarch64: Use non-baseline builds since baseline doesn't exist for ARM64
133+
if [ "$arch_segment" = "x64" ]; then
134+
# For x64, try baseline first, then fallback to non-baseline
135+
asset_regex="^bun-linux-x64${libc_suffix}-baseline\\.zip$"
136+
else
137+
# For aarch64, use non-baseline since baseline doesn't exist
138+
asset_regex="^bun-linux-aarch64${libc_suffix}\\.zip$"
139+
fi
54140

55141
# Bun tags are of the form 'bun-vX.Y.Z'; constrain tag discovery accordingly
56142
release_tag_regex="^bun-v"
57143

58-
# Install Bun via gh-release helper with explicit asset/tag filters
59-
$nanolayer_location \
60-
install \
61-
devcontainer-feature \
62-
"ghcr.io/devcontainers-extra/features/gh-release:1" \
63-
--option repo='oven-sh/bun' \
64-
--option binaryNames='bun' \
65-
--option version="$version_for_release" \
66-
--option assetRegex="$asset_regex" \
67-
--option releaseTagRegex="$release_tag_regex"
144+
# Try multiple asset regex patterns for robust fallback support
145+
# Build the ordered list of asset regex patterns.
146+
# For x64, we prefer "baseline" first for widest CPU compatibility across
147+
# older/varied machines, then fall back to non-baseline. For aarch64, Bun does
148+
# not publish baseline variants, so we use standard builds.
149+
build_asset_patterns() {
150+
local arch="$1"
151+
local libc="$2"
152+
153+
case "$arch" in
154+
x64)
155+
if [ "$libc" = "-musl" ]; then
156+
echo "^bun-linux-x64-musl-baseline\\.zip$"
157+
echo "^bun-linux-x64-musl\\.zip$"
158+
else
159+
echo "^bun-linux-x64-baseline\\.zip$"
160+
echo "^bun-linux-x64\\.zip$"
161+
fi
162+
return 0
163+
;;
164+
aarch64)
165+
if [ "$libc" = "-musl" ]; then
166+
echo "^bun-linux-aarch64-musl\\.zip$"
167+
else
168+
echo "^bun-linux-aarch64\\.zip$"
169+
fi
170+
return 0
171+
;;
172+
*)
173+
echo "ERROR: Unsupported architecture '$arch'" >&2
174+
echo "ERROR: Bun only supports x64 and aarch64 architectures" >&2
175+
echo "ERROR: Available assets can be found at: https://github.com/oven-sh/bun/releases" >&2
176+
return 1
177+
;;
178+
esac
179+
}
180+
181+
# Build asset patterns for current architecture
182+
if ! asset_patterns_output=$(build_asset_patterns "$arch_segment" "$libc_suffix"); then
183+
echo "ERROR: Failed to build asset patterns for arch=$arch_segment, libc_suffix=$libc_suffix" >&2
184+
exit 1
185+
fi
186+
187+
# Convert to array - use a more reliable method
188+
# Split by newlines and create array
189+
asset_patterns=()
190+
while IFS= read -r line; do
191+
if [ -n "$line" ]; then
192+
asset_patterns+=("$line")
193+
fi
194+
done <<< "$asset_patterns_output"
195+
196+
# Verify we have asset patterns
197+
if [ ${#asset_patterns[@]} -eq 0 ]; then
198+
echo "ERROR: No asset patterns generated for arch=$arch_segment, libc_suffix=$libc_suffix" >&2
199+
echo "ERROR: asset_patterns_output was: '$asset_patterns_output'" >&2
200+
exit 1
201+
fi
202+
203+
# Try each asset pattern until one succeeds
204+
install_bun() {
205+
if [ ${#asset_patterns[@]} -eq 0 ]; then
206+
echo "ERROR: install_bun: No asset patterns provided!" >&2
207+
return 1
208+
fi
209+
210+
for asset_regex in "${asset_patterns[@]}"; do
211+
if $nanolayer_location \
212+
install \
213+
devcontainer-feature \
214+
"ghcr.io/devcontainers-extra/features/gh-release:1" \
215+
--option repo='oven-sh/bun' \
216+
--option binaryNames='bun' \
217+
--option version="$version_for_release" \
218+
--option assetRegex="$asset_regex" \
219+
--option releaseTagRegex="$release_tag_regex" 2>&1; then
220+
return 0
221+
fi
222+
done
223+
224+
echo "ERROR: Failed to install Bun. No asset patterns matched." >&2
225+
echo "ERROR: Troubleshooting information:" >&2
226+
echo "ERROR: - Architecture: $arch_segment" >&2
227+
echo "ERROR: - Libc suffix: ${libc_suffix:-(none/glibc)}" >&2
228+
echo "ERROR: - Version: $version_for_release" >&2
229+
echo "ERROR: - Asset patterns tried: ${asset_patterns[*]}" >&2
230+
echo "ERROR: Check https://github.com/oven-sh/bun/releases for available assets" >&2
231+
return 1
232+
}
233+
234+
# Attempt installation
235+
if ! install_bun; then
236+
echo "ERROR: install_bun function failed" >&2
237+
exit 1
238+
fi
239+
240+
echo "Bun installation process completed"
68241

69242
# Provide a convenient bunx shim
70243
if [ -x "/usr/local/bin/bun" ] && ! [ -x "/usr/local/bin/bunx" ]; then
@@ -75,4 +248,4 @@ EOF
75248
chmod +x /usr/local/bin/bunx
76249
fi
77250

78-
echo 'Bun installation completed.'
251+
echo "Bun installation completed successfully"

test/bun/test_specific_version.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
set -e
44

5+
# shellcheck disable=SC1091
6+
# dev-container-features-test-lib is provided by the test framework at runtime
57
source dev-container-features-test-lib
68

79
check "bun version is equal to 1.2.20" sh -c "bun --version | grep '^1.2.20'"

0 commit comments

Comments
 (0)