#!/usr/bin/env bash # # Copyright: Copyright Martin Nowak 2015 -. # License: Boost License 1.0 (www.boost.org/LICENSE_1_0.txt) # Authors: Martin Nowak # Documentation: https://dlang.org/install.html _() { # Returns false if the script is invoked from a Windows command prompt. posix_terminal() { # If run from a POSIX terminal (including under MSYS2 or Cygwin) the TERM # variable is defined to something like "xterm". If run from a Windows # command prompt through bash.exe from an MSYS installation, TERM keeps # its default value, which is "cygwin". if [[ "${TERM:-noterm}" == "cygwin" ]]; then false else true fi } if ! posix_terminal; then # We have been invoked from Windows cmd. Run the login script. # (Cannot use --login bash option, as that runs /etc/bash.bash_logout # afterwards which typically clears the screen, removing any output.) if [ -r /etc/profile ]; then # shellcheck disable=SC1091 source /etc/profile else fatal "Failed to source /etc/profile."; fi fi # Earliest opportunity for security settings, tolerate insecure /etc/profile. set -ueo pipefail # ------------------------------------------------------------------------------ log_() { local tilde='~' echo "${@//$HOME/$tilde}" } log() { if [ "$VERBOSITY" -gt 0 ]; then log_ "$@" fi } logV() { if [ "$VERBOSITY" -gt 1 ]; then log_ "$@" fi } logE() { log_ "$@" >&2 } fatal() { logE "$@" exit 1 } # ------------------------------------------------------------------------------ curl2() { : "${CURL_USER_AGENT:="installer/install.sh $(command curl --version | head -n 1)"}" TIMEOUT_ARGS=(--connect-timeout 5 --speed-time 30 --speed-limit 1024) command curl --fail "${TIMEOUT_ARGS[@]}" -L -A "$CURL_USER_AGENT" "$@" } curl() { if [ "$VERBOSITY" -gt 0 ]; then curl2 -# "$@" else curl2 -sS "$@" fi } retry() { for i in {0..4}; do if "$@"; then break elif [ $i -lt 4 ]; then sleep $((1 << i)) else fatal "Failed to download '$url'" fi done } # path, verify (0/1), urls... # the gpg signature is assumed to be url+.sig download() { local path do_verify mirrors path="$1" do_verify="$2" mirrors=("${@:3}") try_all_mirrors() { for i in "${!mirrors[@]}" ; do if [ "$i" -gt 0 ] ; then log "Falling back to mirror: ${mirrors[$i]}" fi if curl "${mirrors[$i]}" -o "$path" ; then return fi done return 1 } retry try_all_mirrors if [ "$do_verify" -eq 1 ]; then verify "$path" "${mirrors[@]/%/.sig}" fi } # path, urls... download_with_verify() { download "$1" 1 "${@:2}" } # path, urls... download_without_verify() { download "$1" 0 "${@:2}" } # urls... fetch() { local mirrors path path=$(mktemp "$TMP_ROOT/XXXXXX") mirrors=("$@") try_all_mirrors() { for mirror in "${mirrors[@]}" ; do if curl2 -sS "$mirror" -o "$path" ; then return fi done return 1 } retry try_all_mirrors cat "$path" rm "$path" } # ------------------------------------------------------------------------------ HAVE_CYGPATH=no if command -v cygpath &>/dev/null; then HAVE_CYGPATH=yes fi posix_path() { if [[ "$HAVE_CYGPATH" == "yes" ]]; then cygpath "$1" else echo "$1" fi } display_path() { if [[ "$HAVE_CYGPATH" == "yes" ]]; then if posix_terminal; then cygpath "$1" else cygpath -w "$1" fi else echo "$1" fi } abspath() { if [[ -d "$1" ]] then pushd "$1" >/dev/null pwd popd >/dev/null elif [[ -e "$1" ]] then pushd "$(dirname "$1")" >/dev/null echo "$(pwd)/$(basename "$1")" popd >/dev/null else echo "$1" does not exist! >&2 return 127 fi } COMMAND= COMPILER=dmd VERBOSITY=1 GET_PATH_AUTO_INSTALL=0 GET_PATH_COMPILER=dc # Set a default install path depending on the POSIX/Windows environment. if posix_terminal; then ROOT=~/dlang else # Default to a ROOT that is outside the POSIX-like environment. if [ -z "$USERPROFILE" ]; then fatal '%USERPROFILE% should not be empty on Windows.'; fi ROOT=$(posix_path "$USERPROFILE")/dlang fi TMP_ROOT= DUB_VERSION= DUB_BIN_PATH= case $(uname -s) in Darwin) OS=osx;; Linux) OS=linux;; FreeBSD) OS=freebsd;; *_NT-*) OS=windows;; *) fatal "Unsupported OS $(uname -s)" ;; esac check_tools() { while [[ $# -gt 0 ]]; do if ! command -v "$1" &>/dev/null && # detect OSX' liblzma support in libarchive ! { [ "$OS-$1" == osx-xz ] && otool -L /usr/lib/libarchive.*.dylib | grep -qF liblzma; }; then local msg="Required tool $1 not found, please install it." case $OS-$1 in osx-xz) msg="$msg http://macpkg.sourceforge.net";; esac fatal "$msg" fi shift done } # ------------------------------------------------------------------------------ mkdtemp() { mktemp -d "$TMP_ROOT/XXXXXX" } cleanup() { if [[ -n $TMP_ROOT ]]; then rm -rf "$TMP_ROOT"; fi } trap cleanup EXIT # ------------------------------------------------------------------------------ usage() { log 'Usage install.sh [] [] Commands install Install a D compiler (default command) uninstall Remove an installed D compiler list List all installed D compilers update Update this dlang script get-path Path of the installed compiler executable Options -h --help Show this help -p --path Install location POSIX default: ~/dlang Windows default: %USERPROFILE%\dlang -v --verbose Verbose output Run "install.sh --help to get help for a specific command, or consult https://dlang.org/install.html for documentation. If no arguments are provided, the latest DMD compiler will be installed. ' } command_help() { if [ -z "${1-}" ]; then usage return fi local _compiler='Compiler dmd|gdc|ldc latest version of a compiler dmd|gdc|ldc- specific version of a compiler (e.g. dmd-2.071.1, ldc-1.1.0-beta2) dmd|ldc-beta latest beta version of a compiler dmd-nightly latest dmd nightly ldc-latest-ci latest ldc CI build (with assertions enabled) dmd-2016-08-08 specific dmd nightly ' case $1 in install) log 'Usage install.sh install Description Download and install a D compiler. By default the latest release of the DMD compiler is selected. Options -a --activate Only print the path to the activate script Examples install.sh install.sh dmd install.sh install dmd install.sh install dmd-2.071.1 install.sh install ldc-1.1.0-beta2 ' log "$_compiler" ;; uninstall) log 'Usage install.sh uninstall Description Uninstall a D compiler. Examples install.sh uninstall dmd install.sh uninstall dmd-2.071.1 install.sh uninstall ldc-1.1.0-beta2 ' log "$_compiler" ;; list) log 'Usage install.sh list Description List all installed D compilers. ' ;; update) log 'Usage install.sh update Description Update the dlang installer itself and the keyring. ' ;; get-path) log 'Usage install.sh get-path Description Find the path of an installed D compiler. Options --install Install the compiler if it is not installed --dmd Find the DMD-alike interface instead --dub Find the DUB instance Examples install.sh install.sh get-path dmd install.sh get-path dmd-2.093.0 --install install.sh get-path --dmd ldc-1.19.0 install.sh get-path --dub ldc-1.19.0 install.sh get-path ldc-1.19.0 --install install.sh get-path --dub dub-1.21.0 ' log "$_compiler" ;; esac } # ------------------------------------------------------------------------------ parse_args() { local _help= local _installAction= local _getPathAction= local _autoInstallFlag= while [[ $# -gt 0 ]]; do case "$1" in -h | --help) _help=1 ;; -p | --path) if [ -z "${2:-}" ]; then fatal '-p|--path must be followed by a path.'; fi shift ROOT="$(posix_path "$1")"; ;; -v | --verbose) VERBOSITY=2 ;; -a | --activate) if [ -n "$_installAction" ]; then fatal "$1 conflicts with --${_installAction}" fi _installAction="activate" ;; --arch) shift ARCH_OVERRIDE=$1; ;; --dmd) if [ -n "$_getPathAction" ]; then fatal "$1 conflicts with --${_getPathAction}" fi _getPathAction="dmd" ;; --dub) if [ -n "$_getPathAction" ]; then fatal "$1 conflicts with --${_getPathAction}" fi _getPathAction="dub" ;; --install) _autoInstallFlag=1 ;; use | install | uninstall | list | update) COMMAND=$1 ;; get-path) COMMAND=$1 ;; remove) COMMAND=uninstall ;; dmd | dmd-* | gdc | gdc-* | ldc | ldc-* | dub | dub-* | \ dmd,* | ldc,* | gdc,* ) COMPILER=$1 ;; *) usage fatal "Unrecognized command-line parameter: $1" ;; esac shift done mkdir -p "$ROOT" TMP_ROOT=$(mktemp -d "$ROOT/.installer_tmp_XXXXXX") if [ -n "$_help" ]; then command_help $COMMAND exit 0 fi local command_="${COMMAND:-install}" if [ -n "$_installAction" ] ; then if [ "$command_" == "install" ]; then VERBOSITY=0 else logE "ERROR: --activate is not allowed for ${command_}." command_help $COMMAND exit 1 fi fi if [ -n "$_autoInstallFlag" ] ; then if [ "$command_" == "get-path" ]; then GET_PATH_AUTO_INSTALL=1 else logE "ERROR: --install is not allowed for ${command_}." command_help $COMMAND exit 1 fi fi if [ -n "$_getPathAction" ] ; then if [ "$command_" == "get-path" ]; then case "$_getPathAction" in dmd|dub) GET_PATH_COMPILER=$_getPathAction ;; *) fatal "Internal Error. Invalid get-path: $_getPathAction" esac else logE "ERROR: --${_getPathAction} is not allowed for ${command_}." command_help $COMMAND exit 1 fi fi IFS="," read -r _compiler _dub <<< "$COMPILER" if [ -n "${_dub:-}" ] ; then COMPILER="$_compiler" DUB="$_dub" fi } # ------------------------------------------------------------------------------ # run_command command [compiler] run_command() { case $1 in install) install_d "$1" "${2:-}" if posix_terminal; then if [ "$(basename "$SHELL")" = fish ]; then local suffix=.fish fi if [ "$VERBOSITY" -eq 0 ]; then echo "$ROOT/$2/activate${suffix:-}" else log " Run \`source $ROOT/$2/activate${suffix:-}\` in your shell to use $2. This will setup PATH, LIBRARY_PATH, LD_LIBRARY_PATH, DMD, DC, and PS1. Run \`deactivate\` later on to restore your environment." fi else if [ "$VERBOSITY" -eq 0 ]; then display_path "$ROOT/$2/activate.bat" else log " Run \`$(display_path "$ROOT/$2/activate.bat")\` to add $2 to your PATH." fi fi ;; get-path) if [ $GET_PATH_AUTO_INSTALL -eq 0 ] ; then if [ ! -d "$ROOT/$2" ]; then fatal "Requested $2 is not installed. Install or rerun with --install."; fi if [ "$GET_PATH_COMPILER" == "dub" ] ; then local dubBinPath dubBinPath="$(binexec_for_dub_compiler "$2")" if [ ! -f "$dubBinPath" ] ; then fatal "Requested DUB is not installed. Install or rerun with --install."; fi fi fi previousVerbosity=$VERBOSITY VERBOSITY=-1 install_d "$1" "${2:-}" VERBOSITY=$previousVerbosity case "$GET_PATH_COMPILER" in dmd) if [[ $COMPILER =~ ^dub ]] ; then fatal "ERROR: DUB is not a compiler." fi echo "$ROOT/$2/$(binexec_for_dmd_compiler "$2")" ;; dc) if [[ $COMPILER =~ ^dub ]] ; then fatal "ERROR: DUB is not a compiler." fi echo "$ROOT/$2/$(binexec_for_dc_compiler "$2")" ;; dub) binexec_for_dub_compiler "$2" ;; *) fatal "Unknown value for GET_PATH_COMPILER encountered." esac ;; uninstall) if [ -z "${2:-}" ]; then fatal "Missing compiler argument for $1 command."; fi uninstall_compiler "$2" ;; list) list_compilers ;; update) install_dlang_installer ;; esac } install_d() { local commandName="$1" if [ -z "${2:-}" ]; then fatal "Missing compiler argument for $commandName command."; fi local compilerName="$2" check_tools curl if [ ! -f "$ROOT/install.sh" ] || [ ! -f "$ROOT/d-keyring.gpg" ] ; then install_dlang_installer fi if [ -d "$ROOT/$compilerName" ]; then log "$compilerName already installed"; else install_compiler "$compilerName" fi # Only try to install dub if it wasn't the main compiler if ! [[ $COMPILER =~ ^dub ]] ; then if [ -n "${DUB:-}" ] ; then # A dub version was explicitly specified with ,dub-1.8.0 DUB_BIN_PATH="${ROOT}/${DUB}" # Check whether the latest dub version needs to be resolved if ! [[ $DUB =~ ^dub- ]] ; then resolve_latest "$DUB" install_dub "dub-$DUB_VERSION" else install_dub "$DUB" fi else # compiler was installed without requesting dub local -r binpath=$(binpath_for_compiler "$compilerName") if [ -f "$ROOT/$2/$binpath/dub" ]; then if [[ $("$ROOT/$2/$binpath/dub" --version) =~ ([0-9]+\.[0-9]+\.[0-9]+(-[^, ]+)?) ]]; then log "Using dub ${BASH_REMATCH[1]} shipped with $compilerName" else log "Using dub shipped with $compilerName" fi else # no dub bundled - manually installing DUB_BIN_PATH="${ROOT}/dub" resolve_latest dub install_dub "dub-$DUB_VERSION" fi fi fi write_env_vars "$compilerName" } install_dlang_installer() { local tmp mirrors tmp=$(mkdtemp) local mirrors=( "https://dlang.org/install.sh" "https://downloads.dlang.org/other/install.sh" ) local keyring_mirrors=( "https://dlang.org/d-keyring.gpg" "https://downloads.dlang.org/other/d-keyring.gpg" ) mkdir -p "$ROOT" log "Downloading https://dlang.org/d-keyring.gpg" if [ ! -f "$ROOT/d-keyring.gpg" ]; then download_without_verify "$tmp/d-keyring.gpg" "${keyring_mirrors[@]}" else download_with_verify "$tmp/d-keyring.gpg" "${keyring_mirrors[@]}" fi mv "$tmp/d-keyring.gpg" "$ROOT/d-keyring.gpg" log "Downloading ${mirrors[0]}" download_with_verify "$tmp/install.sh" "${mirrors[@]}" mv "$tmp/install.sh" "$ROOT/install.sh" rmdir "$tmp" chmod +x "$ROOT/install.sh" log "The latest version of this script was installed as $ROOT/install.sh. It can be used it to install further D compilers. Run \`$ROOT/install.sh --help\` for usage information. " } resolve_latest() { local input_compiler=${1:-$COMPILER} case $input_compiler in dmd) local mirrors=( "https://downloads.dlang.org/releases/LATEST" "http://ftp.digitalmars.com/LATEST" ) logV "Determing latest dmd version (${mirrors[0]})." COMPILER="dmd-$(fetch "${mirrors[@]}")" ;; dmd-beta) local mirrors=( "https://downloads.dlang.org/pre-releases/LATEST" "http://ftp.digitalmars.com/LATEST_BETA" ) logV "Determing latest dmd-beta version (${mirrors[0]})." COMPILER="dmd-$(fetch "${mirrors[@]}")" ;; dmd-nightly) COMPILER="dmd-nightly" ;; dmd-*) # nightly master or feature branch # dmd-nightly, dmd-master, dmd-branch # but not: dmd-2016-10-19 or dmd-branch-2016-10-20 # dmd-2.068.0 or dmd-2.068.2-5 # dmd-2.064 or dmd-2.064-0 if [[ ! $input_compiler =~ -[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] && [[ ! $input_compiler =~ -[0-9][.][0-9]{3}[.][0-9]{1,3}(-[0-9]{1,3})? ]] && [[ ! $input_compiler =~ -[0-9][.][0-9]{3}(.[0-9]{1,3})? ]]; then local url=https://downloads.dlang.org/nightlies/$input_compiler/LATEST logV "Determing latest $input_compiler version ($url)." COMPILER="dmd-$(fetch "$url")" # rewrite dmd-2016-10-19 -> dmd-master-2016-10-19 (default branch for nightlies) elif [[ $COMPILER =~ ^dmd-([0-9]{4}-[0-9]{2}-[0-9]{2})$ ]]; then COMPILER="dmd-master-${BASH_REMATCH[1]}" fi ;; ldc) local url=https://ldc-developers.github.io/LATEST logV "Determing latest ldc version ($url)." COMPILER="ldc-$(fetch $url)" ;; ldc-beta) local url=https://ldc-developers.github.io/LATEST_BETA logV "Determining latest ldc-beta version ($url)." COMPILER="ldc-$(fetch $url)" ;; ldc-latest-ci) local url=https://thecybershadow.net/d/github-ldc logV "Finding latest ldc CI binary package (at $url)." local package package="$(fetch $url)" if [[ $package =~ ldc2-([0-9a-f]*)-$OS-$ARCH. ]]; then COMPILER="ldc-${BASH_REMATCH[1]}" else fatal "Could not find ldc CI binaries (OS: $OS, arch: $ARCH)" fi ;; ldc-*) ;; gdc) local url=https://gdcproject.org/downloads/LATEST logV "Determing latest gdc version ($url)." COMPILER="gdc-$(fetch $url)" ;; gdc-*) ;; dub) local mirrors=( "https://dlang.github.io/dub/LATEST" "https://code.dlang.org/download/LATEST" ) logV "Determining latest dub version (${mirrors[0]})." DUB_VERSION="$(fetch "${mirrors[@]}" | sed 's/^v//')" local DUB="dub-${DUB_VERSION}" if [ "$COMPILER" == "dub" ] ; then COMPILER="$DUB" fi ;; dub-*) ;; *) fatal "Invalid compiler: $COMPILER" esac } install_compiler() { local compiler="$1" # dmd-2.065, dmd-2.068.0, dmd-2.068.1-b1 if [[ $1 =~ ^dmd-2\.([0-9]{3})(\.[0-9])?(-.*)?$ ]]; then local basename="dmd.2.${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[3]}" local ver="2.${BASH_REMATCH[1]}" if [[ $ver > "2.064z" ]]; then basename="$basename.$OS" ver="$ver${BASH_REMATCH[2]}" if [ $OS = freebsd ]; then basename="$basename-$MODEL" fi fi if [[ $OS == windows && $ver > "2.068.0" ]] && command -v 7z &>/dev/null; then local ext=".7z" elif [[ $ver > "2.068.0z" && $OS != windows ]]; then local ext=".tar.xz" else local ext=".zip" fi if [[ $ARCH != x86* ]]; then fatal "no DMD binaries available for $ARCH" fi local mirrors if [ -n "${BASH_REMATCH[3]}" ]; then # pre-release mirrors=( "https://downloads.dlang.org/pre-releases/2.x/$ver/$basename$ext" "http://ftp.digitalmars.com/$basename$ext" ) else mirrors=( "https://downloads.dlang.org/releases/2.x/$ver/$basename$ext" "http://ftp.digitalmars.com/$basename$ext" ) fi download_and_unpack_with_verify "$ROOT/$compiler" "${mirrors[@]}" # dmd-2015-11-20, dmd-feature_branch-2016-10-20 elif [[ $1 =~ ^dmd(-(.*))?-[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then local branch=${BASH_REMATCH[2]:-master} local basename="dmd.$branch.$OS" if [ $OS = freebsd ]; then basename="$basename-$MODEL" fi if [[ $ARCH != x86* ]]; then fatal "no DMD binaries available for $ARCH" fi local ext test $OS = windows && ext=.7z || ext=.tar.xz local url="https://downloads.dlang.org/nightlies/$1/$basename$ext" download_and_unpack_with_verify "$ROOT/$compiler" "$url" # Nightlies uploaded to the DMD repo elif [[ $1 == dmd-nightly ]]; then local baseUrl="https://github.com/dlang/dmd/releases/download/nightly/dmd.master" local ext test $OS = windows && ext=7z || ext=tar.xz local mod= test $OS = freebsd && mod=-64 download_and_unpack_without_verify "$ROOT/$compiler" "$baseUrl.$OS$mod.$ext" # Create symlink `dmd-master => dmd-nightly` to be compatible with the old behaviour ln -s "$ROOT/$compiler" "$ROOT/dmd-master" # ldc-0.12.1 or ldc-0.15.0-alpha1 elif [[ $1 =~ ^ldc-(([0-9]+)\.([0-9]+)\.[0-9]+(-.*)?)$ ]]; then local ver=${BASH_REMATCH[1]} local vernum=$((BASH_REMATCH[2] * 1000 + BASH_REMATCH[3])) local ext test $OS = windows && ext=.7z || ext=.tar.xz local url="https://github.com/ldc-developers/ldc/releases/download/v$ver/ldc2-$ver-$OS-$ARCH$ext" if [[ $OS == windows && $vernum -lt 1007 ]]; then url="https://github.com/ldc-developers/ldc/releases/download/v$ver/ldc2-$ver-win$MODEL-msvc.zip" fi download_and_unpack_without_verify "$ROOT/$compiler" "$url" # ldc-latest-ci: ldc-8c0abd52 elif [[ $1 =~ ^ldc-([0-9a-f]+) ]]; then local package_hash=${BASH_REMATCH[1]} local ext test $OS = windows && ext=.7z || ext=.tar.xz local url="https://github.com/ldc-developers/ldc/releases/download/CI/ldc2-$package_hash-$OS-$ARCH$ext" # Install into 'ldc-8c0abd52-20171222' directory. download_and_unpack_without_verify "$ROOT/$compiler" "$url" # gdc-4.8.2, gdc-4.9.0-alpha1, gdc-5.2, or gdc-5.2-alpha1 elif [[ $1 =~ ^gdc-([0-9]+\.[0-9]+(\.[0-9]+)?(-.*)?)$ ]]; then local name=${BASH_REMATCH[0]} if [ $OS != linux ]; then fatal "no gdc binaries available for $OS" fi case $ARCH in x86_64) local triplet=x86_64-linux-gnu;; x86) local triplet=i686-linux-gnu;; esac local url="https://gdcproject.org/downloads/binaries/$triplet/$name.tar.xz" download_and_unpack_without_verify "$ROOT/$compiler" "$url" url=https://raw.githubusercontent.com/D-Programming-GDC/GDMD/a67179d54611ae8cfb1d791cf7ab8e36c3224b76/dmd-script log "Downloading gdmd $url" download_without_verify "$ROOT/$1/bin/gdmd" "$url" chmod +x "$ROOT/$1/bin/gdmd" elif [[ $1 =~ ^dub-v?([0-9]+\.[0-9]+(\.[0-9]+)?(-.*)?)$ ]]; then install_dub "$1" else fatal "Unknown compiler '$compiler'" fi } find_gpg() { if command -v gpg2 &>/dev/null; then echo gpg2 elif command -v gpg &>/dev/null; then echo gpg else echo "Warning: No gpg tool found to verify downloads." >&2 fi } unpack_zip() { local zip=$1 local dir=$2 ( # Avoid invoking Windows programs with absolute paths, as # POSIX environments on Windows are unreliable in converting # POSIX paths to Windows paths on programs' command lines. # Use relative paths instead. cd "$ROOT" zip=${zip/$ROOT\//} dir=${dir/$ROOT\//} if command -v 7z &>/dev/null; then 7z x -o"$dir" "$zip" 1>&2 else # Allow unzip to exit with code 1, which it uses to signal warnings # such as ".zip appears to use backslashes as path separators". unzip -q -d "$dir" "$zip" || [ $? -le 1 ] fi ) } # path, verify (0/1), urls... # the gpg signature is assumed to be url+.sig download_and_unpack() { local path do_verify urls path="$1" do_verify="$2" urls=("${@:3}") local tmp name tmp=$(mkdtemp) name="$(basename "${urls[0]}")" check_tools curl if [[ $name =~ \.tar\.xz$ ]]; then check_tools tar xz elif [[ $name =~ \.7z$ ]]; then check_tools 7z elif [[ $name =~ \.zip$ ]]; then if ! command -v 7z &>/dev/null; then check_tools unzip fi fi log "Downloading and unpacking ${urls[0]}" download "$tmp/$name" "$do_verify" "${urls[@]}" if [[ $name =~ \.tar\.xz$ ]]; then tar --strip-components=1 -C "$tmp" -Jxf "$tmp/$name" elif [[ $name =~ \.tar\.gz$ ]]; then tar --strip-components=1 -C "$tmp" -xf "$tmp/$name" else mkdir "$tmp"/target unpack_zip "$tmp/$name" "$tmp"/target local files=("$tmp"/target/*) if [[ "${#files[@]}" -eq 1 && -d "${files[0]}" ]]; then # Single directory at the top of the archive, # as is common with .tar archives. Move it out. find "$tmp"/target -mindepth 2 -maxdepth 2 -exec sh -c "exec mv "\$@" '$tmp'" sh {} \+ rmdir "$tmp"/target/* else find "$tmp"/target -mindepth 1 -maxdepth 1 -exec sh -c "exec mv "\$@" '$tmp'" sh {} \+ fi rmdir "$tmp"/target fi rm "$tmp/$name" mv "$tmp" "$path" } # path, urls... download_and_unpack_with_verify() { download_and_unpack "$1" 1 "${@:2}" } # path, urls... download_and_unpack_without_verify() { download_and_unpack "$1" 0 "${@:2}" } # path, urls... verify() { local path urls path="$1" urls=("${@:2}") : "${GPG:=$(find_gpg)}" if [ -z "$GPG" ]; then return fi if ! $GPG --list-keys >/dev/null; then fatal "Broken GPG installation" fi local out if ! out=$(fetch "${urls[@]}" | $GPG -q --verify --keyring "$ROOT/d-keyring.gpg" --no-default-keyring - "$path" 2>&1); then rm "$path" # delete invalid files logE "$out" fatal "Invalid signature ${urls[0]}" fi } binpath_for_compiler() { case $1 in dmd*) local suffix= [ $OS = osx ] || [ $OS = windows ] || suffix=$MODEL local -r binpath=$OS/bin$suffix ;; ldc*) local -r binpath=bin ;; gdc*) local -r binpath=bin ;; dub*) local -r binpath= ;; esac echo "$binpath" } # Path to the compiler executable with a DMD-compatible interface binexec_for_dmd_compiler() { local binPath binPath=$(binpath_for_compiler "$1")/ case $1 in dmd*) binPath+=dmd ;; ldc*) binPath+=ldmd2 ;; gdc*) binPath+=gdmd ;; esac echo "$binPath" } # Path to the compiler executable with its custom interface binexec_for_dc_compiler() { local binPath binPath=$(binpath_for_compiler "$1")/ case $1 in dmd*) binPath+=dmd ;; ldc*) binPath+=ldc2 ;; gdc*) binPath+=gdc ;; esac echo "$binPath" } binexec_for_dub_compiler() { local dub binPath dub="${DUB:-}" if [[ "$dub" =~ ^dub- ]] ; then binPath="$ROOT/$DUB/dub" elif [ "$dub" == "dub" ] ; then binPath="$ROOT/$DUB/dub" else binPath="$ROOT/$1/$(binpath_for_compiler "$1")" case $1 in dub*) binPath+=dub ;; dmd*|ldc*|gdc*) binPath+=/dub ;; esac # check for old compiler which ship without dub if [ ! -f "$binPath" ]; then binPath="$ROOT/dub/dub" fi fi echo "$binPath" } write_env_vars() { ROOT_ABS="$(abspath "$ROOT")" local -r binpath=$(binpath_for_compiler "$1") case $1 in dmd*) local suffix= [ $OS = osx ] || suffix=$MODEL local libpath=$OS/lib$suffix local dc=dmd local dmd=dmd ;; ldc*) local libpath=lib local dc=ldc2 local dmd=ldmd2 ;; gdc*) if [ -d "$ROOT/$1/lib$MODEL" ]; then local libpath=lib$MODEL else # older gdc releases only ship 64-bit libs local libpath=lib fi local dc=gdc local dmd=gdmd ;; dub*) local libpath= local dc= local dmd= ;; esac logV "Writing environment variables to $ROOT/$1/activate" { # when activate is called twice, deactivate the prior context first echo "if [ ! -z \${_OLD_D_PATH+x} ] ; then deactivate; fi" echo echo "deactivate() {" echo " export PATH=\"\$_OLD_D_PATH\"" if [ -n "$libpath" ] ; then echo " export LIBRARY_PATH=\"\$_OLD_D_LIBRARY_PATH\"" echo " export LD_LIBRARY_PATH=\"\$_OLD_D_LD_LIBRARY_PATH\"" echo " unset _OLD_D_LIBRARY_PATH" echo " unset _OLD_D_LD_LIBRARY_PATH" fi if [ -n "$dmd" ] ; then echo " unset DMD" echo " unset DC" fi echo " export PS1=\"\$_OLD_D_PS1\"" echo " unset _OLD_D_PATH" echo " unset _OLD_D_PS1" echo " unset -f deactivate" echo "}" echo echo "_OLD_D_PATH=\"\${PATH:-}\"" if [ -n "$libpath" ] ; then echo "_OLD_D_LIBRARY_PATH=\"\${LIBRARY_PATH:-}\"" echo "_OLD_D_LD_LIBRARY_PATH=\"\${LD_LIBRARY_PATH:-}\"" echo "export LIBRARY_PATH=\"$ROOT_ABS/$1/$libpath\${LIBRARY_PATH:+:}\${LIBRARY_PATH:-}\"" echo "export LD_LIBRARY_PATH=\"$ROOT_ABS/$1/$libpath\${LD_LIBRARY_PATH:+:}\${LD_LIBRARY_PATH:-}\"" fi echo "_OLD_D_PATH=\"\${PATH:-}\"" echo "_OLD_D_PS1=\"\${PS1:-}\"" echo "export PS1=\"($1)\${PS1:-}\"" echo "export PATH=\"${DUB_BIN_PATH}${DUB_BIN_PATH:+:}$ROOT_ABS/$1/$binpath\${PATH:+:}\${PATH:-}\"" if [ -n "$dmd" ] ; then echo "export DMD=$dmd" echo "export DC=$dc" fi } > "$ROOT/$1/activate" logV "Writing environment variables to $ROOT/$1/activate.fish" { echo "function deactivate" echo " set -gx PATH \$_OLD_D_PATH" if [ -n "$libpath" ] ; then echo " set -gx LIBRARY_PATH \$_OLD_D_LIBRARY_PATH" echo " set -gx LD_LIBRARY_PATH \$_OLD_D_LD_LIBRARY_PATH" echo " set -e _OLD_D_LIBRARY_PATH" echo " set -e _OLD_D_LD_LIBRARY_PATH" fi echo " functions -e fish_prompt" echo " functions -c _old_d_fish_prompt fish_prompt" echo " functions -e _old_d_fish_prompt" echo echo " set -e _OLD_D_PATH" if [ -n "$dmd" ] ; then echo " set -e DMD" echo " set -e DC" fi echo " functions -e deactivate" echo "end" echo echo "set -g _OLD_D_PATH \$PATH" echo "set -g _OLD_D_PS1 \$PS1" echo echo "set -gx PATH ${DUB_BIN_PATH:+\'}${DUB_BIN_PATH}${DUB_BIN_PATH:+\' }'$ROOT_ABS/$1/$binpath' \$PATH" if [ -n "$libpath" ] ; then echo "set -g _OLD_D_LIBRARY_PATH \$LIBRARY_PATH" echo "set -g _OLD_D_LD_LIBRARY_PATH \$LD_LIBRARY_PATH" echo "set -gx LIBRARY_PATH '$ROOT_ABS/$1/$libpath' \$LIBRARY_PATH" echo "set -gx LD_LIBRARY_PATH '$ROOT_ABS/$1/$libpath' \$LD_LIBRARY_PATH" fi if [ -n "$dmd" ] ; then echo "set -gx DMD $dmd" echo "set -gx DC $dc" fi echo "functions -c fish_prompt _old_d_fish_prompt" echo "function fish_prompt" echo " printf '($1)%s' (_old_d_fish_prompt)" echo "end" } > "$ROOT/$1/activate.fish" if [[ $OS == windows ]]; then logV "Writing environment variables to $ROOT/$1/activate.bat" { local -r winpath=$(cygpath -w "$ROOT/$1/$binpath") echo "@echo off" echo "if not \"%PATH:${winpath}=%\"==\"%PATH%\" (" echo " echo $1 is already active." echo " goto :EOF" echo ")" echo "echo Adding $1 to your PATH." echo "echo Run \`set PATH=%%_OLD_D_PATH%%\` later on to restore your PATH." echo "set _OLD_D_PATH=%PATH%" echo "set PATH=${DUB_BIN_PATH:+$(cygpath -w "${DUB_BIN_PATH}");}${winpath};%PATH%" if [[ $PROCESSOR_ARCHITECTURE != x86 ]]; then echo "set PATH=${winpath}64;%PATH%" fi } > "$ROOT/$1/activate.bat" fi } uninstall_compiler() { if [ ! -d "$ROOT/$1" ]; then fatal "$1 is not installed in $ROOT" fi log "Removing $(display_path "$ROOT/$1")" rm -rf "${ROOT:?}/$1" # Remove the compatibility symlink for the nightlies if [ "$1" == "dmd-nightly" ]; then rm -rf "${ROOT:?}/dmd-master" fi } list_compilers() { check_tools find if [ -d "$ROOT" ]; then find "$ROOT" \ -mindepth 1 \ -maxdepth 1 \ -not -name 'dub*' \ -not -name install.sh \ -not -name d-keyring.gpg \ -not -name '.*' \ -exec basename {} \; | \ grep . # fail if none found fi } install_dub() { if [ $OS != linux ] && [ $OS != windows ] && [ $OS != osx ]; then log "no dub binaries available for $OS" return fi local dub="$1" if [ -d "$ROOT/$dub" ]; then log "$dub already installed" return fi local tmp url tmp=$(mkdtemp) dubVersion=${dub##dub-} local ext=.tar.gz arch=$ARCH if [[ $OS == windows ]]; then ext=.zip if [[ "$dubVersion" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then local vernum=$((BASH_REMATCH[1] * 1000000 + BASH_REMATCH[2] * 1000 + BASH_REMATCH[3])) if [[ $vernum -lt 1014000 ]]; then # Older releases, pre-1.14.0, were on code.dlang.org, # and did not have x86_64 versions. arch=x86 elif [[ $vernum -lt 1020000 ]]; then # Releases since 1.14.0 have x86_64 archives on GitHub. # However, these are corrupted (https://github.com/dlang/dub/issues/1795). # At this point it's unclear if the archives are going to be fixed, # so just install them for now. : fi fi fi local mirrors=( "https://github.com/dlang/dub/releases/download/v${dubVersion}/dub-v${dubVersion}-$OS-$arch$ext" "https://code.dlang.org/files/$dub-$OS-$arch$ext" ) log "Downloading and unpacking ${mirrors[0]}" download_without_verify "$tmp/dub$ext" "${mirrors[@]}" if [[ $ext == .tar.gz ]]; then tar -C "$tmp" -zxf "$tmp/dub$ext" else unpack_zip "$tmp/dub$ext" "$tmp" fi logV "Removing old dub symlink" rm -rf "$ROOT/dub" mv "$tmp" "$ROOT/$dub" logV "Linking $ROOT/dub -> $ROOT/$dub" ln -s "$dub" "$ROOT/dub" } # ------------------------------------------------------------------------------ parse_args "$@" case $(uname -m) in x86_64|amd64) ARCH=x86_64; MODEL=64;; aarch64|arm64) ARCH=aarch64; MODEL=64;; i*86) ARCH=x86; MODEL=32;; *) fatal "Unsupported Arch $(uname -m)" ;; esac if [[ ${ARCH_OVERRIDE:+1} ]]; then ARCH=$ARCH_OVERRIDE fi if [[ $OS-$ARCH = windows-x86_64 && $COMPILER = ldc* ]]; then ARCH=x64 fi if [[ $OS-$ARCH = osx-aarch64 ]]; then if [[ $COMPILER == dmd* ]]; then log " DMD does not have builds for macOS on aarch64/arm64 architecture. Switching to x86_64 architecture (requires Rosetta). " ARCH=x86_64 fi if [[ $COMPILER == ldc* ]]; then log " LDC has builds for macOS on aarch64/arm64 architecture since ldc-1.25.0. If you are installing an earlier version and get a download error, try '--arch x86_64' to install the x86_64 version instead (requires Rosetta). Use '--arch universal' to install the universal LDC package that can target arm64 (native) and x86_64 (Rosetta). " ARCH=arm64 fi fi resolve_latest "$COMPILER" run_command ${COMMAND:-install} "$COMPILER" } _ "$@"