├── .shellcheckrc ├── bin ├── list-legacy-filenames ├── list-bin-paths ├── exec-env ├── get-version-from-legacy-file ├── latest-stable ├── list-all └── install ├── shims └── mix ├── scripts └── lint ├── LICENSE ├── .github └── workflows │ └── workflow.yml └── README.md /.shellcheckrc: -------------------------------------------------------------------------------- 1 | disable=SC2016 2 | -------------------------------------------------------------------------------- /bin/list-legacy-filenames: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo ".exenv-version" 4 | -------------------------------------------------------------------------------- /shims/mix: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env elixir 2 | # asdf-plugin: elixir 3 | Mix.start 4 | Mix.CLI.main 5 | -------------------------------------------------------------------------------- /bin/list-bin-paths: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # let asdf create shims for installed escripts 3 | echo "bin .mix/escripts" 4 | -------------------------------------------------------------------------------- /bin/exec-env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "${MIX_HOME:-}" = "" ]; then 4 | export MIX_HOME=$ASDF_INSTALL_PATH/.mix 5 | fi 6 | 7 | if [ "${MIX_ARCHIVES:-}" = "" ]; then 8 | export MIX_ARCHIVES=${MIX_HOME:-}/archives 9 | fi 10 | -------------------------------------------------------------------------------- /bin/get-version-from-legacy-file: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | get_legacy_version() { 4 | current_directory=$1 5 | exenv_version_file="$current_directory/.exenv-version" 6 | 7 | # Get version from .exenv-version file. It is used by exenv. 8 | if [ -f "$exenv_version_file" ]; then 9 | cat "$exenv_version_file" 10 | fi 11 | } 12 | 13 | get_legacy_version "$1" 14 | -------------------------------------------------------------------------------- /scripts/lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Unoffical Bash "strict mode" 4 | # http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -euo pipefail 6 | #ORIGINAL_IFS=$IFS 7 | IFS=$'\t\n' # Stricter IFS settings 8 | 9 | # This script exists to make linting during local development easy. These checks 10 | # are identical to the ones run by the Github workflow. 11 | 12 | # Run shellcheck on Bash scripts 13 | shellcheck bin/* scripts/* 14 | 15 | # Run formatter 16 | shfmt -i 2 -d . 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 asdf version manager 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Main workflow 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - synchronize 8 | - reopened 9 | paths-ignore: 10 | - "**.md" 11 | push: 12 | branches: 13 | - master 14 | paths-ignore: 15 | - "**.md" 16 | 17 | jobs: 18 | plugin_test: 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | os: 23 | - os: macos-latest 24 | version: 1.18.3-otp-27 25 | - os: ubuntu-latest 26 | version: 1.13.4-otp-22 27 | runs-on: ${{ matrix.os.os }} 28 | 29 | steps: 30 | - name: Install system packages on Ubuntu 31 | if: ${{ runner.os == 'Linux' }} 32 | run: sudo apt-get update && sudo apt-get install curl erlang 33 | 34 | - name: Install system packages on macOS 35 | if: ${{ runner.os == 'macOS' }} 36 | run: brew install coreutils erlang 37 | 38 | - name: Test plugin 39 | uses: asdf-vm/actions/plugin-test@v2 40 | with: 41 | command: elixir --version 42 | version: ${{ matrix.os.version }} 43 | 44 | lint: 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | - name: Checkout code 49 | uses: actions/checkout@v3 50 | 51 | - name: Run ShellCheck 52 | run: shellcheck bin/* 53 | 54 | format: 55 | runs-on: macos-latest 56 | 57 | steps: 58 | - name: Checkout code 59 | uses: actions/checkout@v3 60 | 61 | - name: Install shfmt 62 | run: brew install shfmt 63 | 64 | - name: Run shfmt 65 | run: shfmt -i 2 -d . 66 | -------------------------------------------------------------------------------- /bin/latest-stable: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | version_gte_api_change() { 4 | # Split the versions by dot character into arrays 5 | IFS='.' read -r -a input_version_array <<<"$1" 6 | IFS='.' read -r -a api_change_version_array <<<"0.16.0" 7 | 8 | # Support for 3 numbers in semantic versioning 9 | for i in {0..2}; do 10 | # suport for shorter versions by padding with 0 11 | [[ -z ${input_version_array[i]} ]] && input_version_array[i]=0 12 | 13 | if ((input_version_array[i] > api_change_version_array[i])); then 14 | # input version is higher 15 | return 0 16 | elif ((input_version_array[i] < api_change_version_array[i])); then 17 | # input version is lower 18 | return 1 19 | fi 20 | # else next loop 21 | done 22 | 23 | # versions are equal 24 | return 0 25 | } 26 | 27 | # get the directory of the current script 28 | ASDF_ELIXIR_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 29 | 30 | # calling a script to list all elixir releases 31 | # reject releases before 1.0.0 (starting with 0) 32 | # reject main and master branches 33 | # reject releases with otp version 34 | # reject release candidates 35 | # reject empty line 36 | # finally take last one 37 | ASDF_ELIXIR_LATEST_VERSION=$("$ASDF_ELIXIR_SCRIPT_DIR"/list-all | tr " " "\n" | grep -Ev "^0|^main|^master|otp|rc|^$" | tail -n 1) 38 | 39 | # support for v-dev and different asdf API versions 40 | ASDF_ELIXIR_ASDF_VERSION="$(asdf version)" 41 | if [ "$ASDF_ELIXIR_ASDF_VERSION" = "v-dev" ] || version_gte_api_change "${ASDF_ELIXIR_ASDF_VERSION#v}"; then 42 | # use --no-header flag only in latest asdf API (go builds) 43 | ASDF_ELIXIR_LATEST_OTP=$(asdf current --no-header erlang) 44 | else 45 | ASDF_ELIXIR_LATEST_OTP=$(asdf current erlang) 46 | fi 47 | 48 | # Note: asdf latest does not allows latest stable version to start with number 49 | # therefore installing from source by git ref is not supported, see: 50 | # https://github.com/asdf-vm/asdf/blob/v0.16.5/internal/versions/versions.go#L276 51 | printf "%s-otp-%s\n" "$ASDF_ELIXIR_LATEST_VERSION" "$(printf "%s" "$ASDF_ELIXIR_LATEST_OTP" | tr -s " ." "\n" | sed -n 2p)" 52 | -------------------------------------------------------------------------------- /bin/list-all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Download the list of versions available on Hex.pm. 4 | # 5 | # Hex.pm keeps a public builds.txt file where it lists all the builds available 6 | # in their servers. 7 | # 8 | # If we're able to successfuly fetch the builds file, its content is written to 9 | # STDOUT. Otherwise, nothing is written to STDOUT and an error message is 10 | # written to STDERR. 11 | download_hex_pm_builds() { 12 | local releases_url='https://builds.hex.pm/builds/elixir/builds.txt' 13 | 14 | if ! curl -Lfs $releases_url; then 15 | cat >&2 < Failed to fetch Hex.pm versions list. 17 | 18 | Hex.pm returned an error for the following URL: 19 | 20 | ${releases_url} 21 | 22 | Make sure your internet connection is stable and that you can reach 23 | https://builds.hex.pm/. 24 | 25 | If the problem persists, please report it on GitHub: 26 | https://github.com/asdf-vm/asdf-elixir/issues/new 27 | EOS 28 | 29 | return 1 30 | fi 31 | } 32 | 33 | get_hex_pm_versions() { 34 | # Get the builds file from Hex.pm 35 | download_hex_pm_builds | 36 | # Filter only the first field (the version name) 37 | cut -d\ -f1 | 38 | # Normalize version names 39 | sed -E ' 40 | # Discard numeric versions just with major 41 | /^v[0-9]+(-.*)?$/d; 42 | 43 | # Discard numeric versions just with major and minor 44 | /^v[0-9]+\.[0-9]+(-.*)?$/d; 45 | 46 | # Remove `v` prefix from numeric versions 47 | s/^v([0-9]+\.[0-9]+\.[0-9]+(-.*)?)$/\1/ 48 | ' 49 | } 50 | 51 | # Sort the list of versions. 52 | # 53 | # Does not expect any arguments, assumes the list of versions is provided in 54 | # STDIN. 55 | # 56 | sort_versions() { 57 | # Sort versions by the numerical values of each field. 58 | # 59 | # By default, sort will consider the entire line and sort lines 60 | # alphabetically according to the current system locale, which influences 61 | # details like whether downcase letters come before the uppercase ones. 62 | # 63 | # First, you'll notice the environment variable `LC_ALL` is being set to 64 | # `C` for this command. This enables bytewise sorting, which means 65 | # characters will be sorted by their numerical representation in the 66 | # encoding table (think ASCII for US English). This makes the sorting 67 | # algorithm completely uniform since it disregards the user's system 68 | # locale. 69 | # 70 | # Then you'll notice the `-t.` option, which changes the command to split 71 | # lines on the provided separator `.`, instead of considering the whole 72 | # line, which will influence the next options. 73 | # 74 | # Finally you'll notice the `k` options, which are key definitions that 75 | # tell sort how we want the sorting algorithm to look at lines. Each key 76 | # definition will be taken into consideration in order when deciding the 77 | # position of a line. 78 | # 79 | # Each key definition has the following format: 80 | # * First it specifies the number of the field where this definition 81 | # starts, according to the separation specified by `-t`. 82 | # * Then it specified the number of the field where this definition stops. 83 | # When you want to consider a single field, both start and stop values 84 | # are the same in the definition. 85 | # * Finally, the definitions include a single-letter ordering option, which 86 | # will override the global ordering options for the current key. In this 87 | # case, we use `n` to ensure the sorting is numerical. 88 | # 89 | LC_ALL=C sort -t. -k 1,1 -k 2,2n -k 3,3n 90 | } 91 | 92 | # Output the list of versions in a single line 93 | get_hex_pm_versions | sort_versions | tr '\n' ' ' 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asdf-elixir 2 | 3 | Elixir plugin for [asdf](https://github.com/asdf-vm/asdf) version manager 4 | 5 | ## Install 6 | 7 | This plugin needs `unzip` to be installed. 8 | 9 | If you have a Debian system you can install it by typing: 10 | `sudo apt-get install unzip` 11 | 12 | > *Elixir requires Erlang to be installed. You can use the [asdf-erlang](https://github.com/asdf-vm/asdf-erlang) plugin to install Erlang versions.* 13 | 14 | ``` 15 | asdf plugin add elixir https://github.com/asdf-vm/asdf-elixir.git 16 | ``` 17 | 18 | ## Check compatibility between elixir and erlang: 19 | 20 | https://hexdocs.pm/elixir/compatibility-and-deprecations.html#compatibility-between-elixir-and-erlang-otp 21 | 22 | ## Elixir precompiled versions 23 | 24 | Precompiled Elixir packages are built by [Bob](https://github.com/hexpm/bob/blob/master/README.md#elixir-builds) whenever 25 | a git push or a new release is made at the elixir repo. There's also a [status page](https://bobs-list.kobrakai.de/) available with an overview of all available packages and their availability. 26 | 27 | The precompiled packages are built against every officially supported OTP version, however if you only specify the 28 | elixir version, like `1.4.5`, the downloaded binaries will be those compiled against the oldest OTP release 29 | supported by that version. 30 | 31 | If you would like to use precompiled binaries built with a more recent OTP, you can append `-otp-${OTP_MAJOR_VERSION}` to any installable version that can be given to asdf-elixir. 32 | 33 | So, for example, to install Elixir 1.5.0 and take advantage of the new features from OTP-20 you might install version `1.5.0-otp-20`. 34 | 35 | Be sure to also install the corresponding Erlang/OTP version with asdf-erlang, and to have both selected versions in your 36 | `.tool-versions` file. 37 | 38 | > **Note**: You do not need to have Erlang installed to install a precompiled version of Elixir installed, but you will need to have it available at runtime, otherwise your Elixir commands will fail. Even though it's possible to install Elixir first, it's recommended to install Erlang first. 39 | 40 | ## Compiling from a Git reference or from source 41 | 42 | ### Using the CLI 43 | 44 | You can download and compile a specific commit reference from the [Elixir GitHub repository](https://github.com/elixir-lang/elixir/commits/main) by running: `asdf install elixir ref:`. You can then set the local/global version to your new version by running: 45 | 46 | ```bash 47 | # install in the entire environment 48 | asdf set --home elixir ref- 49 | 50 | # Or install locally in project 51 | asdf set elixir ref- 52 | ``` 53 | 54 | **or asdf < 0.16.0** 55 | ```bash 56 | # install in the entire environment 57 | asdf global elixir ref: 58 | 59 | # Or install locally in project 60 | asdf local elixir ref: 61 | ``` 62 | 63 | You can also [compile Elixir from source](https://github.com/elixir-lang/elixir/tree/master#compiling-from-source) without using `asdf` (for example, so that you can use the `master` branch of elixir or a branch with your own modifications), then use it by specifying the directory path: 64 | 65 | ``` 66 | # After Elixir already installed into /path/to/elixir 67 | asdf local elixir path:/path/to/elixir 68 | # Or 69 | asdf global elixir path:/path/to/elixir 70 | ``` 71 | 72 | 73 | ### .tool-versions file 74 | 75 | You can specify the version to install with a line like so in your `.tool-versions` file: 76 | 77 | ``` 78 | elixir ref: 79 | ``` 80 | 81 | Or if you've already compiled Elixir from source in a specific directory: 82 | 83 | ``` 84 | elixir path:/path/to/elixir 85 | ``` 86 | 87 | Note that the path specified must be an absolute path to the Elixir installation's root directory (e.g. the directory containing the `bin` directory). 88 | 89 | ## Elixir escripts support 90 | 91 | This plugin supports elixir escripts, adding them to your path just like any other elixir binary. 92 | Whenever you install a new escript with `mix escript.install` you need to `asdf reshim elixir` in order 93 | to create shims for it. 94 | 95 | ## Default `mix` commands 96 | 97 | After installing you can specify which additional mix commands must be run after adding a new Elixir version. 98 | This can be particurlarly useful for installing often used archives. 99 | They need to be specified in `$HOME/.default-mix-commands`, the flag `--force` will be added by default. 100 | An example: 101 | 102 | ``` 103 | local.hex 104 | local.rebar 105 | archive.install hex phx_new 106 | ``` 107 | 108 | ## Use 109 | 110 | Check [asdf](https://github.com/asdf-vm/asdf) readme for instructions on how to install & manage versions of Elixir. 111 | 112 | ## Helpful Articles: 113 | 114 | https://www.cogini.com/blog/using-asdf-with-elixir-and-phoenix/ 115 | -------------------------------------------------------------------------------- /bin/install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | install_elixir() { 4 | local install_type=$1 5 | local version=$2 6 | local install_path=$3 7 | local tmp_download_dir 8 | 9 | if [ -n "$ASDF_DOWNLOAD_PATH" ]; then 10 | [ ! -d "$ASDF_DOWNLOAD_PATH" ] && mkdir -p "$ASDF_DOWNLOAD_PATH" 11 | tmp_download_dir="$ASDF_DOWNLOAD_PATH" 12 | elif [ -z "$TMPDIR" ]; then 13 | tmp_download_dir="$(mktemp -d -t elixir_build_XXXXXX)" 14 | else 15 | tmp_download_dir="$TMPDIR" 16 | fi 17 | 18 | if [ "$install_type" = "version" ]; then 19 | install_elixir_version "$version" "$install_path" "$tmp_download_dir" 20 | else 21 | install_elixir_ref "$version" "$install_path" "$tmp_download_dir" 22 | fi 23 | 24 | mkdir -p "$install_path/.mix/archives" 25 | } 26 | 27 | install_elixir_version() { 28 | local version=$1 29 | local install_path=$2 30 | local tmp_download_dir=$3 31 | 32 | # path to the tar file 33 | local source_path="$tmp_download_dir/elixir-precompiled-${version}.zip" 34 | download_zip_file_for_version "$version" "$source_path" 35 | 36 | echo "==> Copying release into place" 37 | 38 | if ! type "unzip" &>/dev/null; then 39 | echo "ERROR: Command 'unzip' not found." 40 | echo "unzip must be installed on your system." 41 | exit 1 42 | fi 43 | 44 | unzip -q "$source_path" -d "$install_path" || exit 1 45 | } 46 | 47 | install_elixir_ref() { 48 | local ref=$1 49 | local install_path=$2 50 | local tmp_download_dir=$3 51 | 52 | # path to the tar file 53 | local source_path="$tmp_download_dir/elixir-ref-${ref}-src.tar.gz" 54 | download_source_archive_for_ref "$ref" "$source_path" 55 | 56 | echo "==> Making the release" 57 | 58 | # Expand source archive 59 | tar zxf "$source_path" -C "$install_path" --strip-components=1 || exit 1 60 | 61 | # Build from source in a subshell because we don't want to disturb current working dir 62 | # installing using DESTDIR makes sure there are no tempfiles in the install dir 63 | # and fixes compatibility issues with the elixir-ls language server project 64 | if ! ( 65 | cd "$install_path" || exit 1 66 | make clean test DESTDIR="$install_path" install 67 | ); then 68 | # handle the result 69 | echo "Build failed, cleaning..." 70 | rm -rf "$install_path" 71 | exit 1 72 | else 73 | echo "Build succeeded!" 74 | cp -a "$install_path/usr/local/." "$install_path/" 75 | rm -rf "${install_path:?}/usr" 76 | fi 77 | } 78 | 79 | download_zip_file_for_version() { 80 | local version=$1 81 | local download_path=$2 82 | local download_url 83 | download_url="$(get_download_url_for_version "$version")" 84 | 85 | # determine if the file exists 86 | echo "==> Checking whether specified Elixir release exists..." 87 | http_status=$(curl -L -I -w '%{http_code}' -s -o /dev/null "$download_url") 88 | 89 | if [ "$http_status" -eq 404 ] || [ "$http_status" -eq 403 ]; then 90 | cat < Elixir version not found. 92 | 93 | Hex.pm returned a ${http_status} for the following URL: 94 | 95 | ${download_url} 96 | 97 | You can view a list of all Elixir releases by running 'asdf list all elixir'. 98 | 99 | Note: If you want to download a specific release of Elixir, please 100 | specify the full version number (e.g. 1.2.1 instead of 1.3). 101 | EOS 102 | 103 | exit 1 # non zero due to file not existing 104 | fi 105 | 106 | echo "==> Downloading ${version} to ${download_path}" 107 | curl -Lo "$download_path" -C - "$download_url" 108 | } 109 | 110 | download_source_archive_for_ref() { 111 | local ref=$1 112 | local download_path=$2 113 | local download_url="https://github.com/elixir-lang/elixir/archive/${ref}.tar.gz" 114 | 115 | # determine if the file exists 116 | echo "==> Checking whether specified Elixir reference exists..." 117 | http_status=$(curl -L -I -w '%{http_code}' -s -o /dev/null "$download_url") 118 | 119 | if [ "$http_status" -eq 404 ]; then 120 | cat < Elixir reference not found. 122 | 123 | GitHub returned a 404 for the following URL: 124 | 125 | ${download_url} 126 | 127 | You can view a list of all Elixir releases by running 'asdf list all elixir' 128 | or by visiting https://github.com/elixir-lang/elixir/releases 129 | 130 | Note: If you want to specify a git reference by which to install 131 | Elixir, it must be a valid git tag or branch (generally of the form v1.2.1). 132 | EOS 133 | 134 | exit 1 # non zero due to file not existing 135 | fi 136 | 137 | echo "==> Downloading ${ref} to ${download_path}" 138 | curl -Lo "$download_path" -C - "$download_url" 139 | } 140 | 141 | get_download_url_for_version() { 142 | local version=$1 143 | 144 | # if version is a release number, prepend v 145 | if [[ "$version" =~ ^[0-9]+\.* ]]; then 146 | version="v${version}" 147 | fi 148 | 149 | echo "https://builds.hex.pm/builds/elixir/${version}.zip" 150 | } 151 | 152 | run_default_mix_commands() { 153 | local install_path=$1 154 | local default_mix_commands="${HOME}/.default-mix-commands" 155 | 156 | if [ ! -f "$default_mix_commands" ]; then return; fi 157 | 158 | while read -ra command; do 159 | echo -e "\nRunning \033[33mmix ${command[*]} --force\033[39m... " 160 | # shellcheck source=bin/exec-env 161 | source "$(dirname "$0")/exec-env" 162 | PATH=$install_path/bin:$PATH MIX_EXS="" "$install_path"/bin/mix "${command[@]}" --force