|
| 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