nodejsのソースコードのビルド
今現在(2023-08-01)raspbian はbullseyeベースなのでaptでnodejsをインストールするとnodejs12が入ります。
ただ、zenn-cliがnodejs12以下はサポート切ってたりで、bullseyeのnpmからzenn-cliをインストールしようとすると怒られます。一応zenn-cliは入りますが、警告だが、非推奨だかのメッセージが出ます。
よってraspberry piからapt->npm->zenn-cliという感じに怒られずにええ感じにインストールできない。
単純にnodejs関連のパッケージだけstable bookwormのレポジトリを向くようにしてもいいですが、それでも最新のLTSでないし、どうせ最新のLTS使わないならまあせっかく?なんでビルドしましょうと思ってやってみました。
前提条件
github cliインストール済み
tmuxインストール済み。
sysstatインストール済み(統計取るのに必須。特に取るつもりがないなら不要)。
ソースコードの準備
# gh repo clone用のディレクトリ作成
mkdir -p ~/src/nodejs
cd src/nodejs
githubからーソースコードをダウンロードする
gh repo clone nodejs/node
cd node
# タグ一覧表示
git tag
# 使いたいバージョンのタグにcheckout
git checkout v18.17.1
以下はプロジェクト直下にいることを前提に説明していきます。
ビルド方法
ビルド方法はnodejsの場合はBUILDING.mdに書いてあります。
大抵のプロジェクトにはローカルのビルド方法が書いてあるので、その通りにやってください。
無い場合はソースコードを読んだり、実際ビルドしたときにエラーが出るlibファイルを見たり、Makefile読んだりで自分で調べましょう。
古いプロジェクトでない限り、どこかにやり方が書かれていて、How to的な環境は整っていると思います。
nodejsの説明はかなり丁寧なので、わかりやすいです。Readmeや環境構築の説明の書き方の例として参考にしたらいいかも。
依存関係
BUILDING.mdに書いてある通り、Debianは下のようなパッケージが必要です。
sudo apt install python3 g++ make python3-pip
このpythonですが、nodejs v18まで(v19もかも未検証)はpython3.9~3.6がないとビルドできないので注意。最新のnodejs v20ならpython3.11でもビルドできます。
configure
ビルドするときのconfigです。
特に気にしなくていいです。
./configure
rootのときに/usr/local/binと/usr/binが干渉しあってしまうのが嫌なら、下のようにインストール先を変えます
# こうすると/usr/local/nodeのディレクトリを使ってその下にlibやbinを作ってくれる。
./configure --prefix=/usr/local/node
使うときには
PATHに/usr/local/node/binへのパスを通すか、
/usr/local/node/bin/nodeのように直接実行します。
build
tmuxで起動
これはssh経由でビルドしていたりして、sshのセッションが中断されることにより、
処理が中断されないように必要です。
tmuxである必要はありませんが、一番考えなくてもいいのがtmuxですし、愛好家は多いのでこれを使うのが良いかと。
tmuxで適当なセッション作っておきましょう。
tmux
ログ置き場
とりあえずビルドして使うだけでも結構時間がかかるので、ログだけは最低でもとっておいた方が
良いと思います。流れてなくなってしまうので。
ログの場所はどこでもいいですが、自分は特に指定がないなら、一般ユーザーでビルドするときは
~/var/log/プロジェクト名/make
みたいなディレクトリを作ってそこに書き込んでいます。
ここではとりあえず、この場所をログ保管場所としておきましょう。
mkdir -p ~/var/log/nodejs/node/make
help
Makefileにはhelpが書かれているので、一度見てみましょう。
# make --helpでなくて、make helpであることに注意。
# make --helpだとmakeコマンドのhelpがでる。
make help
make installでdefaultのインストール先が/usr/localであることや、
make cleanでキャッシュの削除ができること、
make uninstallがあることを確認します(make uninstallできないプロジェクトもある)。
ビルドしてみる
Makefileをビルドしてみます。
何も引数がない場合はmake allとなってすべてビルドするみたいですね。
make -jはビルドするときのjob数の指定です。コア数に応じたjob数を選んでください。
ここではBUILDING.mdにはmake -j4としてビルドせよと書かれていますが、
raspberry piみたいに貧弱なpcなら-j2, -j3の方がいいかも。
これは貧弱なpcだとバックグラウンドの処理が相対的に重くなっているので、ビルド中にデスクトップが止まったりクラッシュする可能性があるためです。
ここら辺は実際にその環境で実行しないとわからないと思います。
コア数が気になるならlscpuや/proc/cpuinfoを見てコア数を調査しておきましょう。
ビルドのログは流れてしまうので下のようにログを取るのがいいかと。
こうしないと失敗時の切り戻しが面倒です。
# こうすると時間経過もログに残せる。
make -j4 2>&1 | awk '{print strftime("%Y-%m-%dT%H:%M:%S"), $0}' | tee ~/var/log/nodejs/node/make/"$(date +'%Y%m%dT%H%M%SZ')".log
pcのスペック的にしんどいかも知りたい人はこの時にtmuxで別セッションを開いておき、
負荷を調べておきましょう。何度もビルドする可能性があるなら測った方がいいです。
mkdir -p ~/var/log/nodejs/node/iostat/
# これをやると1秒間に一回、cpuやディスクioの統計をとってくれる。
iostat -x 1 | awk '{print strftime("%Y-%m-%dT%H:%M:%S"), $0}' | tee ~/var/log/nodejs/node/iostat/"$(date +'%Y%m%dT%H%M%SZ')".log
ビルドが始まったら、tmuxをデタッチ、sshを終了して、ビルドが終わるまでほっときましょう。
ビルド時間中に豆知識
cppをやったことない人はびっくりすると思いますが、warning出まくります。
また、膨大な量のログが出るのでびっくりすると思いますが、よくあることです。
正常なので、とくにエラーで停止しない限りほっときましょう。
自分がraspberry pi4Bでビルドしたところ、3時間から4時間ほどで
iostatで調べるとユーザーレベルでcpuが常に90%代でした。
rasperry pi はmicrosdカードにデータがあるので、ディスクioもある程度ネックになるかと思いましたが、iowaitがほとんど無かったので、統計的にはcpuの方が足引っ張ってますね。
負荷を少しでも下げたい場合、特にraspberrypi でビルドしている人はGUIを切ってやった方がいいかも。
私はraspberry pi4Bで最初にビルドした場合はクラッシュしたのでsystemctl set-default multi-user.targetとしてcuiにしてビルドしなおしました。
これらの数値はあくまで私の環境での話であり、高温、低温下や、microsdカードの空き容量や種類によるストレージの速度、電源が足りているかなどでも速度は変わると思います。
参考までに。
ただ、最新のPCなら30分から1時間で終わりそうなので、新しいPC使っている人はあまり気にしなくていい気もします。
出来立てほやほやを使ってみる
ビルドできたなら使ってみます。
下記のコマンドを実行すると対話形式で立ち上がると思います。
./out/Realease/node
リリースバージョンかつbookwormでも採用されたバージョンなので大丈夫だと思いますが、make installする前に軽く動作確認しときましょう。
install
インストール場所にインストールします。
実行すると依存関係を考えて、binや、includeなどを適切な場所に入ります。
これは2~5分程度で終わります。
aptやdnfなどパッケージ管理ツールでなくMakefileでビルドしたなど、自分で勝手に入れたものはlinuxでは/usr/local/配下に入れるのが普通です。
先ほどの説明通り、下のコマンドを実行すると/usr/local/に各ファイルが入ります。
sudo make install
ビルドした複数のバージョンを分けて使いたい
previewやらなんやら、後自分でビルドしたやつとかを使い分けたい場合は擬似的に仮装環境や
なんとかenvみたいなものを実装する必要があります。
とりあえずビルド時にnode用に深いルートができるように、調節します。
versionにビルドバージョンを書く
ただ、update時や削除時にprefixが無いと正しい位置のnodejsとnpmのライブラリを削除しないので注意。
# 17.2-previewとか最新のmainならunstableとか適当に名前をつける。
version=18.17.1
./configure --prefix=/usr/local/node/version/${version}
あとはsudo make install時に勝手にインストールしてくれる
sudo make install
こうすると/usr/local/node/binにnodeとnpmのシンボリックリンクを置いておくといい感じに
やってくれます。
sudo ln -s /usr/local/node/version/${version}/bin/node /usr/local/node/bin/node
sudo ln -s /usr/local/node/version/${version}/bin/npm /usr/local/node/bin/npm
sudo ln -s /usr/local/node/version/${version}/bin/npx /usr/local/node/bin/npx
なぜ/usr/local/binに実行ファイルを置かないかというと、rootのパスはデフォルトで/usr/binより/usr/local/binのほうが優先なので、/usr/local/binと/usr/binに同じファイルを置くとシステムに影響を与えてしまうからです。
/usr/local/node/binを使いたいときにPathに追加しましょう。
ただ複数のバージョンを使い分ける場合はnodejsとnpmが常に使いたい
バージョンのシンボリックリンクを指しているか注意しましょう。
面倒になるなら下のようななんとかenvを自作してスクリプトとして持っておきます。
ここでは仮にnodelocalsという名前にします。
#!/usr/bin/env bash
#
# nodejs /usr/local/node/version/\$version version management
#######################################
# nodejs /usr/local/node/version/\$version version management
# Globals:
# None
# Arguments:
# All.
# Outputs:
# format
# Returns:
# 0 if command success, non-zero on error.
# Example:
# nodelocals list
# # out put installed version from /usr/local/node/version/\$version.
# nodelocals set \$version
# # set node, npm, npx to /usr/local/node/bin
#######################################
function nodelocals() {
local i
local new_array=( $@ )
local version
# nodeがない場合は空文字
local selected_version=$(readlink /usr/local/node/bin/node | awk -F/ '{print $6}')
for ((i=0;i<$#;i++)); do
# if find list flags from args, show installed nodejs destination /usr/local/node/bin/node.
if [ "${new_array[$i]}" = "list" ]; then
# 引数がlist以外にあるとおかしい。
if [ -n "$2" ]; then
echo 'list is no argument.' >&2
return 1
fi
# echo 使っているものに*がつくようにする。
local selected
for version in $(ls -1 /usr/local/node/version); do
selected=" "
if [ "${version}" = "${selected_version}" ]; then
selected='*'
fi
# アスタリスクをそのままecho に通すと展開されるので""をつける。
echo "$selected" $version
done
return 0
fi
# localが選ばれたら、
if [ "${new_array[$i]}" = "set" ]; then
if [ ${UID} -ne "0" ] && [ ${EUID} -ne "0" ]; then
echo 'this function use superuser only' >&2
return 1
fi
# version取得
shift
version=$1
local check_version
local exist=1
# versionの存在確認
for check_version in $(ls -1 /usr/local/node/version); do
if [ "${version}" = "${check_version}" ]; then
exist=0
fi
done
if [ "$exist" = "1" ]; then
echo 'select installed version!' >&2
return 1
fi
# symlinkで紐付け
ln -snf /usr/local/node/version/${version}/bin/node /usr/local/node/bin/node
ln -snf /usr/local/node/version/${version}/bin/npm /usr/local/node/bin/npm
ln -snf /usr/local/node/version/${version}/bin/npx /usr/local/node/bin/npx
return 0
fi
done
}
function usage() {
cat 1>&2 <<EOF
nodelocals
nodejs /usr/local/node/version/\${version} allignment version management tools
USAGE:
nodelocals [FLAGS] [OPTIONS]
FLAGS:
list Show /usr/local/node/version/\$version installed nodejs version engines.
set Set node, npm, npx to /usr/local/node/bin
-h, --help Prints help information
OPTIONS:
--debug Set bash debug Option
EOF
}
function main {
local i
local new_array=( $@ )
for ((i=0;i<$#;i++)); do
if [ "${new_array[$i]}" = "--help" ] || [ "${new_array[$i]}" = "-h" ]; then
usage
return
fi
# if find --debug flag from args, start debug mode.
if [ "${new_array[$i]}" = "--debug" ]; then
set -x
trap "
set +x
trap - RETURN
" RETURN
unset new_array[$i]
fi
done
# reindex assign.
new_array=${new_array[@]}
nodelocals $new_array
}
main $@
これに実行権限を与えておきます。
sudo chmod 755 /usr/local/bin/nodelocals
各環境へのbinへのシンボリックリンクのディレクトリを作って置きます。
sudo mkdir -p /usr/local/node/bin
使い方
# /usr/local/nodeにインストールしたnodejs一覧表示
nodelocals list
# /usr/local/nodeに存在する指定したバージョンのnode, npm, npxを/usr/local/binにセットする。
sudo nodelocals set 18.9.1
これにインストール処理やら、なんやら追加されたらなんとかenvです。
実装じたいは割と簡単だと思います。
ただ、あんまやりすぎるとnodeenvやnvm,nに対する車輪の再発明になるので程々にね。
論点は全部自分で管理したいかどうかかな?
/usr/localみたいなシステムに複数バージョン置いときたいとか。
/usr/local/にインストールされたものを使ってみる
/usr/local/binにパスが通っていたら、ちゃんと実行できるはずです。
# 存在確認
command -v npm
command -v node
node -v
## 対話形式で実行してみる。
node
## zenn-cliをインストールしてみる。
sudo npm install -g zenn-cli
上に書かれている方法でインストール先を変更した場合はちゃんとnodeバージョンのversionごとにnpmのインストール先が変わっているはずです。
確認してみましょう
# bin
ls -1 /usr/local/node/version/${version}/bin
# lib
ls -1 /usr/local/node/version/${version}/lib
uninstall
Makefileによってはアンインストール方法が無いものもあります。
その場合は手動で消すことになりますが、nodejsはuninstallができるように書かれているので、
Makefileの場所で以下のようにすればアンインストールされます。
sudo make uninstall
上の方法でバージョンごとにインストール先を変えた場合は/usr/local/node/${version}/にそのnodejsに依存しているライブラリやバイナリが保存されています。なので、バージョンの環境ごとrm -rfで削除しても良いです。
# nodejs18.17.1の環境を削除する時
version=18.17.1
rm -rf /usr/local/node/version/${version}
先ほど書いたnodelocalsに uninstallみたいなの入れてもいいですが、自分で書く管理コストとか、利便性考えたらあんまないかも。他の人が使うなら必要かも。
updateしたい
アンインストールしてから、クリーン、gitで適切なバージョン、
configure, make,make installとします。
cleanしてから、gitで適切なバージョンにチェックアウトして
make installで上書きという形だとシステムに前のバージョンの依存関係のライブラリが残ります。
気をつけましょう。
## 残っているプロジェクトのMakefileを使ってアンインストール
sudo make uninstall
# キャッシュクリーン
make clean
# 使いたいバージョンにチェックアウト
git checkout v{適切なバージョン}
./configure
make
sudo make install
まとめ
手順に沿ってやれば出来るので、
特に難しい点は無いと思います。
なんとなく気がついたかと思いますが、Makefileでversionを分けずにsystemにinstallするならプロジェクトを持っておかないと、
アンインストールとupdate時に困ります。プロジェクトは持っておきましょう。
上の手順で実行したら、ビルド時にどの程度負荷がかかっていたのかわかるので、
「俺はnodejsに貢献したい!」とか、「JS王に俺はなる!」みたいな人は負荷が多過ぎたら、
時間がかかり過ぎてやってられないと思います。
おとなしくnodejs自体の開発環境用のPC買いましょう。
Discussion