src/FFI/libclang/Compiler.php
<?php
declare(strict_types=1);
namespace Klitsche\Clang\FFI\libclang;
use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Script\Event;
use Symfony\Component\Process\Process;
class Compiler
{
protected Composer $composer;
protected IOInterface $io;
protected array $config = [
'Linux' => [
'clangBin' => null,
'clangArgs' => [
'-I/usr/lib/llvm-13/include/',
'-I/usr/lib/llvm-12/include/',
'-I/usr/lib/llvm-11/include/',
'-I/usr/lib/llvm-10/include/',
'-I/usr/lib/llvm-9/include/',
'-L/usr/lib/llvm-13/lib/',
'-L/usr/lib/llvm-12/lib/',
'-L/usr/lib/llvm-11/lib/',
'-L/usr/lib/llvm-10/lib/',
'-L/usr/lib/llvm-9/lib/',
'-lclang',
'-v',
'-shared',
'-fPIC',
],
'cfile' => __DIR__ . '/wrapper.c',
'lib' => __DIR__ . '/libclangwrapper.so',
],
'Darwin' => [
'clangBin' => null,
'clangArgs' => [
'-I/usr/local/opt/llvm@13/include/',
'-I/usr/local/opt/llvm@12/include/',
'-I/usr/local/opt/llvm@11/include/',
'-I/usr/local/opt/llvm@10/include/',
'-I/usr/local/opt/llvm@9/include/',
'-I/usr/local/opt/llvm/include/',
'-I/usr/local/include',
'-L/usr/local/opt/llvm@13/lib/',
'-L/usr/local/opt/llvm@12/lib/',
'-L/usr/local/opt/llvm@11/lib/',
'-L/usr/local/opt/llvm@10/lib/',
'-L/usr/local/opt/llvm@9/lib/',
'-L/usr/local/opt/llvm/lib/',
'-lclang',
'-v',
'-shared',
'-fPIC',
],
'cfile' => __DIR__ . '/wrapper.c',
'lib' => __DIR__ . '/libclangwrapper.dylib',
],
'Windows' => [
'clangBin' => null,
'clangArgs' => [
'-I/usr/local/opt/llvm/include/',
'-L/usr/local/opt/llvm/lib/',
'-lclang',
'-v',
'-shared',
'-fPIC',
],
'cfile' => __DIR__ . '/wrapper.c',
'lib' => __DIR__ . '/libclangwrapper.dll',
],
];
protected function __construct(Composer $composer, IOInterface $io)
{
$this->composer = $composer;
$this->io = $io;
$extras = $this->composer->getConfig()->get('extras');
$this->config = array_merge($this->config, $extras['clang-ffi'] ?? []);
}
public static function run(Event $event): int
{
$compiler = new static($event->getComposer(), $event->getIO());
return $compiler->internalRun();
}
protected function internalRun(): int
{
$this->config[PHP_OS_FAMILY]['clangBin'] = $this->detectClang();
if (empty($this->config[PHP_OS_FAMILY]['clangBin'])) {
$this->io->writeError('<error>Cannot detect clang binary to compile libclangwrapper</error>');
$this->io->writeError(' clang >= 9 is required');
$this->io->writeError(' Is llvm with clang installed on your system?');
$this->io->writeError(' You may configure extras.clang-ffi.{PHP_OS_FAMILY}.clangBin in composer.json');
return 1;
}
if (empty($this->config[PHP_OS_FAMILY]['cfile'])) {
$this->io->writeError('<error>extras.clang-ffi.{PHP_OS_FAMILY}.cfile not set</error>');
return 1;
}
if (empty($this->config[PHP_OS_FAMILY]['lib'])) {
$this->io->writeError('<error>extras.clang-ffi.{PHP_OS_FAMILY}.lib not set</error>');
return 1;
}
$process = new Process(
array_merge(
[
$this->config[PHP_OS_FAMILY]['clangBin'],
],
$this->config[PHP_OS_FAMILY]['clangArgs'] ?? [],
[
$this->config[PHP_OS_FAMILY]['cfile'],
],
[
sprintf('-o%s', $this->config[PHP_OS_FAMILY]['lib']),
],
),
__DIR__
);
$this->io->writeError(sprintf('<info>Compile %s</info>', $this->config[PHP_OS_FAMILY]['lib']));
$this->io->writeError($process->getCommandLine(), true, IOInterface::VERBOSE);
$process->run(
function ($type, $buffer): void {
if ($type === Process::ERR) {
$this->io->writeError(sprintf('%s', $buffer), true, IOInterface::DEBUG);
} else {
$this->io->write(sprintf('%s', $buffer), true, IOInterface::DEBUG);
}
}
);
if (file_exists($this->config[PHP_OS_FAMILY]['lib'])) {
$this->io->writeError(sprintf('Successfully compiled %s', $this->config[PHP_OS_FAMILY]['lib']));
return $process->getExitCode();
}
$this->io->writeError(sprintf('<error>Failed to compile %s</error>', $this->config[PHP_OS_FAMILY]['lib']));
return $process->getExitCode();
}
protected function detectClang(): ?string
{
$clangBins = [
'clang-13',
'clang-12',
'clang-11',
'clang-10',
'clang-9',
'clang',
];
$clangBinConfig = $this->config[PHP_OS_FAMILY]['clangBin'] ?? null;
if ($clangBinConfig !== null) {
$clangBins = array_unshift($clangBins, $clangBinConfig);
}
foreach (
$clangBins as $clangBin
) {
$clangBinFound = $this->findBinary($clangBin);
if ($clangBinFound === null) {
continue;
}
$this->io->writeError(sprintf('<info>Found clang binary: %s</info>', $clangBinFound), true, IOInterface::VERBOSE);
$process = new Process([$clangBinFound, '--version']);
$process->run();
if ($process->getExitCode() === 0) {
$this->io->writeError(sprintf('%s', $process->getOutput()), true, IOInterface::VERY_VERBOSE);
return $clangBinFound;
}
}
return null;
}
protected function findBinary(string $name): ?string
{
$findBinaryCommands = ['command -v', 'which', 'type -p'];
foreach ($findBinaryCommands as $findBinaryCommand) {
$process = new Process(
[
$findBinaryCommand,
$name,
]
);
$process->run();
if ($process->getExitCode() === 0) {
break;
}
}
if ($process->getExitCode() !== 0) {
return null;
}
return trim(str_replace($name . ' is', '', $process->getOutput()));
}
}