Skip to content

Commit 818b4e5

Browse files
committed
feat(amper): Add Amper installation.
1 parent da82397 commit 818b4e5

File tree

2 files changed

+541
-0
lines changed

2 files changed

+541
-0
lines changed

amper

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
#!/bin/sh
2+
3+
#
4+
# Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
5+
#
6+
7+
# Possible environment variables:
8+
# AMPER_DOWNLOAD_ROOT Maven repository to download Amper dist from.
9+
# default: https://packages.jetbrains.team/maven/p/amper/amper
10+
# AMPER_JRE_DOWNLOAD_ROOT Url prefix to download Amper JRE from.
11+
# default: https:/
12+
# AMPER_BOOTSTRAP_CACHE_DIR Cache directory to store extracted JRE and Amper distribution
13+
# AMPER_JAVA_HOME JRE to run Amper itself (optional, does not affect compilation)
14+
# AMPER_JAVA_OPTIONS JVM options to pass to the JVM running Amper (does not affect the user's application)
15+
# AMPER_NO_WELCOME_BANNER Disables the first-run welcome message if set to a non-empty value
16+
17+
set -e -u
18+
19+
# The version of the Amper distribution to provision and use
20+
amper_version=0.9.2
21+
# Establish chain of trust from here by specifying exact checksum of Amper distribution to be run
22+
amper_sha256=33304bb301d0c5276ad4aa718ce8a10486fda4be45179779497d2036666bb16f
23+
24+
AMPER_DOWNLOAD_ROOT="${AMPER_DOWNLOAD_ROOT:-https://packages.jetbrains.team/maven/p/amper/amper}"
25+
AMPER_JRE_DOWNLOAD_ROOT="${AMPER_JRE_DOWNLOAD_ROOT:-https:/}"
26+
27+
die() {
28+
echo >&2
29+
echo "$@" >&2
30+
echo >&2
31+
exit 1
32+
}
33+
34+
download_and_extract() {
35+
moniker="$1"
36+
file_url="$2"
37+
file_sha="$3"
38+
sha_size="$4"
39+
cache_dir="$5"
40+
extract_dir="$6"
41+
show_banner_on_cache_miss="$7"
42+
43+
if [ -e "$extract_dir/.flag" ] && [ "$(cat "$extract_dir/.flag")" = "${file_sha}" ]; then
44+
# Everything is up-to-date in $extract_dir, do nothing
45+
return 0;
46+
fi
47+
48+
mkdir -p "$cache_dir"
49+
50+
# Take a lock for the download of this file
51+
short_sha=$(echo "$file_sha" | cut -c1-32) # cannot use the ${short_sha:0:32} syntax in regular /bin/sh
52+
download_lock_file="$cache_dir/download-${short_sha}.lock"
53+
process_lock_file="$cache_dir/download-${short_sha}.$$.lock"
54+
echo $$ >"$process_lock_file"
55+
while ! ln "$process_lock_file" "$download_lock_file" 2>/dev/null; do
56+
lock_owner=$(cat "$download_lock_file" 2>/dev/null || true)
57+
if [ -n "$lock_owner" ] && ps -p "$lock_owner" >/dev/null; then
58+
echo "Another Amper instance (pid $lock_owner) is downloading $moniker. Awaiting the result..."
59+
sleep 1
60+
elif [ -n "$lock_owner" ] && [ "$(cat "$download_lock_file" 2>/dev/null)" = "$lock_owner" ]; then
61+
rm -f "$download_lock_file"
62+
# We don't want to simply loop again here, because multiple concurrent processes may face this at the same time,
63+
# which means the 'rm' command above from another script could delete our new valid lock file. Instead, we just
64+
# ask the user to try again. This doesn't 100% eliminate the race, but the probability of issues is drastically
65+
# reduced because it would involve 4 processes with perfect timing. We can revisit this later.
66+
die "Another Amper instance (pid $lock_owner) locked the download of $moniker, but is no longer running. The lock file is now removed, please try again."
67+
fi
68+
done
69+
70+
# shellcheck disable=SC2064
71+
trap "rm -f \"$download_lock_file\"" EXIT
72+
rm -f "$process_lock_file"
73+
74+
unlock_and_cleanup() {
75+
rm -f "$download_lock_file"
76+
trap - EXIT
77+
return 0
78+
}
79+
80+
if [ -e "$extract_dir/.flag" ] && [ "$(cat "$extract_dir/.flag")" = "${file_sha}" ]; then
81+
# Everything is up-to-date in $extract_dir, just release the lock
82+
unlock_and_cleanup
83+
return 0;
84+
fi
85+
86+
if [ "$show_banner_on_cache_miss" = "true" ] && [ -z "${AMPER_NO_WELCOME_BANNER:-}" ]; then
87+
echo
88+
echo ' _____ Welcome to '
89+
echo ' /:::::| ____ ___ ____ ____ __ ___ '
90+
echo ' /::/|::| |::::\_|:::\ |:::::\ /::::\ |::|/:::| '
91+
echo ' /::/ |::| |::|\:::|\::\ |::|\::\ /:/__\:\ |:::/ '
92+
echo ' /::/__|::| |::| |::| |::| |::| |::|:::::::/ |::| '
93+
echo ' /:::::::::| |::| |::| |::| |::|/::/ \::\__ |::| '
94+
echo ' /::/ |::| |::| |::| |::| |:::::/ \::::| |::| '
95+
echo ' |::| '
96+
echo " |::| v.$amper_version "
97+
echo
98+
echo "This is the first run of this version, so we need to download the actual Amper distribution."
99+
echo "Please give us a few seconds, subsequent runs will be faster."
100+
echo
101+
fi
102+
103+
echo "Downloading $moniker..."
104+
105+
temp_file="$cache_dir/download-file-$$.bin"
106+
rm -f "$temp_file"
107+
if command -v curl >/dev/null 2>&1; then
108+
if [ -t 1 ]; then CURL_PROGRESS="--progress-bar"; else CURL_PROGRESS="--silent --show-error"; fi
109+
# shellcheck disable=SC2086
110+
curl $CURL_PROGRESS -L --fail --retry 5 --connect-timeout 30 --output "${temp_file}" "$file_url"
111+
elif command -v wget >/dev/null 2>&1; then
112+
if [ -t 1 ]; then WGET_PROGRESS=""; else WGET_PROGRESS="-nv"; fi
113+
wget $WGET_PROGRESS --tries=5 --connect-timeout=30 --read-timeout=120 -O "${temp_file}" "$file_url"
114+
else
115+
die "ERROR: Please install 'wget' or 'curl', as Amper needs one of them to download $moniker"
116+
fi
117+
118+
check_sha "$file_url" "$temp_file" "$file_sha" "$sha_size"
119+
120+
rm -rf "$extract_dir"
121+
mkdir -p "$extract_dir"
122+
123+
case "$file_url" in
124+
*".zip")
125+
if command -v unzip >/dev/null 2>&1; then
126+
unzip -q "$temp_file" -d "$extract_dir"
127+
else
128+
die "ERROR: Please install 'unzip', as Amper needs it to extract $moniker"
129+
fi ;;
130+
*)
131+
if command -v tar >/dev/null 2>&1; then
132+
tar -x -f "$temp_file" -C "$extract_dir"
133+
else
134+
die "ERROR: Please install 'tar', as Amper needs it to extract $moniker"
135+
fi ;;
136+
esac
137+
138+
rm -f "$temp_file"
139+
140+
echo "$file_sha" >"$extract_dir/.flag"
141+
142+
# Unlock and cleanup the lock file
143+
unlock_and_cleanup
144+
145+
echo "Download complete."
146+
echo
147+
}
148+
149+
# usage: check_sha SOURCE_MONIKER FILE SHA_CHECKSUM SHA_SIZE
150+
# $1 SOURCE_MONIKER (e.g. url)
151+
# $2 FILE
152+
# $3 SHA hex string
153+
# $4 SHA size in bits (256, 512, ...)
154+
check_sha() {
155+
sha_size=$4
156+
if command -v shasum >/dev/null 2>&1; then
157+
echo "$3 *$2" | shasum -a "$sha_size" --status -c || {
158+
die "ERROR: Checksum mismatch for $2 (downloaded from $1): expected checksum $3 but got $(shasum --binary -a "$sha_size" "$2" | awk '{print $1}')"
159+
}
160+
return 0
161+
fi
162+
163+
shaNsumCommand="sha${sha_size}sum"
164+
if command -v "$shaNsumCommand" >/dev/null 2>&1; then
165+
echo "$3 *$2" | $shaNsumCommand -w -c || {
166+
die "ERROR: Checksum mismatch for $2 (downloaded from $1): expected checksum $3 but got $($shaNsumCommand "$2" | awk '{print $1}')"
167+
}
168+
return 0
169+
fi
170+
171+
echo "Both 'shasum' and 'sha${sha_size}sum' utilities are missing. Please install one of them"
172+
return 1
173+
}
174+
175+
# ********** System detection **********
176+
177+
kernelName=$(uname -s)
178+
arch=$(uname -m)
179+
case "$kernelName" in
180+
Darwin* )
181+
simpleOs="macos"
182+
jre_os="macosx"
183+
jre_archive_type="tar.gz"
184+
default_amper_cache_dir="$HOME/Library/Caches/JetBrains/Amper"
185+
;;
186+
Linux* )
187+
simpleOs="linux"
188+
jre_os="linux"
189+
jre_archive_type="tar.gz"
190+
default_amper_cache_dir="$HOME/.cache/JetBrains/Amper"
191+
# If linux runs in 32-bit mode, we want the "fake" 32-bit architecture, not the real hardware,
192+
# because in this mode linux cannot run 64-bit binaries.
193+
# shellcheck disable=SC2046
194+
arch=$(linux$(getconf LONG_BIT) uname -m)
195+
;;
196+
CYGWIN* | MSYS* | MINGW* )
197+
simpleOs="windows"
198+
jre_os="win"
199+
jre_archive_type=zip
200+
if command -v cygpath >/dev/null 2>&1; then
201+
default_amper_cache_dir=$(cygpath -u "$LOCALAPPDATA\JetBrains\Amper")
202+
else
203+
die "The 'cypath' command is not available, but Amper needs it. Use amper.bat instead, or try a Cygwin or MSYS environment."
204+
fi
205+
;;
206+
*)
207+
die "Unsupported platform $kernelName"
208+
;;
209+
esac
210+
211+
amper_cache_dir="${AMPER_BOOTSTRAP_CACHE_DIR:-$default_amper_cache_dir}"
212+
213+
# ********** Provision Amper distribution **********
214+
215+
amper_url="$AMPER_DOWNLOAD_ROOT/org/jetbrains/amper/amper-cli/$amper_version/amper-cli-$amper_version-dist.tgz"
216+
amper_target_dir="$amper_cache_dir/amper-cli-$amper_version"
217+
download_and_extract "Amper distribution v$amper_version" "$amper_url" "$amper_sha256" 256 "$amper_cache_dir" "$amper_target_dir" "true"
218+
219+
# ********** Provision JRE for Amper **********
220+
221+
if [ "x${AMPER_JAVA_HOME:-}" = "x" ]; then
222+
case $arch in
223+
x86_64 | x64) jre_arch="x64" ;;
224+
aarch64 | arm64) jre_arch="aarch64" ;;
225+
*) die "Unsupported architecture $arch" ;;
226+
esac
227+
228+
# Auto-updated from syncVersions.main.kts, do not modify directly here
229+
zulu_version=25.28.85
230+
java_version=25.0.0
231+
232+
pkg_type=jre
233+
platform="$jre_os $jre_arch"
234+
case $platform in
235+
"macosx x64") jre_sha256=a73455c80413daa31af6b09589e3655bb8b8b91e4aa884ca7c91dc5552b9e974 ;;
236+
"macosx aarch64") jre_sha256=37316ebea9709eb4f8bc58f0ddd2f58e53720d3e7df6f78c64125915b44d322d ;;
237+
"linux x64") jre_sha256=807e96e43db00af3390a591ed40f2c8c35f7f475fb14b6061dfb19db33702cba ;;
238+
"linux aarch64") jre_sha256=ad75e426e3f101cfa018f65fde07d82b10337d4f85250ca988474d59891c5f50 ;;
239+
"win x64") jre_sha256=d3c5db7864e6412ce3971c0b065def64942d7b0f3d02581f7f0472cac21fbba9 ;;
240+
"win aarch64") jre_sha256=f5f6d8a913695649e8e2607fe0dc79c81953b2583013ac1fb977c63cb4935bfb; pkg_type=jdk ;;
241+
*) die "Unsupported platform $platform" ;;
242+
esac
243+
244+
# URL for the JRE (see https://api.azul.com/metadata/v1/zulu/packages?release_status=ga&include_fields=java_package_features,os,arch,hw_bitness,abi,java_package_type,sha256_hash,size,archive_type,lib_c_type&java_version=25&os=macos,linux,win)
245+
# https://cdn.azul.com/zulu/bin/zulu25.28.85-ca-jre25.0.0-macosx_aarch64.tar.gz
246+
# https://cdn.azul.com/zulu/bin/zulu25.28.85-ca-jre25.0.0-linux_x64.tar.gz
247+
jre_url="$AMPER_JRE_DOWNLOAD_ROOT/cdn.azul.com/zulu/bin/zulu$zulu_version-ca-$pkg_type$java_version-${jre_os}_$jre_arch.$jre_archive_type"
248+
jre_target_dir="$amper_cache_dir/zulu$zulu_version-ca-$pkg_type$java_version-${jre_os}_$jre_arch"
249+
250+
download_and_extract "Amper runtime v$zulu_version" "$jre_url" "$jre_sha256" 256 "$amper_cache_dir" "$jre_target_dir" "false"
251+
252+
effective_amper_java_home=
253+
for d in "$jre_target_dir" "$jre_target_dir"/* "$jre_target_dir"/Contents/Home "$jre_target_dir"/*/Contents/Home; do
254+
if [ -e "$d/bin/java" ]; then
255+
effective_amper_java_home="$d"
256+
fi
257+
done
258+
259+
if [ "x${effective_amper_java_home:-}" = "x" ]; then
260+
die "Unable to find bin/java under $jre_target_dir"
261+
fi
262+
else
263+
effective_amper_java_home="$AMPER_JAVA_HOME"
264+
fi
265+
266+
java_exe="$effective_amper_java_home/bin/java"
267+
if [ '!' -x "$java_exe" ]; then
268+
die "Unable to find bin/java executable at $java_exe"
269+
fi
270+
271+
# ********** Script path detection **********
272+
273+
# We might need to resolve symbolic links here
274+
wrapper_path=$(realpath "$0")
275+
276+
# ********** Launch Amper **********
277+
278+
# In this section we construct the command line by prepending arguments from biggest to lowest precedence:
279+
# 1. basic main class, CLI arguments, and classpath
280+
# 2. user JVM args (AMPER_JAVA_OPTIONS)
281+
# 3. default JVM args (prepended last, which means they appear first, so they are overridden by user args)
282+
283+
# 1. Prepend basic launch arguments
284+
if [ "$simpleOs" = "windows" ]; then
285+
# Can't cygpath the '*' so it has to be outside
286+
classpath="$(cygpath -w "$amper_target_dir")\lib\*"
287+
else
288+
classpath="$amper_target_dir/lib/*"
289+
fi
290+
291+
set -- -cp "$classpath" org.jetbrains.amper.cli.MainKt "$@"
292+
293+
# 2. Prepend user JVM args from AMPER_JAVA_OPTS
294+
#
295+
# We use "xargs" to parse quoted JVM args from inside AMPER_JAVA_OPTS.
296+
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
297+
#
298+
# In Bash we could simply go:
299+
#
300+
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
301+
# set -- "${ARGS[@]}" "$@"
302+
#
303+
# but POSIX shell has neither arrays nor command substitution, so instead we
304+
# post-process each arg (as a line of input to sed) to backslash-escape any
305+
# character that might be a shell metacharacter, then use eval to reverse
306+
# that process (while maintaining the separation between arguments), and wrap
307+
# the whole thing up as a single "set" statement.
308+
#
309+
# This will of course break if any of these variables contains a newline or
310+
# an unmatched quote.
311+
if [ -n "${AMPER_JAVA_OPTIONS:-}" ]; then
312+
eval "set -- $(
313+
printf '%s\n' "$AMPER_JAVA_OPTIONS" |
314+
xargs -n1 |
315+
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
316+
tr '\n' ' '
317+
)" '"$@"'
318+
fi
319+
320+
# 3. Prepend default JVM args
321+
set -- \
322+
@"$amper_target_dir/amper.args" \
323+
"-Damper.wrapper.dist.sha256=$amper_sha256" \
324+
"-Damper.dist.path=$amper_target_dir" \
325+
"-Damper.wrapper.path=$wrapper_path" \
326+
"$@"
327+
328+
# Then we can launch with the overridden $@ arguments
329+
exec "$java_exe" "$@"

0 commit comments

Comments
 (0)