こんにちは、高木です。
今回の話題は前処理とは少し違うのですが、密接に関連することなので取り上げておきたいと思います。PHPを使ってC言語やC++のソースファイルをコンパイルしようと思います。コンパイルするといっても、コンパイラー自体を作るのではなく、既存のコンパイラーを呼び出します。
PHPを使ってコンパイルすることにはいろいろメリットがあります。Makefileだと、ちょっと凝ったことをするには外部コマンドに依存してしまいます。外部コマンドに依存するということはプラットフォームにも依存することを意味します。Visual StudioのプロジェクトファイルのようなIDE固有の方法ならなおのこと特定環境に依存してしまいます。PHPでコンパイルすることで環境間の差を吸収することができます。
環境間の差を吸収するにはCMakeを使うことができます。もちろんCMakeでもかまわないのですが、PHPを使った方がいろいろ融通が利くのです。具体的にどんな融通が利くかはこれから追々解説していきたいと思います。
今回はPHPでコンパイルする話題に関しては初回ですので、簡単に概要を解説するだけにとどめます。次回以降、少しずつ深掘りしていければと考えています。
PHPを使ってコンパイルを行う上で最低限の要件は次のようなものがあると考えています。
- コンパイラーを子プロセスとして呼び出せること
- 標準エラーを加工無く出力できること
- 依存関係を解決できること
依存関係の解決を除けば、適切に子プロセスを実行することさえ出来ればなんとかなりそうです。PHPには子プロセスを呼び出す方法がいくつか用意されています。exec関数、shell_exec関数、system関数、passthru関数、popen関数、proc_open関数、そして実行演算子です。これらはすべて子プロセスを実行しますが、微妙に振る舞いが異なります。
バイナリーデータを含む生の出力を、まったく加工することなくそのまま出力するにはpassthru関数が適切です。標準出力や標準エラーをいったん取り込んで自由に加工するのであればproc_open関数が適切でしょう。
好みもあるのかもしれませんが、エラーメッセージなどの出力は、あまりバッファリングせずにすぐに出力してくれた方が助かります。また、出力するデータはエンコーディングが異なる場合もあれば改行文字が異なる場合もありますので、バイナリデータを扱えた方が何かと便利です。というわけで、いったんはpassthru関数を使うことにしようと思います。
とりあえずコンパイルするソースファイルとして次のようなhello.ccを考えてみます。
0 1 2 3 4 5 6 7 |
#include <iostream> int main() { cout << "hello" << endl; } |
ちょっとわざとらしい例ですが、あえてコンパイルエラーが出るように書いてみました。これを次のコマンドを使ってGCCでコンパイルしてみます。
0 1 2 |
g++ hello.cc |
すると当然エラーが発生します。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
hello.cc: In function ‘int main()’: hello.cc:5:3: error: ‘cout’ was not declared in this scope; did you mean ‘std::cout’? 5 | cout << "hello" << endl; | ^~~~ | std::cout In file included from hello.cc:1: /usr/include/c++/10/iostream:61:18: note: ‘std::cout’ declared here 61 | extern ostream cout; /// Linked to standard output | ^~~~ hello.cc:5:22: error: ‘endl’ was not declared in this scope; did you mean ‘std::endl’? 5 | cout << "hello" << endl; | ^~~~ | std::endl In file included from /usr/include/c++/10/iostream:39, from hello.cc:1: /usr/include/c++/10/ostream:681:5: note: ‘std::endl’ declared here 681 | endl(basic_ostream<_CharT, _Traits>& __os) | ^~~~ |
PHPからコンパイルしたとき、このエラーメッセージがそのまま出力されればOKです。本当は多バイト文字を含む例の方がよかったんですが、とりあえず今回はこれでいきます。
これを踏まえて、PHPで次のようなコードを書いてみました。
0 1 2 3 4 |
<?php $command = "g++ hello.cc"; passthru($command); |
これを実行すると、次のようなメッセージが出力されました。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
hello.cc: In function ‘int main()’: hello.cc:5:3: error: ‘cout’ was not declared in this scope; did you mean ‘std::cout’? 5 | cout << "hello" << endl; | ^~~~ | std::cout In file included from hello.cc:1: /usr/include/c++/10/iostream:61:18: note: ‘std::cout’ declared here 61 | extern ostream cout; /// Linked to standard output | ^~~~ hello.cc:5:22: error: ‘endl’ was not declared in this scope; did you mean ‘std::endl’? 5 | cout << "hello" << endl; | ^~~~ | std::endl In file included from /usr/include/c++/10/iostream:39, from hello.cc:1: /usr/include/c++/10/ostream:681:5: note: ‘std::endl’ declared here 681 | endl(basic_ostream<_CharT, _Traits>& __os) | ^~~~ |
エラーメッセージは漏れなく出力されているようです。次回からは、これを基礎として肉付けを行っていきたいと思います。