dfx (Dfinity Canister SDK)をバージョン指定してインストールする方法を整理し、その実行コマンドの中身を読み解きます。
はじめに
こんにちは、@bioerrorlogです。
Dfinity Canister SDK / dfxをインストールするときはいつも、脳を空っぽにしてドキュメントにあるコマンドをコピペしていました。
内容を気にせずにコマンド実行するのも据わりが悪いので、一度くらいはインストールコマンドを読み解いておこうと思いました。
dfxバージョンを指定してインストールするコマンドをメモし、その実行内容を整理していきます。
dfxをバージョン指定してインストールする
インストールコマンド
ドキュメントの通り、例えばバージョン0.8.2
をインストールするときは次のコマンドを実行します。
DFX_VERSION=0.8.2 sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)"
環境変数DFX_VERSION
に任意のバージョンを指定することで、指定のdfxバージョンをインストールすることができます。
インストールコマンド読み解き
では、上記インストールコマンドの処理内容を読み解いていきます。
DFX_VERSION=0.8.2 <実行コマンド>
このようにコマンドを実行することで、環境変数DFX_VERSION
にバージョン番号を代入した状態で以降のコマンドが実行されます。
export
コマンドに環境変数宣言部分を切り出したとしても、インストールするバージョンが指定できる点は同じです。
export DFX_VERSION=0.8.0
sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)"
sh -ci "$(xxxxx)"
この部分ではsh
コマンドによって"$(xxxxx)"
から受け渡される文字列がshellプロセスとして実行されます。
-c
オプションによって(標準入力でなく)後続の文字列をshellで実行することを指定できます。
-i
オプションはコマンドをインタラクティブに実行することを指定するオプションです。
が、現状のインストールコマンドでは特にターミナルとやり取りする要素はないので不要なのでは...?と思っています。
(実際、-i
部分を抜いてコマンドを実行しても変わりなくインストールできます。)
"$(xxxxx)"
では、その内部を評価した後の文字列が渡されます。
(shellにおいては、ダブルクォーテーション部分はその評価後の状態が、シングルクォーテーションでは内部がそのままの文字列として渡されます。)
curl -fsSL https://sdk.dfinity.org/install.sh
この部分でcurl
コマンドによってdfxのインストールスクリプトをダウンロードしています。
-f
オプションは、リクエストが失敗したときにはなにも出力させず、代わりにexitコード22を返却するようにするものです。
(リクエストが失敗したときにエラー通知用HTMLがサーバーから返されたとしても、何も出力しない挙動にすることが出来ます)
-sS
によって進捗メーター表示をなくします。
(厳密には-s
によって進捗メーターとエラーメッセージの表示をなくし、加えて-S
を指定することでエラーメッセージは表示するよう設定されます。)
-L
は、リクエストページが移動してた場合に新しい場所に対して再リクエストを投げるようにするオプションです。
※各オプションの詳しい説明はmanページや下記リンクが参考になります:
explainshell.com - curl -fsSL https://sdk.dfinity.org/install.sh
上記curl
コマンドによってhttps://sdk.dfinity.org/install.sh
から取得され、sh
コマンドによって実行されるdfxのインストールスクリプトの中身は以下です。
長いスクリプトなので内容は網羅しませんが、次の部分でインストール先ディレクトリにdfx
実行ファイルを配置し、dfxがインストールされています。
$MV "$_dfx_file" "${_install_dir}" 2>/dev/null \
スクリプト全文:
set -u
get_tag_from_manifest_json() {
cat \
| tr -d '\n' \
| grep -o "\"$1\":[[:space:]]*\"[a-zA-Z0-9.]*" \
| grep -o "[0-9.]*$"
}
DFX_BOOL_FLAGS=""
define_flag_BOOL() {
local VARNAME
VARNAME="flag_$(echo "$1" | tr /a-z/ /A-Z)"
eval "$VARNAME=\${$VARNAME:-}"
DFX_BOOL_FLAGS="${DFX_BOOL_FLAGS}--${1} $VARNAME $2"
}
get_flag_name() {
echo "$1"
}
get_var_name() {
echo "$2"
}
read_flags() {
while [ -n "$*" ]; do
local ARG=$1
shift
OLD_IFS="$IFS"
IFS=$'\n'
for line in ${DFX_BOOL_FLAGS}; do
[ "$line" ] || break
IFS="$OLD_IFS"
FLAG="$(get_flag_name "$line")"
VARNAME="$(get_var_name "$line")"
if [ "$ARG" == "$FLAG" ]; then
eval "$VARNAME=1"
fi
done
done
}
log() {
if "$_ansi_escapes_are_valid"; then
printf "\33[1minfo:\33[0m %s\n" "$1" 1>&2
else
printf '%s\n' "$1" 1>&2
fi
}
say() {
printf 'dfinity-sdk: %s\n' "$1"
}
warn() {
if $_ansi_escapes_are_valid; then
printf "\33[1mwarn:\33[0m %s\n" "$1" 1>&2
else
printf '%s\n' "$1" 1>&2
fi
}
err() {
say "$1" >&2
exit 1
}
need_cmd() {
if ! check_cmd "$1"; then
err "need '$1' (command not found)"
fi
}
check_cmd() {
command -v "$1" >/dev/null 2>&1
}
assert_nz() {
if [ -z "$1" ]; then err "assert_nz $2"; fi
}
ensure() {
if ! "$@"; then err "command failed: $*"; fi
}
ignore() {
"$@"
}
define_flag_BOOL "insecure" "Allows downloading from insecure URLs, either using HTTP or TLS 1.2 or less."
check_help_for() {
local _cmd
local _arg
local _ok
_cmd="$1"
_ok="y"
shift
if check_cmd sw_vers; then
case "$(sw_vers -productVersion)" in
10.13*) ;;
10.14*) ;;
10.15*) ;;
11.*) ;;
*)
warn "Detected OS X platform older than 10.13 (High Sierra)"
_ok="n"
;;
esac
fi
for _arg in "$@"; do
if ! "$_cmd" --help | grep -q -- "$_arg"; then
_ok="n"
fi
done
test "$_ok" = "y"
}
check_support_for() {
local err="$1"
shift
local cmd="$*"
! ($cmd 2>&1 | grep "$err" >/dev/null)
}
downloader() {
local _dld
if check_cmd curl; then
_dld=curl
elif check_cmd wget; then
_dld=wget
else
_dld='curl or wget'
fi
if [ "$1" = --check ]; then
need_cmd "$_dld"
elif [ "$_dld" = curl ]; then
if check_help_for curl --proto --tlsv1.2; then
curl --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2"
elif ! [ "$flag_INSECURE" ]; then
warn "Not forcing TLS v1.2, this is potentially less secure"
curl --silent --show-error --fail --location "$1" --output "$2"
else
err "TLS 1.2 is not supported on this platform. To force using it, use the --insecure flag."
fi
elif [ "$_dld" = wget ]; then
if check_help_for wget --https-only --secure-protocol; then
wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2"
elif ! [ "$flag_INSECURE" ]; then
warn "Not forcing TLS v1.2, this is potentially less secure"
wget "$1" -O "$2"
else
err "TLS 1.2 is not supported on this platform. To force using it, use the --insecure flag."
fi
else
err "Unknown downloader"
fi
}
SDK_WEBSITE="https://sdk.dfinity.org"
DFX_RELEASE_ROOT="${DFX_RELEASE_ROOT:-$SDK_WEBSITE/downloads/dfx}"
DFX_MANIFEST_JSON_URL="${DFX_MANIFEST_JSON_URL:-$SDK_WEBSITE/manifest.json}"
DFX_VERSION="${DFX_VERSION:-}"
SCRIPT_COMMIT_DESC="aff944417cccf34abe58dac9283fdf2b92d10458"
get_tag_from_manifest_json() {
cat \
| tr -d '\n' \
| grep -o "\"$1\":[[:space:]]*\"[a-zA-Z0-9.]*" \
| grep -o "[0-9.]*$"
}
get_manifest_version() {
local _version
_version="$(downloader "${DFX_MANIFEST_JSON_URL}" - | get_tag_from_manifest_json latest)" || return 2
printf %s "${_version}"
}
validate_install_dir() {
local dir="${1%/}"
if ! [ -d "$dir" ]; then
if ! mkdir -p "$dir"; then
if type sudo >/dev/null; then
sudo mkdir -p "$dir"
fi
fi
fi
! [ -d "$dir" ] && return 1
! [ -w "$dir" ] && return 2
case ":$PATH:" in
*:$dir:*) ;;
*) return 3 ;;
esac
return 0
}
sdk_install_dir() {
if [ "${DFX_INSTALL_ROOT:-}" ]; then
validate_install_dir "${DFX_INSTALL_ROOT}"
printf %s "${DFX_INSTALL_ROOT}"
elif validate_install_dir /usr/local/bin; then
printf %s /usr/local/bin
elif [ "$(uname -s)" = Darwin ]; then
validate_install_dir /usr/local/bin
printf %s /usr/local/bin
elif validate_install_dir /usr/bin; then
printf %s /usr/bin
else
printf %s "${HOME}/bin"
fi
}
main() {
_ansi_escapes_are_valid=false
if [ -t 2 ]; then
if [ "${TERM+set}" = 'set' ]; then
case "$TERM" in
xterm* | rxvt* | urxvt* | linux* | vt*)
_ansi_escapes_are_valid=true
;;
esac
fi
fi
read_flags "$@"
log "Executing dfx install script, commit: $SCRIPT_COMMIT_DESC"
downloader --check
need_cmd uname
need_cmd mktemp
need_cmd chmod
need_cmd mkdir
need_cmd rm
need_cmd tar
need_cmd gzip
need_cmd touch
get_architecture || return 1
local _arch="$RETVAL"
assert_nz "$_arch" "arch"
if [ -z "${DFX_VERSION}" ]; then
DFX_VERSION=$(get_manifest_version)
fi
log "Version found: $DFX_VERSION"
local _dfx_url="${DFX_RELEASE_ROOT}/${DFX_VERSION}/${_arch}/dfx-${DFX_VERSION}.tar.gz"
local _dir
_dir="$(mktemp -d 2>/dev/null || ensure mktemp -d -t dfinity-sdk)"
local _dfx_archive="${_dir}/dfx.tar.gz"
local _dfx_file="${_dir}/dfx"
log "Creating uninstall script in ~/.cache/dfinity"
mkdir -p "${HOME}/.cache/dfinity/"
install_uninstall_script
log "Checking for latest release..."
ensure mkdir -p "$_dir"
ensure downloader "$_dfx_url" "$_dfx_archive"
tar -xf "$_dfx_archive" -O >"$_dfx_file"
ensure chmod u+x "$_dfx_file"
local _install_dir
_install_dir="$(sdk_install_dir)"
printf "%s\n" "Will install in: ${_install_dir}"
mkdir -p "${_install_dir}" || true
if [ -w "${_install_dir}" ]; then
MV="mv"
else
if ! type sudo >/dev/null; then
err "Install directory '${_install_dir}' not writable and sudo command not found"
fi
MV="sudo mv"
fi
$MV "$_dfx_file" "${_install_dir}" 2>/dev/null \
|| err "Failed to install the DFINITY Development Kit: please check your permissions and try again."
log "Installed $_install_dir/dfx"
ignore rm -rf "$_dir"
}
get_architecture() {
local _ostype _cputype _arch
_ostype="$(uname -s)"
_cputype="$(uname -m)"
if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then
if sysctl hw.optional.x86_64 | grep -q ': 1'; then
_cputype=x86_64
fi
fi
if [ "$_ostype" = Darwin ] && [ "$_cputype" = arm64 ]; then
_cputype=x86_64
fi
case "$_ostype" in
Linux)
_ostype=linux
;;
Darwin)
_ostype=darwin
;;
*)
err "unrecognized OS type: $_ostype"
;;
esac
case "$_cputype" in
x86_64 | x86-64 | x64 | amd64)
_cputype=x86_64
;;
*)
err "unknown CPU type: $_cputype"
;;
esac
_arch="${_cputype}-${_ostype}"
RETVAL="$_arch"
}
install_uninstall_script() {
set +u
local uninstall_file_path
local uninstall_script
uninstall_script=$(
cat <<'EOF'
#!/usr/bin/env sh
uninstall() {
check_rm "${DFX_INSTALL_ROOT}/dfx"
check_rm "${HOME}/bin/dfx"
check_rm /usr/local/bin/dfx /usr/bin/dfx
clean_cache
}
check_rm() {
local file
for file in "$@"
do
[ -e "${file}" ] && rm "${file}"
done
}
clean_cache() {
if [ -z "$HOME" ]; then
exit "HOME environment variable unset."
fi
rm -Rf "${HOME}/.cache/dfinity"
}
uninstall
EOF
)
set -u
assert_nz "${HOME}"
uninstall_file_path="${HOME}/.cache/dfinity/uninstall.sh"
log "uninstall path=${uninstall_file_path}"
rm -f "${uninstall_file_path}"
printf "%s" "$uninstall_script" >"${uninstall_file_path}"
ensure chmod u+x "${uninstall_file_path}"
}
main "$@" || exit $?
おわりに
以上、dfxをバージョン指定してインストールする方法をメモし、その実行コマンドの中身を読み解きました。
dfxのインストール方法は非常シンプルかつ処理時間も短いので、色々なバージョンの実験が気軽にできます。
触って遊んで、その挙動に慣れ親しんでいきたいものです。
[関連記事]
www.bioerrorlog.work
www.bioerrorlog.work
www.bioerrorlog.work
参考
Installing the SDK | Internet Computer Home
explainshell.com - curl -fsSL example.org
explainshell.com - sh -ci
What is the sh -c command? - Ask Ubuntu