開発環境の準備
開発用コンテナの起動
docker run -it --rm --workdir /work -p 0.0.0.0:7681:7681 rust:1.77.0-slim-bookworm bash
WORKDIR=/workttyd のソースコード取得
git インストール。
apt update
apt install -y gitttyd のリポジトリを clone.
cd $WORKDIR
git clone --depth 1 -b 1.7.5 https://github.com/tsl0922/ttydプロジェクトディレクトリへ移動。
cd $WORKDIR/ttydttyd をライブラリとしてビルド
ttyd の README を参照しながら、ビルドに必要なパッケージをインストールする。
apt install -y build-essential cmake git libjson-c-dev libwebsockets-dev前回の apt install と重複があるが、気にせず ttyd の
README からそのままコピペ。
CMakeLists.txt を修正
実行バイナリではなく、 .a を生成するように
CMakeLists.txt を修正する。
sed -i -e 's/add_executable(${PROJECT_NAME} ${SOURCE_FILES})/add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})/' ./CMakeLists.txtadd_executable: 実行バイナリを生成するadd_library:.aを生成する
関数 main の名前変更
別プログラムに組み込みたいので、エントリーポイントである
main が存在すると困る。
なので、 main を ttyd_main
へリネームする。
sed -i -e 's/main(/ttyd_main(/' ./src/server.c別プログラムから、 ttyd_main(2, ["ttyd", "-W", "bash"])
のような感じで呼び出すイメージ。
ttyd のビルド
mkdir build && cd build
cmake ..
makeこれで、 libttyd.a が生成される。
ttyd を組み込む Rust プログラムを作る
ttydwrapper というプロジェクトを作って、 固定値で
ttyd -W -t enableSixel=true bash
コマンドを実行するのと同じように ttyd を呼び出すプログラムを作る。
bindgen に必要なパッケージのインストール
C 言語との FFI をするにあたり、 bindgen
というコマンドを利用するので、それに必要なパッケージをインストールする。
apt update
apt install -y libclang-16-devCargo プロジェクトの作成
今回は、 ttydwrapper という名前で作成。
cd $WORKDIR
cargo new ttydwrapper
cd ttydwrapper
rustup component add rustfmtFFI 用のヘッダーファイルを作成
Rust から C の ttyd_main を呼び出すので、
ttyd_main
の定義が記載されているヘッダーファイルを作成する。
Rust では、一般的に、 binding.h
に必要なヘッダーファイルを列挙するらしい。
なので今回は、 ttyd.h に ttyd_main
を定義し、そのヘッダーファイルを binding.h
へ記載する構成でヘッダーを作成した。
mkdir include
cat << EOF > ./include/ttyd.h
void ttyd_main(int argc, char **argv);
EOF
cat << EOF > ./include/binding.h
#include "ttyd.h"
EOFバインディングを生成
bindgen コマンドで、ヘッダーから Rust
のコードを生成する。
cargo install bindgen-cli
bindgen ./include/binding.h > ./src/bindings.rs以下のような、 binding.rs が生成される。
/* automatically generated by rust-bindgen 0.69.4 */
extern "C" {
pub fn ttyd_main(argc: ::std::os::raw::c_int, argv: *mut *mut ::std::os::raw::c_char);
}生成された binding.rs を lib.rs
からインクルードさせるのが一般的らしいので libs.rs
を作る。
cat << EOF > ./src/lib.rs
include!("bindings.rs");
EOFライブラリをコピー
ライブラリは lib 以下に無いとダメなので .a
を移動する。
mkdir $WORKDIR/ttydwrapper/lib
cp $WORKDIR/ttyd/build/libttyd.a $WORKDIR/ttydwrapper/lib/ライブラリの情報を作成
build.rs
にビルド時に必要なライブラリ情報の列挙をする。
cat << EOF > ./build.rs
fn main() {
println!("cargo:rustc-link-search=native=$WORKDIR/ttydwrapper/lib");
println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu");
println!("cargo:rustc-link-lib=static=ttyd");
println!("cargo:rustc-link-lib=dylib=uv");
println!("cargo:rustc-link-lib=dylib=websockets");
println!("cargo:rustc-link-lib=static=ssl");
println!("cargo:rustc-link-lib=static=crypto");
println!("cargo:rustc-link-lib=static=z");
println!("cargo:rustc-link-lib=static=json-c");
}
EOFビルド時に
build.rs の設定を使用するように修正
Cargo.toml の [package] の末尾に
build = "build.rs" を追加する。
(sed で実現できなかったので vim で編集した)
main 関数の作成
Rust の main 関数を作成し、 ttyd_main
を呼び出すプログラムを作る。
cat << EOF > ./src/main.rs
use std::ffi::CString;
use std::os::raw::c_char;
use ttydwrapper::ttyd_main;
fn main() {
let args = vec!["tty", "-W", "-t", "enableSixel=true", "bash"];
let c_args: Vec<CString> = args.into_iter()
.map(|arg| CString::new(arg).expect("CString::new failed"))
.collect();
let mut argv: Vec<*mut c_char> = c_args.iter()
.map(|arg| arg.as_ptr() as *mut c_char)
.collect();
let argv_ptr: *mut *mut c_char = argv.as_mut_ptr();
unsafe {
ttyd_main(5, argv_ptr);
}
}
EOFビルド
cargo buildこれで ./target/debug/ttydwrapper が生成される。
動作確認
コンテナ起動時に 7681
をポートフォワーディングしているので、 ttydwrapper
を実行した後 http://localhost:7681 へアクセスすると ttyd
のターミナルが開く。
以上。
ここまで書いたところで、本当にやりたいのは Windows でだった事に気付いた…。
0 件のコメント:
コメントを投稿