CLOVER🍀

That was when it all began.

SDKMANの中身の一部がいつの間にかRustになっていたという話

これは、なにをしたくて書いたもの?

自分はJavaに関するソフトウェアをインストールするのに、よくSDKMANを使っています。

Home | SDKMAN! the Software Development Kit Manager

環境によってはmiseを使っていることもあるのですが、Javaに関するものに特化する場合はSDKMANの方が
便利だなと思ったりします。

そんな感じでずっと使っているのですが、SDKMANの一部がいつの間にかRustになっていることに気づいたので
ちょっとメモしておこうかなということで。

環境

今回の環境はこちら。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.2 LTS
Release:        24.04
Codename:       noble


$ uname -srvmpio
Linux 6.8.0-62-generic #65-Ubuntu SMP PREEMPT_DYNAMIC Mon May 19 17:15:03 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

SDKMANをインストールする

まずはSDKMANをインストールします。

$ curl -s "https://get.sdkman.io" | bash
$ source "/home/vagrant/.sdkman/bin/sdkman-init.sh"

Installation | SDKMAN! the Software Development Kit Manager

これでSDKMANが使えるようになります。

バージョンを確認。

$ sdk version

SDKMAN!
script: 5.19.0
native: 0.7.4 (linux x86_64)

ここで「native」と出てくるのがヒントですね。

なにがnative?

sdkコマンドはbashの関数なので、定義を見てみます。

$ declare -f sdk
sdk ()
{
    COMMAND="$1";
    QUALIFIER="$2";
    case "$COMMAND" in
        l)
            COMMAND="list"
        ;;
        ls)
            COMMAND="list"
        ;;
        v)
            COMMAND="version"
        ;;
        u)
            COMMAND="use"
        ;;
        i)
            COMMAND="install"
        ;;
        rm)
            COMMAND="uninstall"
        ;;
        c)
            COMMAND="current"
        ;;
        ug)
            COMMAND="upgrade"
        ;;
        d)
            COMMAND="default"
        ;;
        h)
            COMMAND="home"
        ;;
        e)
            COMMAND="env"
        ;;
    esac;
    if [[ "$COMMAND" != "update" ]]; then
        ___sdkman_check_candidates_cache "$SDKMAN_CANDIDATES_CACHE" || return 1;
    fi;
    SDKMAN_AVAILABLE="true";
    if [ -z "$SDKMAN_OFFLINE_MODE" ]; then
        SDKMAN_OFFLINE_MODE="false";
    fi;
    __sdkman_update_service_availability;
    if [ -f "${SDKMAN_DIR}/etc/config" ]; then
        source "${SDKMAN_DIR}/etc/config";
    fi;
    if [[ -z "$COMMAND" ]]; then
        ___sdkman_help;
        return 1;
    fi;
    CMD_FOUND="";
    if [[ "$COMMAND" != "selfupdate" || "$sdkman_selfupdate_feature" == "true" ]]; then
        CMD_TARGET="${SDKMAN_DIR}/src/sdkman-${COMMAND}.sh";
        if [[ -f "$CMD_TARGET" ]]; then
            CMD_FOUND="$CMD_TARGET";
        fi;
    fi;
    CMD_TARGET="${SDKMAN_DIR}/ext/sdkman-${COMMAND}.sh";
    if [[ -f "$CMD_TARGET" ]]; then
        CMD_FOUND="$CMD_TARGET";
    fi;
    if [[ -z "$CMD_FOUND" ]]; then
        echo "";
        __sdkman_echo_red "Invalid command: $COMMAND";
        echo "";
        ___sdkman_help;
    fi;
    if [[ "$COMMAND" == "offline" && -n "$QUALIFIER" && -z $(echo "enable disable" | grep -w "$QUALIFIER") ]]; then
        echo "";
        __sdkman_echo_red "Stop! $QUALIFIER is not a valid offline mode.";
    fi;
    local final_rc=0;
    local native_command="${SDKMAN_DIR}/libexec/${COMMAND}";
    if [[ "$sdkman_native_enable" == 'true' && -f "$native_command" ]]; then
        "$native_command" "${@:2}";
    else
        if [ -n "$CMD_FOUND" ]; then
            if [[ -n "$QUALIFIER" && "$COMMAND" != "help" && "$COMMAND" != "offline" && "$COMMAND" != "flush" && "$COMMAND" != "selfupdate" && "$COMMAND" != "env" && "$COMMAND" != "completion" && "$COMMAND" != "edit" && "$COMMAND" != "home" && -z $(echo ${SDKMAN_CANDIDATES[@]} | grep -w "$QUALIFIER") ]]; then
                echo "";
                __sdkman_echo_red "Stop! $QUALIFIER is not a valid candidate.";
                return 1;
            fi;
            local converted_command_name=$(echo "$COMMAND" | tr '-' '_');
            __sdk_"$converted_command_name" "${@:2}";
        fi;
    fi;
    final_rc=$?;
    return $final_rc
}

nativeといっているのはこのあたりみたいですね。

    local native_command="${SDKMAN_DIR}/libexec/${COMMAND}";
    if [[ "$sdkman_native_enable" == 'true' && -f "$native_command" ]]; then
        "$native_command" "${@:2}";

SDKMAN_DIRというのは$HOME/.sdkmanディレクトリーのことです。

$ printenv SDKMAN_DIR
$HOME/.sdkman

ということは、nativeなコマンドはここにあるわけですね。

$ ll ${SDKMAN_DIR}/libexec
合計 6148
drwxrwxr-x  2 xxxxx xxxxx    4096  6月 28 15:20 ./
drwxrwxr-x 11 xxxxx xxxxx    4096  6月 28 15:20 ../
-rwxr-xr-x  1 xxxxx xxxxx 1120624  6月 28 15:20 current*
-rwxr-xr-x  1 xxxxx xxxxx 1179520  6月 28 15:20 default*
-rwxr-xr-x  1 xxxxx xxxxx 1202120  6月 28 15:20 help*
-rwxr-xr-x  1 xxxxx xxxxx 1119760  6月 28 15:20 home*
-rwxr-xr-x  1 xxxxx xxxxx 1148920  6月 28 15:20 uninstall*
-rwxr-xr-x  1 xxxxx xxxxx  505608  6月 28 15:20 version*

この6つがnativeなようです。

versionコマンドを実行してみましょう。

$ ${SDKMAN_DIR}/libexec/version

SDKMAN!
script: 5.19.0
native: 0.7.4 (linux x86_64)

すべてがnativeコマンドであるわけではなく、その他のコマンドはこちらにあるようです。

    CMD_FOUND="";
    if [[ "$COMMAND" != "selfupdate" || "$sdkman_selfupdate_feature" == "true" ]]; then
        CMD_TARGET="${SDKMAN_DIR}/src/sdkman-${COMMAND}.sh";
        if [[ -f "$CMD_TARGET" ]]; then
            CMD_FOUND="$CMD_TARGET";
        fi;
    fi;

確認。

$ ll ${SDKMAN_DIR}/src
合計 104
drwxr-xr-x  2 xxxxx xxxxx 4096  6月 28 15:20 ./
drwxrwxr-x 11 xxxxx xxxxx 4096  6月 28 15:20 ../
-rw-r--r--  1 xxxxx xxxxx 2485  6月 28 15:20 sdkman-availability.sh
-rw-r--r--  1 xxxxx xxxxx 1024  6月 28 15:20 sdkman-cache.sh
-rw-r--r--  1 xxxxx xxxxx  941  6月 28 15:20 sdkman-config.sh
-rw-r--r--  1 xxxxx xxxxx 2139  6月 28 15:20 sdkman-current.sh
-rw-r--r--  1 xxxxx xxxxx 1326  6月 28 15:20 sdkman-default.sh
-rw-r--r--  1 xxxxx xxxxx 3169  6月 28 15:20 sdkman-env-helpers.sh
-rw-r--r--  1 xxxxx xxxxx 4297  6月 28 15:20 sdkman-env.sh
-rw-r--r--  1 xxxxx xxxxx 1575  6月 28 15:20 sdkman-flush.sh
-rw-r--r--  1 xxxxx xxxxx 2688  6月 28 15:20 sdkman-help.sh
-rw-r--r--  1 xxxxx xxxxx 1299  6月 28 15:20 sdkman-home.sh
-rw-r--r--  1 xxxxx xxxxx 7646  6月 28 15:20 sdkman-install.sh
-rw-r--r--  1 xxxxx xxxxx 3228  6月 28 15:20 sdkman-list.sh
-rw-r--r--  1 xxxxx xxxxx 3518  6月 28 15:20 sdkman-main.sh
-rw-r--r--  1 xxxxx xxxxx  920  6月 28 15:20 sdkman-offline.sh
-rw-r--r--  1 xxxxx xxxxx 2473  6月 28 15:20 sdkman-path-helpers.sh
-rw-r--r--  1 xxxxx xxxxx 2349  6月 28 15:20 sdkman-selfupdate.sh
-rw-r--r--  1 xxxxx xxxxx 1503  6月 28 15:20 sdkman-uninstall.sh
-rw-r--r--  1 xxxxx xxxxx 2357  6月 28 15:20 sdkman-update.sh
-rw-r--r--  1 xxxxx xxxxx 3443  6月 28 15:20 sdkman-upgrade.sh
-rw-r--r--  1 xxxxx xxxxx 1956  6月 28 15:20 sdkman-use.sh
-rw-r--r--  1 xxxxx xxxxx 3040  6月 28 15:20 sdkman-utils.sh
-rw-r--r--  1 xxxxx xxxxx  791  6月 28 15:20 sdkman-version.sh

ちなみに、こちら見ると${SDKMAN_DIR}/extディレクトリーにスクリプトを置くと自分でコマンドを追加できそうですね。

    CMD_TARGET="${SDKMAN_DIR}/ext/sdkman-${COMMAND}.sh";
    if [[ -f "$CMD_TARGET" ]]; then
        CMD_FOUND="$CMD_TARGET";
    fi;

このスクリプトの本体はこちらです。

https://github.com/sdkman/sdkman-cli/blob/5.19.0/src/main/bash/sdkman-main.sh

少し脱線しました。

いつからnativeコマンドが入った?

SDKMANはbashスクリプトのイメージだったので、いつからこうなったのか調べてみました。

どうやらSDKMAN 5.13.0のようです。

Add capability for native binary support

https://github.com/sdkman/sdkman-cli/releases/tag/5.13.0

5.13.0のリリース日を見ると、2021年10月18日です。かなり前からですね?そんなに気づかなかったのでしょうか?

nativeの実体は?

nativeの実体は、こちらのリポジトリーです。sdkman-cli-nativeですね。

Native CLI subcommand components for SDKMAN! written in Rust. Use the binaries generated by this project in the sdk wrapper shell function from the sdkman-cli project.

GitHub - sdkman/sdkman-cli-native: The next generation of SDKMAN! CLI commands, written in Rust

Rustで実装されているようです。

こちらで表示されていた「native」というのは、sdkman-cli-nativeのバージョンのことだったわけですね。

$ sdk version

SDKMAN!
script: 5.19.0
native: 0.7.4 (linux x86_64)

Release Release v0.7.4 · sdkman/sdkman-cli-native · GitHub

ところで、0.0.1のリリース日を見ると2022年2月10日になっています。最初はちょっと違ったんでしょうね…?

Release Release v0.0.1 · sdkman/sdkman-cli-native · GitHub

ちょっと気になりましたが、あまり追わないことにします。

おわりに

SDKMANのバージョン表記に「native」と出てくるのがちょっと気になって、少し調べてみました。

2021年からネイティブに切り替わっていっていたことに全然気づいてなかったのですが、すでに4年経過しています。

よくここまで気づいていなかったとも思ったのですが、そもそもこのツールは最初はSDKMANではなくGVMという名前でした。
それは10年以上前の話です。

使い勝手がまったく変わらないので、ツールの名前が変わっても自然に使っていたのですが、意外と気づかないもの
なんだなぁと。

そして、こういうところでもRustが使われているですね。

Markdownでコードブロックをネストさせる

たまにREADME.mdを引用する時などで、MarkdownMarkdown記法として書こうとすると、コードブロックがネスト
していた場合にうまくいかず困っていました。

が、調べてみるとネストさせる方法がちゃんと書かれていたのでメモしておくことにしました。

どういうことをやりたかったか見せた方が早いので、先に載せておきましょう。

# heading

abcde.

```java
public class Test {
}
```

fghij.

このMarkdownのコードブロックをどう書いているかというと````markdownで始まり````で終わる(backtick quote 4つ)という
ことになっています。
※なお、このコードブロックは先にbacktick quoteをひとつ書き、ひとつスペースを空けた後に4つbacktick quoteを書いています

そして、中にbacktick quote 3つでjavaが挟まっています。

つまり、こう書いています。
※このコードブロックを書くために、1番外側はbacktick quote 5つにしています

````markdown
# heading

abcde.

```java
public class Test {
}
```

fghij.
````

これに関しては、以下あたりがヒントになります。

To include a literal backtick character within a code span, you can use multiple backticks as the opening and closing delimiters:

The backtick delimiters surrounding a code span may include spaces — one after the opening, one before the closing. This allows you to place literal backtick characters at the beginning or end of a code span:

Syntax / Span Elements / Code

CommonMark Spec / Leaf blocks / Fenced code blocks

Creating and highlighting code blocks - GitHub Docs

CommonMarkの仕様を見ると、改行を含むコードブロックだからといってbacktick quote 3つにこだわる必要がないことが
わかります。

たとえば、以下のコードはbacktick quote 3つではなく~~~pythonと書いています。

def say():
  return "Hello"

知らなかったのと、全然違うエスケープ方法を想像していたのでメモということで。

ちゃんと調べるべきですね…。