Deno を Solaris (OpenIndiana) で動かす

JavaScript/TypeScript ランタイムの Deno を OpenIndiana に移植しました。

なぜ Deno?

deno.land ja.wikipedia.org

Deno は Node.js の作者である Ryan Dahl によって作成された JavaScript/TypeScript ランタイムです。詳細は上記 Wikipedia を参照してください。

最近の Vim 界隈では Deno を利用したプラグインエコシステム Denops が流行っており、OpenIndiana でも使ってみたいと思ったものの、Deno の対応 OS は Windows, macOS, Linux だけです。

github.com

そこで、OpenIndiana に移植することにしました。

ビルド環境の準備

Deno のビルドに必要なソフトウェアをインストールします。

Deno は Rust で書かれているため、Rust のツールチェインが必要です。Rustup を使ってツールチェインをインストールします。

rustup.rs

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# ~/.cargo/bin にパスを通しておく
rustup update

OpenIndiana のパッケージシステム pkg でビルドに必要なパッケージをインストールします。

sudo pkg install gcc-7 clang-13 ninja

gcc-7 の依存パッケージ g++-7-runtime により libstdc++ がインストールされ、g++ および clang++ は C++ プログラム生成時にこれをリンクします。 しかし、この libstdc++ はデフォルトの 64bit ライブラリパス /lib/amd64:/usr/lib/amd64 ではなく /usr/gcc/7/lib/amd64 下に配置されるため、そのままではリンク時に失敗します。

今回は、crle でライブラリパスを追加することで対処します。

sudo crle -64 -u -l /usr/gcc/7/lib/amd64

その他、メタビルドシステム gn が必要ですが、これはソースからビルドする必要があります。

gn.googlesource.com

# リポジトリを取得
git clone https://gn.googlesource.com/gn
cd gn

# ninja のビルドファイルを生成
# clang はデフォルトでは 32bit バイナリを出力するため、"-m64" オプションを指定して 64bit バイナリにする (必須ではない)
# ar は /usr/gnu/bin 下にある gnu ar ではフォーマットが合わない (Solaris の ar が必要) ため、PATH に /usr/gnu/bin が入っている場合はフルパスを指定する
CXX='clang++ -m64' AR=/usr/bin/ar LD=/usr/bin/ld python build/gen.py

# ビルド
ninja -C out

パスの通っているディレクトリにインストールします。

# ~/.local/bin にパスを通しておく
install -m755 out/gn ~/.local/bin

移植する

Deno のバージョンは執筆時点 (2022/5/9) での最新である 1.21.2 とします。

依存ライブラリのうち、illumos (OpenIndiana のカーネル) 対応していないものについては、ソースコードをローカルにダウンロードして変更を加えます。

errno

github.com

最新バージョンでは illumos に対応していますが、Deno はそれより古いバージョンを参照するため、Cargo.toml を変更してバージョンを詐称します。

git clone https://github.com/lambda-fairy/rust-errno
cd rust-errno
git checkout v0.2.8
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [package]

 name = "errno"
-version = "0.2.8"
+version = "0.1.8"
 authors = ["Chris Wong <lambda.fairy@gmail.com>"]

 license = "MIT/Apache-2.0"

v8

github.com

JavaScript エンジン V8 の Rust バインディングを提供するライブラリです。

Solaris/illumos 用のビルド設定を追加することが主な内容になります。

git clone https://github.com/denoland/rusty_v8
cd rusty_v8
git checkout v0.42.0
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,2 +1,9 @@
 [target.aarch64-linux-android]
 linker = "./third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang++"
+
+[target.x86_64-unknown-illumos]
+rustflags = ["-C", "link-args=-lffi -lstdc++"]
+
+[env]
+V8_FROM_SOURCE = "1"
+CLANG_BASE_PATH = "/usr"
--- a/build/config/BUILDCONFIG.gn
+++ b/build/config/BUILDCONFIG.gn
@@ -211,6 +211,12 @@ if (host_toolchain == "") {
     }
   } else if (host_os == "aix") {
     host_toolchain = "//build/toolchain/aix:$host_cpu"
+  } else if (host_os == "solaris") {
+    if (is_clang) {
+      host_toolchain = "//build/toolchain/solaris:clang_$host_cpu"
+    } else {
+      host_toolchain = "//build/toolchain/solaris:$host_cpu"
+    }
   } else {
     assert(false, "Unsupported host_os: $host_os")
   }
@@ -253,6 +259,12 @@ if (target_os == "android") {
   _default_toolchain = "//build/toolchain/win:uwp_$target_cpu"
 } else if (target_os == "aix") {
   _default_toolchain = "//build/toolchain/aix:$target_cpu"
+} else if (target_os == "solaris") {
+  if (is_clang) {
+    _default_toolchain = "//build/toolchain/solaris:clang_$target_cpu"
+  } else {
+    _default_toolchain = "//build/toolchain/solaris:$target_cpu"
+  }
 } else {
   assert(false, "Unsupported target_os: $target_os")
 }
@@ -291,6 +303,7 @@ is_linux = current_os == "linux"
 is_mac = current_os == "mac"
 is_nacl = current_os == "nacl"
 is_win = current_os == "win" || current_os == "winuwp"
+is_solaris = current_os == "solaris"

 is_apple = is_ios || is_mac
 is_posix = !is_win && !is_fuchsia
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -399,7 +399,7 @@ config("compiler") {
       # compute, so only use it in the official build to avoid slowing down
       # links.
       ldflags += [ "-Wl,--build-id=sha1" ]
-    } else if (current_os != "aix") {
+    } else if (current_os != "aix" && current_os != "solaris") {
       ldflags += [ "-Wl,--build-id" ]
     }

@@ -1244,7 +1244,7 @@ config("compiler_deterministic") {
   # Tells the compiler not to use absolute paths when passing the default
   # paths to the tools it invokes. We don't want this because we don't
   # really need it and it can mess up the goma cache entries.
-  if (is_clang && !is_nacl) {
+  if (is_clang && !is_nacl && !is_solaris) {
     cflags += [ "-no-canonical-prefixes" ]
   }
 }
@@ -2046,7 +2046,7 @@ if (is_win) {
         "-Wl,-no_function_starts",
       ]
     }
-  } else if (current_os != "aix") {
+  } else if (current_os != "aix" && current_os != "solaris") {
     # Non-Mac Posix flags.
     # Aix does not support these.
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -197,7 +197,7 @@ declare_args() {
   # Not supported for macOS (see docs/mac_lld.md), and not functional at all for
   # iOS. But used for mac cross-compile on linux (may not work properly).
   # The default linker everywhere else.
-  use_lld = is_clang && (!is_apple || host_os == "linux")
+  use_lld = is_clang && ((!is_apple && !is_solaris) || host_os == "linux")
 }

 declare_args() {
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -376,6 +376,8 @@ template("gcc_toolchain") {
         # AIX does not support either -D (deterministic output) or response
         # files.
         command = "$ar -X64 {{arflags}} -r -c -s {{output}} {{inputs}}"
+      } else if (current_os == "solaris") {
+        command = "$ar {{arflags}} -r -c -s {{output}} {{inputs}}"
       } else {
         rspfile = "{{output}}.rsp"
         rspfile_content = "{{inputs}}"
--- /dev/null
+++ b/build/toolchain/solaris/BUILD.gn
@@ -0,0 +1,46 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/toolchain/gcc_toolchain.gni")
+
+gcc_toolchain("clang_x64") {
+  prefix = rebase_path("${clang_base_path}/bin", root_build_dir)
+  cc = "${prefix}/clang"
+  cxx = "${prefix}/clang++"
+  readelf = "readelf"
+  nm = "/usr/bin/nm"
+  ar = "/usr/bin/ar"
+  ld = cxx
+
+  # Output linker map files for binary size analysis.
+  enable_linker_map = true
+
+  toolchain_args = {
+    current_cpu = "x64"
+    current_os = "solaris"
+
+    is_clang = true
+  }
+}
+
+gcc_toolchain("x64") {
+  cc = "gcc"
+  cxx = "g++"
+  readelf = "readelf"
+  nm = "/usr/bin/nm"
+  ar = "/usr/bin/ar"
+  ld = cxx
+
+  # Output linker map files for binary size analysis.
+  enable_linker_map = true
+
+  toolchain_args = {
+    current_cpu = "x64"
+    current_os = "solaris"
+
+    # reclient does not support gcc.
+    use_rbe = false
+    is_clang = false
+  }
+}
--- a/build.rs
+++ b/build.rs
@@ -251,6 +251,10 @@ fn platform() -> &'static str {
   {
     "mac"
   }
+  #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+  {
+    "solaris"
+  }
 }

 fn download_ninja_gn_binaries() {
--- a/v8/BUILD.gn
+++ b/v8/BUILD.gn
@@ -5148,7 +5148,7 @@ v8_component("v8_libbase") {
       "src/base/platform/platform-posix.cc",
       "src/base/platform/platform-posix.h",
     ]
-    if (current_os != "aix") {
+    if (current_os != "aix" && current_os != "solaris") {
       sources += [
         "src/base/platform/platform-posix-time.cc",
         "src/base/platform/platform-posix-time.h",
@@ -5173,6 +5173,17 @@ v8_component("v8_libbase") {
     ]

     libs = [ "dl" ]
+  } else if (current_os == "solaris") {
+    sources += [
+      "src/base/debug/stack_trace_posix.cc",
+      "src/base/platform/platform-solaris.cc",
+    ]
+
+    libs = [
+      "dl",
+      "socket",
+      "rt",
+    ]
   } else if (is_android) {
     if (current_toolchain == host_toolchain) {
       libs = [

deno

Deno 本体のコードについては、 Solaris/illumos 対応のプルリクエストを出し、1.20.4 でマージされました。

github.com

Cargo.toml を編集し、ローカルのライブラリを参照するように変更します。

git clone https://github.com/denoland/deno
cd deno
git checkout v1.21.2
--- a/.cargo/config
+++ b/.cargo/config
@@ -12,3 +12,10 @@ rustflags = [

 [target.aarch64-apple-darwin]
 rustflags = ["-C", "link-arg=-fuse-ld=lld"]
+
+[target.x86_64-unknown-illumos]
+rustflags = ["-C", "link-args=-lffi -lstdc++"]
+
+[env]
+V8_FROM_SOURCE = "1"
+CLANG_BASE_PATH = "/usr"
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -174,3 +174,7 @@ opt-level = 3
 opt-level = 3
 [profile.release.package.zstd-sys]
 opt-level = 3
+
+[replace]
+"errno:0.1.8" = { path = "../rust-errno" }
+"v8:0.42.0" = { path = "../rusty_v8" }

ビルドします。

cargo build --release -vv

V8 のビルド途中で clang がクラッシュすることがありますが、原因は深掘りしていません。 再度 cargo build を実行すると成功するので、システムリソース関係かもしれません。

テストを通したいのですが、現状ではネットワーク関連の項目でハングします。今後の課題とします。

cargo test -vv

インストールします。ワンバイナリで構成されているため、取り回しがしやすいのも Deno の特長の一つです。

install -m755 target/release/deno ~/.cargo/bin

動作確認

TypeScript で簡単なサーバーを書いてみます。

main.ts

import { Server } from "https://deno.land/std@0.134.0/http/mod.ts";

const handler = function(_req: Request): Response {
    return new Response("Hello World!");
};

const port = 8000;
const server = new Server({port, handler});
console.log("start server");
await server.listenAndServe();
deno run --allow-net main.ts
curl http://127.0.0.1:8000
# Hello World!

テストが通ってないので不安でしたが、これくらいなら問題なさそうです。

Denops プラグインを試す

Denops を利用するプラグインが動作するか確認します。

Vim 上で SKK 日本語入力を提供する skkeleton を試してみました。

zenn.dev

少し動かしただけですが、特に問題なさそうでした。

まとめ

Deno を OpenIndiana に移植しました。これで OpenIndiana の Vim ユーザーでも Denops が利用できますね。

Gist にパッチを置いています。

deno.patch · GitHub

参考リンク

移植については FreeBSD のパッチを参考にしました。

https://svnweb.freebsd.org/ports/head/www/deno/files/