VSCode の Web Shell 用拡張機能を作って Python を動かしてみた
VSCode で WASI の実行が実験的にサポートされ、Web 版 VSCode でもシェル(Web Shell)が利用できるようなりました。
この記事では関連する内容として、下記のついて記述してあります。
- Web Shell を試してみる
- Web Shell 上で CPython を動かしてみる
- WASI と Web Shell 対応の拡張機能の作り方
VSCode の Web Shell とは
詳細は上記のブログ記事を見ていただく方が良いので、ここでは簡単な概要など。
WASI 対応
まず、シェルの前提となる実行環境について。
VSCode の Web 版で Python などの実行をサポートするために、WASI が実験的にサポートされることになりました。
これまでも .wasm
自体を動かすことはできていたのですが、Workspace のファイルを扱うときに統一的な方法がなかったので拡張機能側で工夫する必要がありました。今回の WASI 採用によりこの部分が改善されるため、拡張機能の開発者にとっては大きな利点になるかと思います。
Web Shell
.wasm
が扱えるようになると、Python などの実行環境の他に CLI ツールなども動かしたくなります。(その辺を考慮したのかはわかりませんが)拡張機能をコマンドにように実行できる Web Shell もあわせて実験的に提供されました。
Web Shell に対応した拡張機能では /usr/bin
の下などに仮想的なファイルをマウントできます。このファイルは Web Shell のターミナルから通常のコマンドのように実行できるので、拡張機能の .wasm
をターミナルから利用可能となります。
試してみる
シェルとして試すだけならとくにコードを記述する必要ありません。insiders.vscode.dev をブラウザーで開いて下記の拡張機能をインストールするだけです。
コマンドパレットから Terminal: Create New Web Shell
を実行するとターミナルが開いて Web Shell を利用できるようになります。リモートリポジトリやローカルのフォルダーなどを開いている場合、Web Shell では /workspace
を通してファイルへアクセスできます。
図 1-1 Web Shell のターミナルを開きファイルへアクセス
ただし、この記事を書いている時点ではまだ不安定であり touch
もエラーになりました。また、パイプやリダイレクトなどの機能も未実装な状態でした。
Web Shell で CPython を動かしてみる
冒頭の記事でも動かしていたのですが、Web 版で実際に試せるコードが見当たらなかったので拡張機能を作ってみました。
Python 本体には冒頭の記事でリンクされていた WASI 用の CPython を利用しています。
リポジトリをクローンして下記のように実行します。
```shell-session
$ npm ci
$ npm run build
$ npm run setup:python
$ npm run serve:http
```
insiders.vscode.dev 側でコマンドパレットから Developer: Install Extension From Location
を実行し http://localhost:5000
を指定します。少し待つと拡張機能がインストールされます。
図 2-1 拡張機能がインストールされる
インストールできたら Web Shell を開き python
を実行するとインタープリターを利用できます。Workspace 上の .py
の実行も可能です。
図 2-2 Web Shell で Python を実行
.wasm
で Hello World する拡張機能を作ってみる
ここからは、種類別に Web 版の VSCode で .wasm
を利用する拡張機能の作り方についてなど。
まずは、Web Shell を利用しない簡単な拡張機能を作ってみます。この手順では下記のような拡張機能を作れます。
なお、今回は publish などは行わない簡易的なものなので、 yo
は使わずに手動でファイルを追加しています。
package.json
下記のような package.json
を用意します。
基本的にはコマンドを登録するタイプの Web 拡張機能とほぼ一緒です。ここでは .wasm
を扱うための部分について少し解説。
-
extensionDependencies
は依存する拡張機能を指定する記述です- 上記では wasm-wasi のコア機能を提供する拡張機能(
ms-vscode.wasm-wasi-core
)を指定しています - ただし、現状では pre release 拡張機能はインストールされないので手動インストールが必要です
- 上記では wasm-wasi のコア機能を提供する拡張機能(
-
dependencies
の@vscode/wasm-wasi
がコア機能にアクセスためのパッケージです-
npm install @vscode/wasm-wasi
とやると古いバージョンがインストールさるので注意してください(@next
を指定すると拡張機能とバージョンが合うようです)
-
-
外部パッケージは esbuild でバンドルしますが
.wasm
ファイルは含めません-
.wasm
は静的ファイルとして扱います
-
extension.ts
拡張機能のソースは下記のような extension.ts
となります。
こちらも、基本は一般的な Hello World なサンプル拡張機能と同じ作りです。
ただし、.wasm
の読み込みについては少し注意点があります。サンプルによっては Node.js の API を使っている場合もありますが、これは環境に依存してしまいます(Web 版では基本的に使えない)。
下記のように拡張機能のコンテキストからリソースの URI を決定して読み込むと Web 版などでも読み込めるようになります。
リスト 3-1 hello.wasm
は VSCode の API と URI で読み込む
const filename = Uri.joinPath(
context.extensionUri,
'wasm',
'bin',
'hello.wasm'
)
const bits = await workspace.fs.readFile(filename)
hello.wasm
通常は C 言語や Rust などで .wasm
をビルドしたりしますが、上記リポジトリでは下記のような .rs
を変換したものを wasm/bin/hello.wasm
へ配置してあります。今回はこれをそのまま利用します。
リスト 3-2 hello.wasm
のソース
fn main() {
println!("Hello, world!");
}
図 3-1 静的ファイルとしてプロジェクト内に配置
その他のファイル
tsconfig.json
などを適宜配置します。
実行してみる
Web 版の拡張機能を試しに実行する方法はいくつかりますが、今回はなるべく実環境に近い方がよいので下記の方法を npm スクリプトにしました[1]。
下記にように実行するとサーバーが開始されます。
図 3-2 拡張機能をビルドしサーバーを開始
$ npm ci
$ npm run build
$ npm run serve:http
insiders.vscode.dev 側でコマンドパレットから Developer: Install Extension From Location
を実行し http://localhost:5000
を指定します。少し待つと拡張機能がインストールされます。
図 3-3 拡張機能がインストールされる
インストールされない場合は Developer: Show Window Log
などを見るとエラーが表示されていることがあります[2]。
図 3-4 アドレスを間違えている場合のログ
インストールできたら wasm: Run hello
を実行するとターミナルが開いて Hello World が表示されます。
図 3-5 Hello Workd が表示される
今回の .wasm
は Hello World するだけですが、リポジトリの Dev Container には Rust で .wasm
をビルドできる環境も入っています。いろいろ試してみると面白いかと思います。
Web Shell から実行できる拡張機能を作ってみる
Web Shell のターミナルで通常のコマンドのように実行できる拡張機能についてです。ここでは、主に上記の拡張機能との違いについて記述します。
まずは、依存する拡張機能として Web Shell を追加します。
リスト 4-1 依存関係の追加
"extensionDependencies": [
"ms-vscode.wasm-wasi-core",
"ms-vscode.webshell"
],
下記のように contributes
を定義すると /usr/bin/hello
に仮想的なファイルがマントされます。Web Sehll でこのファイルを実行すると拡張機能(extension.ts
)側の wasm-wasi-hello.webshell
が実行されるようになります。
リスト 4-2 コマンドのファイルをマウント
"contributes": {
"webShellMountPoints": [
{
"mountPoint": "/usr/bin/hello",
"command": "wasm-wasi-hello.webshell"
}
]
},
拡張機能側では commands.registerCommand
した関数に引数などの情報が渡されるので 、それを元にプロセスを作成します。
リスト 4-3 マウントされたファイルに対応するコマンドを登録
commands.registerCommand(
'wasm-wasi-hello.webshell',
async (
_command: string,
args: string[],
_cwd: string,
stdio: Stdio,
rootFileSystem: RootFileSystem
): Promise<number> => {
// ここにプロセスを作成するコードを記述
あとは、さきほどと同じように拡張機能をインストールすれば Web Shell のターミナルから hello
コマンドを利用できます。
図 4-1 hello
をコマンドとして実行(引数も渡せる)
だいたいは一般的な CLI ツールのようにできそうですが、カレントディレクトリの扱いが異なるようなのでファイルは絶対パスにする必要がありました。
Python を実行できる拡張機能を作ってみる
さきほど動かしてみた Python の拡張機能についてです。基本は Web Shell 用にファイルマウントするだけですが、Python の場合は外部に標準ライブラリーも必要になるので、その辺についてなど。
Python 本体
上の方でも少し書きましたが、WASI 対応の CPython については下記で公開されている .zip
ファイルを展開してそのまま利用します(パッチを適用してビルドなどは必要ありませんでした)。
拡張機能側の処理
基本のファイルの部分は Web Shell 用拡張機能の hello.wasm
を python.wasm
を置き換えることになりますが、先ほども触れたカレントディレクトリの問題があります。サンプルには回避処理が入っていたので、そのまま利用しています。
リスト 5-1 カレントディレクトリ関連の回避処理
// WASI doesn't support the concept of an initial working directory.
// So we need to make file paths absolute.
// See <https://github.com/WebAssembly/wasi-filesystem/issues/24>
const optionsWithArgs = new Set([
'-c',
'-m',
'-W',
'-X',
'--check-hash-based-pycs'
])
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (optionsWithArgs.has(arg)) {
const next = args[i + 1]
if (next !== undefined && !next.startsWith('-')) {
i++
continue
}
} else if (arg.startsWith('-')) {
continue
} else if (!arg.startsWith('/')) {
args[i] = `${cwd}/${arg}`
}
}
標準ライブラリー
Python では標準ライブラリーもマウントする必要があります。これは必要なファイルを wasm/lib
の下へコピーし下記のように定義を加えることで実現できます。
リスト 5-2 標準ライブラリーマウントの設定
"contributes": {
"webShellMountPoints": [
{
"mountPoint": "/usr/local/lib/python3.12",
"path": "wasm/lib"
},
{
"mountPoint": "/usr/bin/python",
"command": "wasm-wasi-python.webshell"
}
]
},
図 5-1 ライブラリーのファイルを静的ファイルとして追加
ただし、Web 環境で利用する場合は、ファイルツリーを扱うために .dir.json
を生成しておく必要があります。こちらは @vscode/wasm-wasi
に付属している dir-dump
で生成できます。
図 5-2 dir-dump
でディレクトリの情報を生成
$ npx dir-dump --out wasm/lib.dir.json wasm/lib
$ ls -d wasm/lib*
wasm/lib
wasm/lib.dir.json
セットアップ用のスクリプト
ファイルを展開したり .dir.json
を生成するのはやはり少し手間がかかります。この辺は Python 以外のコマンドを拡張機能化するときにも流用できそうなので、スクリプトにしておくと良いかと思います。
おわりに
VSCode の Web Sell 用拡張機能を作り CPython を動かしてみました。
CodeSpaces などを利用しないブラウザー上の VSCode でもここまで動くのはやはり面白いです。テスト用のフォルダーには Python 以外の実行環境もあったので、今後の展開がとても楽しみです。
また、extensionDependencies
で拡張機能の依存関係を指定できるので、「Python のスクリプトを拡張機能として配布する」といったこともやりやすくなるのかなと思います。
Discussion