🎋

zeriyoshi/pskel を読んで動かしてみる

2023/07/08に公開

昨日、 PHP Extension の記事を公開したら、「 PHP Extension の雛形作っているよ(&公開しているよ)」という話を聞いたので、早速触っていこうと思います。

https://twitter.com/zeriyoshi/status/1677263623595368450

https://zenn.dev/meihei/articles/abe08e20884080

読むパート

動いている GitHub Actions を見てみる

まず、 GitHub Actions が動いているので確認します。

arch × version × type × distro の数のマトリックスで実行されています(圧巻ですね。)

やっていることは Docker のビルドと、docker runなので、次は Dockerfile を確認します。

Dockerfile

FROM ${IMAGE}:${TAG} で PHP のイメージを持ってきて、 RUN ~~ のところで、ディストーションやコンパイラ( GCC or Clang )によって場合分けした PHP のビルドを行っているみたいです。

続いて ci.sh を Docker 内に持ってきて、 ENTRYPOINT で docker run の実行コマンドとして設定されています。
https://github.com/zeriyoshi/pskel/blob/879fe9e2fa11ea1695663a10ccf45ed6a69fc3dd/Dockerfile#L66-L68

最後に ./ext ディレクトリがコピーされます。

https://github.com/zeriyoshi/pskel/blob/879fe9e2fa11ea1695663a10ccf45ed6a69fc3dd/Dockerfile#L70

ci.sh

ci.sh は、先程 Dockerfile でコピーした /ext をビルドするスクリプトになっています。
与えられた環境変数によって実行するコマンドが少し変わっていますが、基本的には phpize ./configure を実行して、 make -j$(nproc) でビルド、 make test でテストをしているみたいです。
逆に、環境変数を与えないと何も実行されないようになっています。

https://github.com/zeriyoshi/pskel/blob/879fe9e2fa11ea1695663a10ccf45ed6a69fc3dd/ci.sh#L3-L10

この環境変数は GitHub Actions の実行コマンドを例に見ると、どう使えばいいかわかります。

https://github.com/zeriyoshi/pskel/blob/879fe9e2fa11ea1695663a10ccf45ed6a69fc3dd/.github/workflows/ci.yaml#L34-L36

動かすパート

テンプレートから作ってみる

Pskel は Template として公開されているので、 早速使ってみることができます。

Create a new repository を押して、このテンプレートを使った repository を作成します。

今回も例のごとく HashTable を表示する PHP Extension の my_print_ht というリポジトリを作ってみました。

https://github.com/meihei3/my_print_ht

とりあえず動かす

git clone して Dockerfile を開いてみると、「俺を押せ」ボタンがあったので実行してみます。
(これは docker build && docker run を実行しています。[1]

何も環境変数を設定していないので、 ci.sh は何も実行せず、正常にコンテナが削除されて終了します。

Successfully built 376f38060922
Creating container…
Container Id: f25730ac363d56348b51382964a4b51b1acfc0a1ff2cbb9056e8e6d340ec9cd6
Container name: '/affectionate_jang'
Starting container '/affectionate_jang'
'<unknown> Dockerfile: Dockerfile' has been deployed successfully.

TEST_EXTENSION=1で環境変数を設定し、 Docker 環境内で PHP を動かして見ます。

ENTRYPOINT で PHP Extension のビルドが行われるので、ビルド後に/bin/bashを実行します。

ci.sh
+ exec /bin/bash

起動した Docker 環境のターミナルで-d extension=/ext/modules/skeleton.soをつけて PHP を実行。動きました。

名前を変える

今は雛形のままであり、 skeleton という名前のままなので、まずは名前を変えてみます。

名前の変更は全て機械的に行いました。

https://github.com/meihei3/my_print_ht/commit/44429f66c0b18c1493869b54a31192d29c4a5e0b

先程同様動かしてみます。

$ php -d extension=/ext/modules/my_print_ht.so -a

動きました🙌

Extension を書いてみる

HashTable のプロパティの情報を print する Extension を作ってみます。

my_print_ht.c
/* {{{ void array_info( [ array $a ] ) */
PHP_FUNCTION(array_info)
{
	zval *arr;
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ARRAY(arr)
	ZEND_PARSE_PARAMETERS_END();

	php_printf("ht->nTableMask: %#x\r\n", Z_ARRVAL_P(arr)->nTableMask);
	php_printf("ht->nNumUsed: %d\r\n", Z_ARRVAL_P(arr)->nNumUsed);
	php_printf("ht->nTableSize: %d\r\n", Z_ARRVAL_P(arr)->nTableSize);
	php_printf("ht->nNumOfElements: %d\r\n", Z_ARRVAL_P(arr)->nNumOfElements);
	php_printf("ht->nNextFreeElement: %d\r\n", Z_ARRVAL_P(arr)->nNextFreeElement);
	php_printf("ht->nInternalPointer: %d\r\n", Z_ARRVAL_P(arr)->nInternalPointer);
}
/* }}}*/
my_print_ht.stub.php
function array_info(array $a): void {}

ちゃんとテストも書きます。

004.phpt
--TEST--
array_info() Basic test
--EXTENSIONS--
my_print_ht
--FILE--
<?php
array_info([]);
array_info([0, 1, 2, 3]);
?>
--EXPECT--
ht->nTableMask: 0xfffffffe
ht->nNumUsed: 0
ht->nTableSize: 8
ht->nNumOfElements: 0
ht->nNextFreeElement: 0
ht->nInternalPointer: 0
ht->nTableMask: 0xfffffffe
ht->nNumUsed: 4
ht->nTableSize: 8
ht->nNumOfElements: 4
ht->nNextFreeElement: 4
ht->nInternalPointer: 0

これで再び docker build && docker run をして PHP を起動します。

PHP Extension が作れました!

CI を通す

作成出来た PHP Extension を GitHub に push すると、 GitHub Actions が実行されます。しかし、コケます。

+ exec /bin/bash
/usr/bin/ci: exec: line 40: /bin/bash: not found
Error: Process completed with exit code 127.

/bin/bashが無いと言われていますが、これはローカル環境検証用なので、 push されたリポジトリ( CI で動作を保証する部分)には不要です。

今回は、TEST_EXTENSION=1の場合と全く同じ構造で、RUN_LOCAL=1の場合のみ実行可能なブロックを作り、その中にexec /bin/bashを入れました。

ci.sh
+if test "${RUN_LOCAL}" != ""; then
+  cd "/ext"
+  phpize
+  ./configure --with-php-config=$(which php-config)
+  make clean
+  make -j$(nproc)
+  TEST_PHP_ARGS="--show-diff -q" make test
+  exec /bin/bash
+fi

...

-exec /bin/bash

こうして、docker run時に引き渡す環境変数を書き換えることで、これまで通りローカル環境で実行が出来ます。

Push して数分待つと、 All Passed となりました!

終わりに

実際に触ってみた所感、雛形として整っているし、 Docker さえ動けば開発出来るし、 CI で動作保証出来るし、新規で PHP Extension を作るには便利だなと思いました。

↓ぜひスターを↓

https://github.com/zeriyoshi/pskel

〜雑談〜
「情報は発信するところに集まる」はマジだなぁと思いつつ、「先ず試してみる」ということ[2]をやってみました。
Extension 開発で培われる知見は php-src にも活かせると思うので、気軽に PHP Extension 開発する人が増えると良いですね。

脚注
  1. PphStorm を使っていますが、 JetBrains の IDE ならどれでも同じことができると思います ↩︎

  2. 昨日エンジニアと飲み会に行っていて、そこで同席していたある方が話されていました ↩︎

Discussion