/* Decoded by unphp.net */ * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ declare(strict_types=1); namespace Zephir; use DirectoryIterator; use Psr\Log\LoggerAwareTrait; use Psr\Log\NullLogger; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use ReflectionClass; use ReflectionException; use Zephir\Backend\Backend; use Zephir\Backend\FcallManagerInterface; use Zephir\Backend\StringsManager; use Zephir\Class\Definition\Definition; use Zephir\Code\ArgInfoDefinition; use Zephir\Code\Builder\Struct; use Zephir\Code\Printer; use Zephir\Compiler\CompilerFileFactory; use Zephir\Compiler\FileInterface; use Zephir\Exception\CompilerException; use Zephir\Exception\IllegalStateException; use Zephir\Exception\InvalidArgumentException; use Zephir\Exception\NotImplementedException; use Zephir\Exception\ParseException; use Zephir\Exception\RuntimeException; use Zephir\FileSystem\FileSystemInterface; use Zephir\FileSystem\HardDisk; use Zephir\Parser\Manager; use function array_filter; use function array_map; use function array_merge; use function array_unique; use function asort; use function basename; use function call_user_func; use function chmod; use function class_exists; use function count; use function defined; use function dirname; use function exec; use function explode; use function extension_loaded; use function file; use function file_exists; use function file_get_contents; use function file_put_contents; use function filemtime; use function fwrite; use function getcwd; use function getenv; use function htmlentities; use function implode; use function in_array; use function interface_exists; use function is_array; use function is_dir; use function is_readable; use function is_string; use function krsort; use function md5; use function md5_file; use function mkdir; use function ob_get_clean; use function ob_start; use function phpinfo; use function preg_match; use function preg_replace; use function realpath; use function sprintf; use function str_replace; use function strcasecmp; use function strip_tags; use function strlen; use function strpos; use function strtolower; use function strtoupper; use function substr; use function trim; use function ucfirst; use function unlink; use function version_compare; use const DIRECTORY_SEPARATOR; use const INFO_GENERAL; use const PHP_EOL; use const PHP_INT_SIZE; use const ZEND_THREAD_SAFE; use const SORT_STRING; use const STDERR; final class Compiler { use LoggerAwareTrait; /** * @var FunctionDefinition[] */ public array $functionDefinitions = []; private array $anonymousFiles = []; private array $compiledFiles = []; private array $constants = []; /** * @var Definition[] */ private array $definitions = []; private array $externalDependencies = []; private array $extraFiles = []; private FcallManagerInterface $fcallManager; /** * @var CompilerFile[] */ private array $files = []; private array $globals = []; /** * @var Definition[] */ private static array $internalDefinitions = []; /** * Additional initializer code. * Used for static property initialization. */ private array $internalInitializers = []; private static bool $loadedPrototypes = false; private ?string $optimizersPath; private ?string $prototypesPath; private StringsManager $stringManager; private ?string $templatesPath; public function __construct( private Config $config, public Backend $backend, private Manager $parserManager, private FileSystemInterface $filesystem, private CompilerFileFactory $compilerFileFactory, ) { $this->logger = new NullLogger(); $this->stringManager = new StringsManager(); $this->fcallManager = $this->backend->getFcallManager(); try { $this->assertRequiredExtensionsIsPresent(); } catch (RuntimeException $e) { fwrite(STDERR, trim($e->getMessage()) . PHP_EOL); exit(1); } } /** * Inserts an anonymous class definition in the compiler. */ public function addClassDefinition(CompilerFileAnonymous $file, Definition $classDefinition): void { $this->definitions[$classDefinition->getCompleteName()] = $classDefinition; $this->anonymousFiles[$classDefinition->getCompleteName()] = $file; } /** * Adds an external dependency to the compiler. */ public function addExternalDependency(string $namespace, string $location): void { $this->externalDependencies[$namespace] = $location; } /** * Adds a function to the function definitions. */ public function addFunction(FunctionDefinition $func, array $statement = []): void { $funcName = strtolower($func->getInternalName()); if (isset($this->functionDefinitions[$funcName])) { throw new CompilerException( "Function '" . $func->getCompleteName() . "' was defined more than one time", $statement ); } $this->functionDefinitions[$funcName] = $func; } /** * Generate a HTML API. * * @throws ConfigException * @throws Exception * @throws ReflectionException */ public function api(array $options = [], bool $fromGenerate = false): void { if (!$fromGenerate) { $this->generate(); } $templatesPath = $this->templatesPath ?: dirname(__DIR__) . '/templates'; $documentator = new Documentation($this->files, $this->config, $templatesPath, $options); $documentator->setLogger($this->logger); $this->logger->info('Generating API into ' . $documentator->getOutputDirectory()); $documentator->build(); } public function calculateDependencies(array $files, $_dependency = null): void { /** * Classes are ordered according to a dependency ranking * Classes with higher rank, need to be initialized first * We first build a dependency tree and then set the rank accordingly */ if (null === $_dependency) { $dependencyTree = []; foreach ($files as $file) { if (!$file->isExternal()) { $classDefinition = $file->getClassDefinition(); $dependencyTree[$classDefinition->getCompleteName()] = $classDefinition->getDependencies(); } } // Make sure the dependencies are loaded first (recursively) foreach ($dependencyTree as $dependencies) { foreach ($dependencies as $dependency) { $dependency->increaseDependencyRank(0); $this->calculateDependencies($dependencyTree, $dependency); } } return; } $dependencyTree = $files; if (isset($dependencyTree[$_dependency->getCompleteName()])) { foreach ($dependencyTree[$_dependency->getCompleteName()] as $dependency) { $dependency->increaseDependencyRank(0); $this->calculateDependencies($dependencyTree, $dependency); } } } /** * Check if the project must be phpized again. * * @return bool */ public function checkIfPhpized(): bool { return !file_exists('ext/Makefile'); } /** * Compiles the extension without installing it. * * @param bool $development * @param int|null $jobs * * @throws Exception */ public function compile(bool $development = false, int $jobs = null): void { $jobs = $jobs ?: 2; /** * Get global namespace. */ $namespace = str_replace('\\', '_', $this->checkDirectory()); $extensionName = $this->config->get('extension-name'); if (empty($extensionName) || !is_string($extensionName)) { $extensionName = $namespace; } $currentDir = getcwd(); if (file_exists("$currentDir/compile.log")) { unlink("$currentDir/compile.log"); } if (file_exists("$currentDir/compile-errors.log")) { unlink("$currentDir/compile-errors.log"); } if (file_exists("$currentDir/ext/modules/{$namespace}.so")) { unlink("$currentDir/ext/modules/{$namespace}.so"); } if (Os::isWindows()) { // TODO(klay): Make this better. Looks like it is non standard Env. Var exec('cd ext && %PHP_DEVPACK%\\phpize --clean', $output, $exit); $releaseFolder = $this->getWindowsReleaseDir(); if (file_exists($releaseFolder)) { exec('rd /s /q ' . $releaseFolder, $output, $exit); } $this->logger->info('Preparing for PHP compilation...'); // TODO(klay): Make this better. Looks like it is non standard Env. Var exec('cd ext && %PHP_DEVPACK%\\phpize', $output, $exit); /** * fix until patch hits all supported PHP builds. * * @see https://github.com/php/php-src/commit/9a3af83ee2aecff25fd4922ef67c1fb4d2af6201 */ $fixMarker = '/* zephir_phpize_fix */'; $configureFile = file_get_contents('ext\\configure.js'); $configureFix = ["var PHP_ANALYZER = 'disabled';", "var PHP_PGO = 'no';", "var PHP_PGI = 'no';"]; $hasChanged = false; if (!str_contains($configureFile, $fixMarker)) { $configureFile = $fixMarker . PHP_EOL . implode(PHP_EOL, $configureFix) . PHP_EOL . $configureFile; $hasChanged = true; } /* fix php's broken phpize patching ... */ $marker = 'var build_dir = (dirname ? dirname : "").replace(new RegExp("^..\\\\\\\\"), "");'; $pos = strpos($configureFile, $marker); if (false !== $pos) { $spMarker = 'if (MODE_PHPIZE) {'; $sp = strpos($configureFile, $spMarker, $pos - 200); if (false === $sp) { throw new CompilerException('outofdate... phpize seems broken again'); } $configureFile = substr($configureFile, 0, $sp) . 'if (false) {' . substr($configureFile, $sp + strlen($spMarker)); $hasChanged = true; } if ($hasChanged) { file_put_contents('ext\\configure.js', $configureFile); } $this->logger->info('Preparing configuration file...'); exec('cd ext && configure --enable-' . $extensionName); } else { exec('cd ext && make clean && phpize --clean', $output, $exit); $this->logger->info('Preparing for PHP compilation...'); exec('cd ext && phpize', $output, $exit); $this->logger->info('Preparing configuration file...'); exec( 'cd ext && export CC="gcc" && export CFLAGS="' . $this->getGccFlags($development) . '" && ./configure --enable-' . $extensionName ); } $currentDir = getcwd(); $this->logger->info('Compiling...'); if (Os::isWindows()) { exec( 'cd ext && nmake 2>' . $currentDir . '\compile-errors.log 1>' . $currentDir . '\compile.log', $output, $exit ); } else { $this->preCompileHeaders(); exec( 'cd ext && (make -s -j' . $jobs . ' 2>' . $currentDir . '/compile-errors.log 1>' . $currentDir . '/compile.log)', $output, $exit ); } } /** * Create config.m4 and config.w32 for the extension. * * TODO: move this to backend? * * @throws Exception */ public function createConfigFiles(string $project): bool { $contentM4 = $this->backend->getTemplateFileContents('config.m4'); if (empty($contentM4)) { throw new Exception("Template config.m4 doesn't exist"); } $contentW32 = $this->backend->getTemplateFileContents('config.w32'); if (empty($contentW32)) { throw new Exception("Template config.w32 doesn't exist"); } $safeProject = 'zend' === $project ? 'zend_' : $project; $compiledFiles = array_map(fn($file) => str_replace('.c', '.zep.c', $file), $this->compiledFiles); /** * If export-classes is enabled all headers are copied to include/php/ext. */ $exportClasses = $this->config->get('export-classes', 'extra'); if ($exportClasses) { $compiledHeaders = array_map(fn($file) => str_replace('.c', '.zep.h', $file), $this->compiledFiles); } else { $compiledHeaders = ['php_' . strtoupper($project) . '.h']; } /** * Check extra-libs, extra-cflags, package-dependencies exists */ $extraLibs = (string)$this->config->get('extra-libs'); $extraCflags = (string)$this->config->get('extra-cflags'); $contentM4 = $this->generatePackageDependenciesM4($contentM4); $buildDirs = []; foreach ($compiledFiles as $file) { $dir = dirname($file); if (!in_array($dir, $buildDirs)) { $buildDirs[] = $dir; } } asort($buildDirs); /** * Generate config.m4. */ $toReplace = [ '%PROJECT_LOWER_SAFE%' => strtolower($safeProject), '%PROJECT_LOWER%' => strtolower($project), '%PROJECT_UPPER%' => strtoupper($project), '%PROJECT_CAMELIZE%' => ucfirst($project), '%FILES_COMPILED%' => implode("\n\t", $this->toUnixPaths($compiledFiles)), '%HEADERS_COMPILED%' => implode(' ', $this->toUnixPaths($compiledHeaders)), '%EXTRA_FILES_COMPILED%' => implode("\n\t", $this->toUnixPaths($this->extraFiles)), '%PROJECT_EXTRA_LIBS%' => $extraLibs, '%PROJECT_EXTRA_CFLAGS%' => $extraCflags, '%PROJECT_BUILD_DIRS%' => implode(' ', $buildDirs), ]; foreach ($toReplace as $mark => $replace) { $contentM4 = str_replace($mark, $replace, $contentM4); } HardDisk::persistByHash($contentM4, 'ext/config.m4'); /** * Generate config.w32. */ $toReplace = [ '%PROJECT_LOWER_SAFE%' => strtolower($safeProject), '%PROJECT_LOWER%' => strtolower($project), '%PROJECT_UPPER%' => strtoupper($project), '%FILES_COMPILED%' => implode( "\r\n\t", $this->processAddSources($compiledFiles, strtolower($project)) ), '%EXTRA_FILES_COMPILED%' => implode( "\r\n\t", $this->processAddSources($this->extraFiles, strtolower($project)) ), ]; foreach ($toReplace as $mark => $replace) { $contentW32 = str_replace($mark, $replace, $contentW32); } $needConfigure = HardDisk::persistByHash($contentW32, 'ext/config.w32'); /** * php_ext.h. */ $content = $this->backend->getTemplateFileContents('php_ext.h'); if (empty($content)) { throw new Exception("Template php_ext.h doesn't exist"); } $toReplace = [ '%PROJECT_LOWER_SAFE%' => strtolower($safeProject), ]; foreach ($toReplace as $mark => $replace) { $content = str_replace($mark, $replace, $content); } HardDisk::persistByHash($content, 'ext/php_ext.h'); /** * ext.h. */ $content = $this->backend->getTemplateFileContents('ext.h'); if (empty($content)) { throw new Exception("Template ext.h doesn't exist"); } $toReplace = [ '%PROJECT_LOWER_SAFE%' => strtolower($safeProject), ]; foreach ($toReplace as $mark => $replace) { $content = str_replace($mark, $replace, $content); } HardDisk::persistByHash($content, 'ext/ext.h'); /** * ext_config.h. */ $content = $this->backend->getTemplateFileContents('ext_config.h'); if (empty($content)) { throw new Exception("Template ext_config.h doesn't exist"); } $toReplace = [ '%PROJECT_LOWER%' => strtolower($project), ]; foreach ($toReplace as $mark => $replace) { $content = str_replace($mark, $replace, $content); } HardDisk::persistByHash($content, 'ext/ext_config.h'); /** * ext_clean. */ $content = $this->backend->getTemplateFileContents('clean'); if (empty($content)) { throw new Exception("Clean file doesn't exist"); } if (HardDisk::persistByHash($content, 'ext/clean')) { chmod('ext/clean', 0755); } /** * ext_install. */ $content = $this->backend->getTemplateFileContents('install'); if (empty($content)) { throw new Exception("Install file doesn't exist"); } $toReplace = [ '%PROJECT_LOWER%' => strtolower($project), ]; foreach ($toReplace as $mark => $replace) { $content = str_replace($mark, $replace, $content); } if (HardDisk::persistByHash($content, 'ext/install')) { chmod('ext/install', 0755); } return (bool)$needConfigure; } /** * Create project.c and project.h according to the current extension. * * TODO: Move the part of the logic which depends on templates (backend-specific) to backend? * * @throws Exception */ public function createProjectFiles(string $project): bool { $needConfigure = $this->checkKernelFiles(); /** * project.c. */ $content = $this->backend->getTemplateFileContents('project.c'); if (empty($content)) { throw new Exception("Template project.c doesn't exist"); } $includes = ''; $reqInitializers = ''; $reqDestructors = ''; $prqDestructors = ''; $modInitializers = ''; $modDestructors = ''; $glbInitializers = ''; $glbDestructors = ''; $files = array_merge($this->files, $this->anonymousFiles); /** * Round 1. Calculate the dependency rank */ $this->calculateDependencies($files); $classEntries = []; $classInits = []; $interfaceEntries = []; $interfaceInits = []; /** * Round 2. Generate the ZEPHIR_INIT calls according to the dependency rank */ /** @var FileInterface $file */ foreach ($files as $file) { if ($file->isExternal()) { continue; } $classDefinition = $file->getClassDefinition(); if ($classDefinition === null) { continue; } $dependencyRank = $classDefinition->getDependencyRank(); if ('class' === $classDefinition->getType()) { $classEntries[$dependencyRank][] = 'zend_class_entry *' . $classDefinition->getClassEntry() . ';'; $classInits[$dependencyRank][] = 'ZEPHIR_INIT(' . $classDefinition->getCNamespace() . '_' . $classDefinition->getName() . ');'; } else { $interfaceEntries[$dependencyRank][] = 'zend_class_entry *' . $classDefinition->getClassEntry() . ';'; $interfaceInits[$dependencyRank][] = 'ZEPHIR_INIT(' . $classDefinition->getCNamespace() . '_' . $classDefinition->getName() . ');'; } } krsort($classInits); krsort($classEntries); krsort($interfaceInits); krsort($interfaceEntries); $completeInterfaceInits = []; foreach ($interfaceInits as $rankInterfaceInits) { asort($rankInterfaceInits, SORT_STRING); $completeInterfaceInits = array_merge($completeInterfaceInits, $rankInterfaceInits); } $completeInterfaceEntries = []; foreach ($interfaceEntries as $rankInterfaceEntries) { asort($rankInterfaceEntries, SORT_STRING); $completeInterfaceEntries = array_merge($completeInterfaceEntries, $rankInterfaceEntries); } $completeClassInits = []; foreach ($classInits as $rankClassInits) { asort($rankClassInits, SORT_STRING); $completeClassInits = array_merge($completeClassInits, $rankClassInits); } $completeClassEntries = []; foreach ($classEntries as $rankClassEntries) { asort($rankClassEntries, SORT_STRING); $completeClassEntries = array_merge($completeClassEntries, $rankClassEntries); } /** * Round 3. Process extension globals */ [$globalCode, $globalStruct, $globalsDefault, $initEntries] = $this->processExtensionGlobals($project); if ('zend' == $project) { $safeProject = 'zend_'; } else { $safeProject = $project; } /** * Round 4. Process extension info. */ $phpInfo = $this->processExtensionInfo(); /** * Round 5. Generate Function entries (FE) */ [$feHeader, $feEntries] = $this->generateFunctionInformation(); /** * Check if there are module/request/global destructors. */ $destructors = $this->config->get('destructors'); if (is_array($destructors)) { $invokeRequestDestructors = $this->processCodeInjection($destructors, 'request'); $includes .= PHP_EOL . $invokeRequestDestructors[0]; $reqDestructors = $invokeRequestDestructors[1]; $invokePostRequestDestructors = $this->processCodeInjection($destructors, 'post-request'); $includes .= PHP_EOL . $invokePostRequestDestructors[0]; $prqDestructors = $invokePostRequestDestructors[1]; $invokeModuleDestructors = $this->processCodeInjection($destructors, 'module'); $includes .= PHP_EOL . $invokeModuleDestructors[0]; $modDestructors = $invokeModuleDestructors[1]; $invokeGlobalsDestructors = $this->processCodeInjection($destructors, 'globals'); $includes .= PHP_EOL . $invokeGlobalsDestructors[0]; $glbDestructors = $invokeGlobalsDestructors[1]; } /** * Check if there are module/request/global initializers. */ $initializers = $this->config->get('initializers'); if (is_array($initializers)) { $invokeRequestInitializers = $this->processCodeInjection($initializers, 'request'); $includes .= PHP_EOL . $invokeRequestInitializers[0]; $reqInitializers = $invokeRequestInitializers[1]; $invokeModuleInitializers = $this->processCodeInjection($initializers, 'module'); $includes .= PHP_EOL . $invokeModuleInitializers[0]; $modInitializers = $invokeModuleInitializers[1]; $invokeGlobalsInitializers = $this->processCodeInjection($initializers, 'globals'); $includes .= PHP_EOL . $invokeGlobalsInitializers[0]; $glbInitializers = $invokeGlobalsInitializers[1]; } /** * Append extra details. */ $extraClasses = $this->config->get('extra-classes'); if (is_array($extraClasses)) { foreach ($extraClasses as $value) { if (isset($value['init'])) { $completeClassInits[] = 'ZEPHIR_INIT(' . $value['init'] . ')'; } if (isset($value['entry'])) { $completeClassEntries[] = 'zend_class_entry *' . $value['entry'] . ';'; } } } $modRequires = array_map( fn($mod) => sprintf('ZEND_MOD_REQUIRED("%s")', strtolower($mod)), $this->config->get('extensions', 'requires') ?: [] ); $toReplace = [ '%PROJECT_LOWER_SAFE%' => strtolower($safeProject), '%PROJECT_LOWER%' => strtolower($project), '%PROJECT_UPPER%' => strtoupper($project), '%PROJECT_CAMELIZE%' => ucfirst($project), '%CLASS_ENTRIES%' => implode( PHP_EOL, array_merge($completeInterfaceEntries, $completeClassEntries) ), '%CLASS_INITS%' => implode( PHP_EOL . "\t", array_merge($completeInterfaceInits, $completeClassInits) ), '%INIT_GLOBALS%' => implode( PHP_EOL . "\t", array_merge((array)$globalsDefault[0], [$glbInitializers]) ), '%INIT_MODULE_GLOBALS%' => $globalsDefault[1], '%DESTROY_GLOBALS%' => $glbDestructors, '%EXTENSION_INFO%' => $phpInfo, '%EXTRA_INCLUDES%' => implode( PHP_EOL, array_unique(explode(PHP_EOL, $includes)) ), '%MOD_INITIALIZERS%' => $modInitializers, '%MOD_DESTRUCTORS%' => $modDestructors, '%REQ_INITIALIZERS%' => implode( PHP_EOL . "\t", array_merge($this->internalInitializers, [$reqInitializers]) ), '%REQ_DESTRUCTORS%' => $reqDestructors, '%POSTREQ_DESTRUCTORS%' => empty($prqDestructors) ? '' : implode( PHP_EOL, [ '#define ZEPHIR_POST_REQUEST 1', 'static PHP_PRSHUTDOWN_FUNCTION(' . strtolower($project) . ')', '{', "\t" . implode( PHP_EOL . "\t", explode(PHP_EOL, $prqDestructors) ), '}', ] ), '%FE_HEADER%' => $feHeader, '%FE_ENTRIES%' => $feEntries, '%PROJECT_INI_ENTRIES%' => implode(PHP_EOL . "\t", $initEntries), '%PROJECT_DEPENDENCIES%' => implode(PHP_EOL . "\t", $modRequires), ]; foreach ($toReplace as $mark => $replace) { $content = str_replace($mark, $replace, $content); } /** * Round 5. Generate and place the entry point of the project */ HardDisk::persistByHash($content, 'ext/' . $safeProject . '.c'); unset($content); /** * Round 6. Generate the project main header. */ $content = $this->backend->getTemplateFileContents('project.h'); if (empty($content)) { throw new Exception("Template project.h doesn't exists"); } $includeHeaders = []; foreach ($this->compiledFiles as $file) { if ($file) { $fileH = str_replace('.c', '.zep.h', $file); $include = '#include "' . $fileH . '"'; $includeHeaders[] = $include; } } /** * Append extra headers. */ $extraClasses = $this->config->get('extra-classes'); if (is_array($extraClasses)) { foreach ($extraClasses as $value) { if (isset($value['header'])) { $include = '#include "' . $value['header'] . '"'; $includeHeaders[] = $include; } } } $toReplace = [ '%INCLUDE_HEADERS%' => implode(PHP_EOL, $includeHeaders), ]; foreach ($toReplace as $mark => $replace) { $content = str_replace($mark, $replace, $content); } HardDisk::persistByHash($content, 'ext/' . $safeProject . '.h'); unset($content); /** * Round 7. Create php_project.h. */ $content = $this->backend->getTemplateFileContents('php_project.h'); if (empty($content)) { throw new Exception("Template php_project.h doesn't exist"); } $toReplace = [ '%PROJECT_LOWER_SAFE%' => strtolower($safeProject), '%PROJECT_LOWER%' => strtolower($project), '%PROJECT_UPPER%' => strtoupper($project), '%PROJECT_EXTNAME%' => strtolower($project), '%PROJECT_NAME%' => mb_convert_encoding($this->config->get('name'), 'ISO-8859-1', 'UTF-8'), '%PROJECT_AUTHOR%' => mb_convert_encoding($this->config->get('author'), 'ISO-8859-1', 'UTF-8'), '%PROJECT_VERSION%' => mb_convert_encoding($this->config->get('version'), 'ISO-8859-1', 'UTF-8'), '%PROJECT_DESCRIPTION%' => mb_convert_encoding( $this->config->get('description'), 'ISO-8859-1', 'UTF-8' ), '%PROJECT_ZEPVERSION%' => Zephir::VERSION, '%EXTENSION_GLOBALS%' => $globalCode, '%EXTENSION_STRUCT_GLOBALS%' => $globalStruct, ]; foreach ($toReplace as $mark => $replace) { $content = str_replace($mark, $replace, $content); } HardDisk::persistByHash($content, 'ext/php_' . $safeProject . '.h'); unset($content); return $needConfigure; } /** * Generates the C sources from Zephir without compiling them. * * @throws Exception * @throws ReflectionException */ public function generate(bool $fromGenerate = false): bool { /** * Get global namespace. */ $namespace = $this->checkDirectory(); /** * Check whether there are external dependencies. */ $externalDependencies = $this->config->get('external-dependencies'); if (is_array($externalDependencies)) { foreach ($externalDependencies as $dependencyNs => $location) { if (!file_exists($location)) { throw new CompilerException( sprintf( 'Location of dependency "%s" does not exist. Check the config.json for more information.', $dependencyNs ) ); } $this->addExternalDependency($dependencyNs, $location); } } /** * Round 1. pre-compile all files in memory */ $this->recursivePreCompile(str_replace('\\', DIRECTORY_SEPARATOR, $namespace)); if (!count($this->files)) { throw new Exception( "Zephir files to compile couldn't be found. Did you add a first class to the extension?" ); } /** * Round 2. Check 'extends' and 'implements' dependencies */ foreach ($this->files as $compileFile) { $compileFile->checkDependencies($this); } /** * Sort the files by dependency ranking. */ $files = []; $rankedFiles = []; $this->calculateDependencies($this->files); foreach ($this->files as $rankFile) { $rank = $rankFile->getClassDefinition()->getDependencyRank(); $rankedFiles[$rank][] = $rankFile; } krsort($rankedFiles); foreach ($rankedFiles as $rankFiles) { $files = array_merge($files, $rankFiles); } $this->files = $files; /** * Convert C-constants into PHP constants. */ $constantsSources = $this->config->get('constants-sources'); if (is_array($constantsSources)) { $this->loadConstantsSources($constantsSources); } /** * Set extension globals. */ $globals = $this->config->get('globals'); if (is_array($globals)) { $this->setExtensionGlobals($globals); } /** * Load function optimizers */ if (false === self::$loadedPrototypes) { $optimizersPath = $this->resolveOptimizersPath(); FunctionCall::addOptimizerDir("{$optimizersPath}/FunctionCall"); $customOptimizersPaths = $this->config->get('optimizer-dirs'); if (is_array($customOptimizersPaths)) { foreach ($customOptimizersPaths as $directory) { FunctionCall::addOptimizerDir(realpath($directory)); } } /** * Load additional extension prototypes. */ $prototypesPath = $this->resolvePrototypesPath(); foreach (new DirectoryIterator($prototypesPath) as $file) { if ($file->isDir() || $file->isDot()) { continue; } // Do not use $file->getRealPath() because it does not work inside phar $realPath = "{$file->getPath()}/{$file->getFilename()}"; $extension = $file->getBasename(".{$file->getExtension()}"); if (!extension_loaded($extension)) { require_once $realPath; } } /** * Load customer additional extension prototypes. */ $prototypeDirs = $this->config->get('prototype-dir'); if (is_array($prototypeDirs)) { foreach ($prototypeDirs as $prototype => $prototypeDir) { /** * Check if the extension is installed */ if (!extension_loaded($prototype)) { $prototypeRealpath = realpath($prototypeDir); if ($prototypeRealpath) { foreach (new RecursiveDirectoryIterator($prototypeRealpath) as $file) { if ($file->isDir()) { continue; } require_once $file->getRealPath(); } } } } } self::$loadedPrototypes = true; } /** * Round 3. Compile all files to C sources. */ $files = []; $hash = ''; foreach ($this->files as $compileFile) { /** * Only compile classes in the local extension, ignore external classes */ if (!$compileFile->isExternal()) { $compileFile->compile($this, $this->stringManager); $compiledFile = $compileFile->getCompiledFile(); $methods = []; $classDefinition = $compileFile->getClassDefinition(); foreach ($classDefinition->getMethods() as $method) { $methods[] = '[' . $method->getName() . ':' . implode('-', $method->getVisibility()) . ']'; if ($method->isInitializer() && $method->isStatic()) { $this->internalInitializers[] = "\t" . $method->getName() . '();'; } } $files[] = $compiledFile; $hash .= '|' . $compiledFile . ':' . $classDefinition->getClassEntry() . '[' . implode('|', $methods) . ']'; } } /** * Round 3.2. Compile anonymous classes */ foreach ($this->anonymousFiles as $compileFile) { $compileFile->compile($this, $this->stringManager); $compiledFile = $compileFile->getCompiledFile(); $methods = []; $classDefinition = $compileFile->getClassDefinition(); foreach ($classDefinition->getMethods() as $method) { $methods[] = '[' . $method->getName() . ':' . implode('-', $method->getVisibility()) . ']'; } $files[] = $compiledFile; $hash .= '|' . $compiledFile . ':' . $classDefinition->getClassEntry() . '[' . implode('|', $methods) . ']'; } $hash = md5($hash); $this->compiledFiles = $files; /** * Round 3.3. Load extra C-sources. */ $extraSources = $this->config->get('extra-sources'); if (is_array($extraSources)) { $this->extraFiles = $extraSources; } else { $this->extraFiles = []; } /** * Round 3.4. Load extra classes sources. */ $extraClasses = $this->config->get('extra-classes'); if (is_array($extraClasses)) { foreach ($extraClasses as $value) { if (isset($value['source'])) { $this->extraFiles[] = $value['source']; } } } /** * Round 4. Create config.m4 and config.w32 files / Create project.c and project.h files. */ $namespace = str_replace('\\', '_', $namespace); $extensionName = $this->config->get('extension-name'); if (empty($extensionName) || !is_string($extensionName)) { $extensionName = $namespace; } $needConfigure = $this->createConfigFiles($extensionName); $needConfigure |= $this->createProjectFiles($extensionName); $needConfigure |= $this->checkIfPhpized(); // Bitwise returns `int` instead of `bool`. $needConfigure = (bool)$needConfigure; /** * When a new file is added or removed we need to run configure again */ if (!$fromGenerate) { if (false === $this->filesystem->exists('compiled-files-sum')) { $needConfigure = true; $this->filesystem->write('compiled-files-sum', $hash); } else { if ($this->filesystem->read('compiled-files-sum') != $hash) { $needConfigure = true; $this->filesystem->delete('compiled-files-sum'); $this->filesystem->write('compiled-files-sum', $hash); } } } /** * Round 5. Generate concatenation functions */ $this->stringManager->genConcatCode(); $this->fcallManager->genFcallCode(); if ($this->config->get('stubs-run-after-generate', 'stubs')) { $this->stubs($fromGenerate); } return $needConfigure; } public function generateFunctionInformation(): array { $headerPrinter = new Printer(); $entryPrinter = new Printer(); /** * Specifying Argument Information */ foreach ($this->functionDefinitions as $func) { $argInfo = new ArgInfoDefinition( $func->getArgInfoName(), $func, $headerPrinter, $func->getCallGathererPass()->getCompilationContext() ); $funcName = $func->getInternalName(); $argInfoName = $func->getArgInfoName(); $headerPrinter->output('PHP_FUNCTION(' . $funcName . ');'); $argInfo->setBooleanDefinition('_IS_BOOL'); $argInfo->setRichFormat(true); $argInfo->render(); /** Generate FE's */ $paramData = 'NULL'; $richFormat = $func->isReturnTypesHintDetermined() && $func->areReturnTypesCompatible(); if ($richFormat || $func->hasParameters()) { $paramData = $argInfoName; } if ($func->isGlobal()) { $entryPrinter->output( 'ZEND_NAMED_FE(' . $func->getName() . ', ZEND_FN(' . $funcName . '), ' . $paramData . ')' ); } else { $entryPrinter->output( 'ZEND_NS_NAMED_FE("' . str_replace('\\', '\\\\', $func->getNamespace()) . '", ' . $func->getName() . ', ZEND_FN(' . $funcName . '), ' . $paramData . ')' ); } } $entryPrinter->output('ZEND_FE_END'); return [$headerPrinter->getOutput(), $entryPrinter->getOutput()]; } /** * Generate package-dependencies config for m4. * * TODO: Move the template depending part to backend? */ public function generatePackageDependenciesM4(string $contentM4): string { $packageDependencies = $this->config->get('package-dependencies'); if (!is_array($packageDependencies)) { return str_replace('%PROJECT_PACKAGE_DEPENDENCIES%', '', $contentM4); } $pkgconfigM4 = $this->backend->getTemplateFileContents('pkg-config.m4'); $pkgconfigCheckM4 = $this->backend->getTemplateFileContents('pkg-config-check.m4'); $extraCFlags = ''; foreach ($packageDependencies as $pkg => $version) { $pkgM4Buf = $pkgconfigCheckM4; $operator = '='; $operatorCmd = '--exact-version'; $ar = explode('=', $version); if (1 === count($ar)) { if ('*' === $version) { $version = '0.0.0'; $operator = '>='; $operatorCmd = '--atleast-version'; } } else { switch ($ar[0]) { case '<': $operator = '<='; $operatorCmd = '--max-version'; break; case '>': $operator = '>='; $operatorCmd = '--atleast-version'; break; } $version = trim($ar[1]); } $toReplace = [ '%PACKAGE_LOWER%' => strtolower($pkg), '%PACKAGE_UPPER%' => strtoupper($pkg), '%PACKAGE_REQUESTED_VERSION%' => $operator . ' ' . $version, '%PACKAGE_PKG_CONFIG_COMPARE_VERSION%' => $operatorCmd . '=' . $version, ]; foreach ($toReplace as $mark => $replace) { $pkgM4Buf = str_replace($mark, $replace, $pkgM4Buf); } $pkgconfigM4 .= $pkgM4Buf; $extraCFlags .= '$PHP_' . strtoupper($pkg) . '_INCS '; } $contentM4 = str_replace('%PROJECT_EXTRA_CFLAGS%', '%PROJECT_EXTRA_CFLAGS% ' . $extraCFlags, $contentM4); return str_replace('%PROJECT_PACKAGE_DEPENDENCIES%', $pkgconfigM4, $contentM4); } /** * Returns class the class definition from a given class name. */ public function getClassDefinition(string $className): Definition | bool { foreach ($this->definitions as $key => $value) { if (!strcasecmp($key, $className)) { return $value; } } return false; } /** * Returns a Zephir Constant by its name. */ public function getConstant(string $name): mixed { return $this->constants[$name]; } /** * Returns an extension global by its name. */ public function getExtensionGlobal(string $name): array { return $this->globals[$name]; } /** * Returns GCC flags for current compilation. */ public function getGccFlags(bool $development = false): string { if (Os::isWindows()) { // TODO return ''; } $gccFlags = getenv('CFLAGS'); if (!is_string($gccFlags)) { if (false === $development) { $gccVersion = $this->getGccVersion(); if (version_compare($gccVersion, '4.6.0', '>=')) { $gccFlags = '-O2 -fvisibility=hidden -Wparentheses -flto -DZEPHIR_RELEASE=1'; } else { $gccFlags = '-O2 -fvisibility=hidden -Wparentheses -DZEPHIR_RELEASE=1'; } } else { $gccFlags = '-O0 -g3'; } } return $gccFlags; } /** * Returns class the class definition from a given class name. * * @throws ReflectionException */ public function getInternalClassDefinition(string $className): Definition { if (!isset(self::$internalDefinitions[$className])) { $reflection = new ReflectionClass($className); self::$internalDefinitions[$className] = Definition::buildFromReflection($reflection); } return self::$internalDefinitions[$className]; } /** * Gets the Zephir Parser Manager. * * @deprecated */ public function getParserManager(): Parser\Manager { return $this->parserManager; } /** * Returns the php include directories returned by php-config. */ public function getPhpIncludeDirs(): string { $this->filesystem->system('php-config --includes', 'stdout', 'php-includes'); return trim($this->filesystem->read('php-includes')); } /** * Returns a short user path. */ public static function getShortUserPath(string $path): string { return str_replace('\\', '/', str_replace(getcwd() . DIRECTORY_SEPARATOR, '', $path)); } /** * Compiles and installs the extension. * * @throws Exception * @throws NotImplementedException * @throws CompilerException */ public function install(bool $development = false): void { // Get global namespace $namespace = str_replace('\\', '_', $this->checkDirectory()); $currentDir = getcwd(); if (Os::isWindows()) { throw new NotImplementedException('Installation is not implemented for Windows yet. Aborting.'); } $this->logger->info('Installing...'); $gccFlags = $this->getGccFlags($development); $command = strtr( // TODO: Sort out with sudo 'cd ext && export CC="gcc" && export CFLAGS=":cflags" && ' . 'make 2>> ":stderr" 1>> ":stdout" && ' . 'sudo make install 2>> ":stderr" 1>> ":stdout"', [ ':cflags' => $gccFlags, ':stderr' => "{$currentDir}/compile-errors.log", ':stdout' => "{$currentDir}/compile.log", ] ); array_map(function ($entry): void { if (!empty($entry)) { $this->logger->debug(trim($entry)); } }, explode('&&', $command)); exec($command, $output, $exit); $fileName = $this->config->get('extension-name') ?: $namespace; if (false === file_exists("{$currentDir}/ext/modules/{$fileName}.so")) { throw new CompilerException( 'Internal extension compilation failed. Check compile-errors.log for more information.' ); } } /** * Allows checking if a class is part of PHP. */ public function isBundledClass(string $className): bool { return class_exists($className, false); } /** * Allows checking if an interface is part of PHP. */ public function isBundledInterface(string $className): bool { return interface_exists($className, false); } /** * Allows to check if a class is part of the compiled extension. */ public function isClass(string $className): bool { foreach ($this->definitions as $key => $value) { if (!strcasecmp($key, $className) && 'class' === $value->getType()) { return true; } } /** * Try to autoload the class from an external dependency */ foreach ($this->externalDependencies as $namespace => $location) { if (preg_match('#^' . $namespace . '\\\\#i', $className)) { return $this->loadExternalClass($className, $location); } } return false; } /** * Checks if $name is a Zephir constant. */ public function isConstant(string $name): bool { return isset($this->constants[$name]); } /** * Checks if a specific extension global is defined. */ public function isExtensionGlobal(string $name): bool { return isset($this->globals[$name]); } /** * Allows checking if an interface is part of the compiled extension. * * @throws CompilerException * @throws IllegalStateException * @throws ParseException */ public function isInterface(string $className): bool { foreach ($this->definitions as $key => $value) { if (!strcasecmp($key, $className) && Definition::TYPE_INTERFACE === $value->getType()) { return true; } } /** * Try to autoload the class from an external dependency */ foreach ($this->externalDependencies as $namespace => $location) { if (preg_match('#^' . $namespace . '\\\\#i', $className)) { return $this->loadExternalClass($className, $location); } } return false; } /** * Loads a class definition in an external dependency. * * @throws CompilerException * @throws IllegalStateException * @throws ParseException */ public function loadExternalClass(string $className, string $location): bool { $filePath = $location . DIRECTORY_SEPARATOR . strtolower( str_replace('\\', DIRECTORY_SEPARATOR, $className) ) . '.zep'; /** * Fix the class name. */ $className = implode( '\\', array_map( 'ucfirst', explode('\\', $className) ) ); if (isset($this->files[$className])) { return true; } if (!file_exists($filePath)) { return false; } /** @var CompilerFile|CompilerFileAnonymous $compilerFile */ $compilerFile = $this->compilerFileFactory->create($className, $filePath); $compilerFile->setIsExternal(true); $compilerFile->preCompile($this); $this->files[$className] = $compilerFile; $this->definitions[$className] = $compilerFile->getClassDefinition(); return true; } /** * Pre-compile headers to speed up compilation. */ public function preCompileHeaders(): void { if (Os::isWindows()) { // TODO: Add Windows support return; } $phpIncludes = $this->getPhpIncludeDirs(); /** @var DirectoryIterator $file */ foreach (new DirectoryIterator('ext/kernel') as $file) { if ($file->isDir() || $file->getExtension() !== 'h') { continue; } $command = sprintf( 'cd ext && gcc -c kernel/%s -I. %s -o kernel/%s.gch', $file->getBaseName(), $phpIncludes, $file->getBaseName() ); $path = $file->getRealPath(); if (!file_exists($path . '.gch') || filemtime($path) > filemtime($path . '.gch')) { $this->filesystem->system($command, 'stdout', 'compile-header'); } } } /** * Process extension code injection. */ public function processCodeInjection(array $entries, string $section = 'request'): array { $codes = []; $includes = []; if (isset($entries[$section])) { foreach ($entries[$section] as $entry) { if (!empty($entry['code'])) { $codes[] = $entry['code'] . ';'; } if (!empty($entry['include'])) { $includes[] = '#include "' . $entry['include'] . '"'; } } } return [implode(PHP_EOL, $includes), implode("\n\t", $codes)]; } /** * Process extension globals. * * @throws Exception */ public function processExtensionGlobals(string $namespace): array { $globalCode = ''; $globalStruct = ''; $globalsDefault = [[], []]; $initEntries = []; /** * Generate the extensions globals declaration. */ $globals = $this->config->get('globals'); if (is_array($globals)) { $structures = []; $variables = []; foreach ($globals as $name => $global) { $parts = explode('.', $name); if (isset($parts[1])) { $structures[$parts[0]][$parts[1]] = $global; } else { $variables[$parts[0]] = $global; } } /** * Process compound structures */ foreach ($structures as $structureName => $internalStructure) { if (preg_match('/^[0-9a-zA-Z_]$/', $structureName)) { throw new Exception("Struct name: '" . $structureName . "' contains invalid characters"); } $structBuilder = new Struct('_zephir_struct_' . $structureName, $structureName); foreach ($internalStructure as $field => $global) { if (preg_match('/^[0-9a-zA-Z_]$/', $field)) { throw new Exception("Struct field name: '" . $field . "' contains invalid characters"); } $structBuilder->addProperty($field, $global['type']); $isModuleGlobal = (int)!empty($global['module']); $globalsDefault[$isModuleGlobal][] = $structBuilder->getCDefault($field, $global, $namespace); $initEntries[] = $structBuilder->getInitEntry($field, $global, $namespace); } $globalStruct .= $structBuilder . PHP_EOL; } $globalCode = PHP_EOL; foreach ($structures as $structureName => $internalStructure) { $globalCode .= "\t" . 'zephir_struct_' . $structureName . ' ' . $structureName . ';' . PHP_EOL; } /** * Process single variables */ foreach ($variables as $name => $global) { if (preg_match('/^[0-9a-zA-Z_]$/', $name)) { throw new Exception("Extension global variable name: '" . $name . "' contains invalid characters"); } if (!isset($global['default'])) { throw new Exception("Extension global variable name: '" . $name . "' contains invalid characters"); } $isModuleGlobal = (int)!empty($global['module']); $type = $global['type']; // TODO: Add support for 'hash' // TODO: Zephir\Optimizers\FunctionCall\GlobalsSetOptimizer switch ($global['type']) { case 'boolean': case 'bool': $type = 'zend_bool'; if (true === $global['default']) { $globalsDefault[$isModuleGlobal][] = "\t" . $namespace . '_globals->' . $name . ' = 1;'; } else { $globalsDefault[$isModuleGlobal][] = "\t" . $namespace . '_globals->' . $name . ' = 0;'; } break; case 'int': case 'uint': case 'long': case 'double': $globalsDefault[$isModuleGlobal][] = "\t" . $namespace . '_globals->' . $name . ' = ' . $global['default'] . ';'; break; case 'char': case 'uchar': $globalsDefault[$isModuleGlobal][] = "\t" . $namespace . '_globals->' . $name . ' = \'' . $global['default'] . '\';'; break; case 'string': $type = 'char *'; $globalsDefault[$isModuleGlobal][] = "\t" . $namespace . '_globals->' . $name . ' = ZSTR_VAL(zend_string_init(ZEND_STRL("' . $global['default'] . '"), 0));'; break; default: throw new Exception( "Unknown type '" . $global['type'] . "' for extension global '" . $name . "'" ); } $globalCode .= "\t" . $type . ' ' . $name . ';' . PHP_EOL; $iniEntry = $global['ini-entry'] ?? []; $iniName = $iniEntry['name'] ?? $namespace . '.' . $name; $scope = $iniEntry['scope'] ?? 'PHP_INI_ALL'; switch ($global['type']) { case 'boolean': case 'bool': $initEntries[] = 'STD_PHP_INI_BOOLEAN("' . $iniName . '", "' . (int)(true === $global['default']) . '", ' . $scope . ', OnUpdateBool, ' . $name . ', zend_' . $namespace . '_globals, ' . $namespace . '_globals)'; break; case 'string': $initEntries[] = sprintf( 'STD_PHP_INI_ENTRY(%s, %s, %s, NULL, %s, %s, %s)', '"' . $iniName . '"', '"' . $global['default'] . '"', $scope, $name, 'zend_' . $namespace . '_globals', $namespace . '_globals', ); break; } } } $globalsDefault[0] = implode(PHP_EOL, $globalsDefault[0]); $globalsDefault[1] = implode(PHP_EOL, $globalsDefault[1]); return [$globalCode, $globalStruct, $globalsDefault, $initEntries]; } /** * Generates phpinfo() sections showing information about the extension. */ public function processExtensionInfo(): string { $phpinfo = ''; $info = $this->config->get('info'); if (!is_array($info)) { return $phpinfo; } foreach ($info as $table) { $phpinfo .= "\t" . 'php_info_print_table_start();' . PHP_EOL; if (isset($table['header'])) { $headerArray = []; foreach ($table['header'] as $header) { $headerArray[] = '"' . htmlentities($header) . '"'; } $phpinfo .= "\t" . 'php_info_print_table_header(' . count($headerArray) . ', ' . implode(', ', $headerArray) . ');' . PHP_EOL; } if (isset($table['rows'])) { foreach ($table['rows'] as $row) { $rowArray = []; foreach ($row as $field) { $rowArray[] = '"' . htmlentities($field) . '"'; } $phpinfo .= "\t" . 'php_info_print_table_row(' . count($rowArray) . ', ' . implode(', ', $rowArray) . ');' . PHP_EOL; } } $phpinfo .= "\t" . 'php_info_print_table_end();' . PHP_EOL; } return $phpinfo; } /** * Sets extensions globals. */ public function setExtensionGlobals(array $globals): void { foreach ($globals as $key => $value) { $this->globals[$key] = $value; } } public function setOptimizersPath(string $optimizersPath): void { $this->optimizersPath = $optimizersPath; } public function setPrototypesPath(string $prototypesPath): void { $this->prototypesPath = $prototypesPath; } public function setTemplatesPath(string $templatesPath): void { $this->templatesPath = $templatesPath; } /** * Generate IDE stubs. * * @throws Exception * @throws ReflectionException */ public function stubs(bool $fromGenerate = false): void { if (!$fromGenerate) { $this->generate(); } $this->logger->info('Generating stubs...'); $path = str_replace( [ '%version%', '%namespace%', ], [ $this->config->get('version'), ucfirst($this->config->get('namespace')), ], $this->config->get('path', 'stubs') ); (new Stubs\Generator($this->files))->generate( $this->config->get('namespace'), $path, $this->config->get('indent', 'extra'), $this->config->get('banner', 'stubs') ?? '' ); } /** * Ensure that required extensions is present. * * @throws RuntimeException */ private function assertRequiredExtensionsIsPresent(): void { $extensionRequires = $this->config->get('extensions', 'requires'); if (empty($extensionRequires)) { return; } $extensions = []; foreach ($extensionRequires as $value) { // TODO: We'll use this as an object in the future. if (!is_string($value)) { continue; } if (!extension_loaded($value)) { $extensions[] = $value; } } if (!empty($extensions)) { throw new RuntimeException( sprintf( 'Could not load extension(s): %s. You must load extensions above before build this extension.', implode(', ', $extensions) ) ); } } /** * Checks if the current directory is a valid Zephir project. * * @throws Exception */ private function checkDirectory(): string { $namespace = $this->config->get('namespace'); if (!$namespace) { // TODO: Add more user friendly message. // For example assume if the user call the command from the wrong dir throw new Exception('Extension namespace cannot be loaded'); } if (!is_string($namespace)) { throw new Exception('Extension namespace is invalid'); } if (!$this->filesystem->isInitialized()) { $this->filesystem->initialize(); } if (!$this->filesystem->exists('.')) { if (!$this->checkIfPhpized()) { $this->logger->info( 'Zephir version has changed, use "zephir fullclean" to perform a full clean of the project' ); } $this->filesystem->makeDirectory('.'); } return $namespace; } /** * Checks if a file must be copied. * * @param string $src * @param string $dst * * @return bool */ private function checkKernelFile(string $src, string $dst): bool { if (preg_match('#kernels/ZendEngine[2-9]/concat\.#', $src)) { return true; } if (!file_exists($dst)) { return false; } return md5_file($src) === md5_file($dst); } /** * Checks which files in the base kernel must be copied. * * @throws Exception */ private function checkKernelFiles(): bool { $kernelPath = 'ext' . DIRECTORY_SEPARATOR . 'kernel'; if (!file_exists($kernelPath)) { if (!mkdir($kernelPath, 0775, true)) { throw new Exception("Cannot create kernel directory: {$kernelPath}"); } } $kernelPath = realpath($kernelPath); $sourceKernelPath = $this->backend->getInternalKernelPath(); $configured = $this->recursiveProcess( $sourceKernelPath, $kernelPath, '@.*\.[ch]$@', [$this, 'checkKernelFile'] ); if (!$configured) { $this->logger->info('Cleaning old kernel files...'); $this->recursiveDeletePath($kernelPath, '@^.*\.[lcho]$@'); @mkdir($kernelPath); $this->logger->info('Copying new kernel files...'); $this->recursiveProcess($sourceKernelPath, $kernelPath, '@^.*\.[ch]$@'); } return !$configured; } /** * Returns current GCC version. */ private function getGccVersion(): string { if (Os::isWindows()) { return '0.0.0'; } if ($this->filesystem->exists('gcc-version')) { return $this->filesystem->read('gcc-version'); } $this->filesystem->system('gcc -dumpversion', 'stdout', 'gcc-version'); $lines = $this->filesystem->file('gcc-version'); $lines = array_filter($lines); $lastLine = $lines[count($lines) - 1]; if (preg_match('/\d+\.\d+\.\d+/', $lastLine, $matches)) { return $matches[0]; } return '0.0.0'; } private function getWindowsReleaseDir(): string { if ($this->isZts()) { if (PHP_INT_SIZE === 4) { // 32-bit version of PHP return 'ext\\Release_TS'; } if (PHP_INT_SIZE === 8) { // 64-bit version of PHP return 'ext\d\\Release_TS'; } // fallback return 'ext\\Release_TS'; } if (PHP_INT_SIZE === 4) { // 32-bit version of PHP return 'ext\\Release'; } if (PHP_INT_SIZE === 8) { // 64-bit version of PHP return 'ext\d\\Release'; } // fallback return 'ext\\Release'; } private function isZts(): bool { if (defined('ZEND_THREAD_SAFE') && ZEND_THREAD_SAFE === true) { return true; } ob_start(); phpinfo(INFO_GENERAL); return (bool)preg_match('/Thread\s*Safety\s*enabled/i', strip_tags(ob_get_clean())); } /** * Registers C-constants as PHP constants from a C-file. * * @param array $constantsSources * * @throws Exception */ private function loadConstantsSources(array $constantsSources): void { foreach ($constantsSources as $constantsSource) { if (!file_exists($constantsSource)) { throw new Exception("File '" . $constantsSource . "' with constants definitions"); } foreach (file($constantsSource) as $line) { if (preg_match('/^\#define[ \t]+([A-Z0-9\_]+)[ \t]+([0-9]+)/', $line, $matches)) { $this->constants[$matches[1]] = ['int', $matches[2]]; continue; } if (preg_match('/^\#define[ \t]+([A-Z0-9\_]+)[ \t]+(\'(.){1}\')/', $line, $matches)) { $this->constants[$matches[1]] = ['char', $matches[3]]; } } } } /** * Pre-compiles classes creating a CompilerFile definition. * * @throws IllegalStateException */ private function preCompile(string $filePath): void { if (!$this->parserManager->isAvailable()) { throw new IllegalStateException($this->parserManager->requirements()); } if (preg_match('#\.zep$#', $filePath)) { $className = str_replace(DIRECTORY_SEPARATOR, '\\', $filePath); $className = preg_replace('#.zep$#', '', $className); $className = implode('\\', array_map('ucfirst', explode('\\', $className))); $compilerFile = $this->compilerFileFactory->create($className, $filePath); $compilerFile->preCompile($this); $this->files[$className] = $compilerFile; $this->definitions[$className] = $compilerFile->getClassDefinition(); } } /** * Process config.w32 sections. * * @param array $sources * @param string $project * * @return array */ private function processAddSources(array $sources, string $project): array { $groupSources = []; foreach ($sources as $source) { $dirName = str_replace(DIRECTORY_SEPARATOR, '/', dirname($source)); if (!isset($groupSources[$dirName])) { $groupSources[$dirName] = []; } $groupSources[$dirName][] = basename($source); } $groups = []; foreach ($groupSources as $dirname => $files) { $groups[] = 'ADD_SOURCES(configure_module_dirname + "/' . $dirname . '", "' . implode(' ', $files) . '", "' . $project . '");'; } return $groups; } /** * Recursively deletes files in a specified location. * * @param string $path Directory to deletes files * @param string $mask Regular expression to deletes files * * @deprecated * */ private function recursiveDeletePath($path, $mask): void { if (!file_exists($path) || !is_dir($path) || !is_readable($path)) { $this->logger->warning("Directory '{$path}' is not readable. Skip..."); return; } $objects = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST ); foreach ($objects as $name => $object) { if (preg_match($mask, $name)) { @unlink($name); } } } /** * Recursively pre-compiles all sources found in the given path. * * @throws IllegalStateException * @throws InvalidArgumentException */ private function recursivePreCompile(string $path): void { if (!is_dir($path)) { throw new InvalidArgumentException( sprintf( "An invalid path was passed to the compiler. Unable to obtain the '%s%s%s' directory.", getcwd(), DIRECTORY_SEPARATOR, $path ) ); } /** * Pre compile all files. */ $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST ); $files = []; foreach ($iterator as $item) { if (!$item->isDir()) { $files[] = $item->getPathname(); } } sort($files, SORT_STRING); foreach ($files as $file) { $this->preCompile($file); } } /** * Copies the base kernel to the extension destination. * * TODO: * * @param $src * @param $dest * @param string $pattern * @param mixed $callback * * @return bool * @deprecated * */ private function recursiveProcess($src, $dest, $pattern = null, $callback = 'copy') { $success = true; $iterator = new DirectoryIterator($src); foreach ($iterator as $item) { $pathName = $item->getPathname(); if (!is_readable($pathName)) { $this->logger->warning('File is not readable :' . $pathName); continue; } $fileName = $item->getFileName(); if ($item->isDir()) { if ('.' != $fileName && '..' != $fileName && '.libs' != $fileName) { if (!is_dir($dest . DIRECTORY_SEPARATOR . $fileName)) { mkdir($dest . DIRECTORY_SEPARATOR . $fileName, 0755, true); } $this->recursiveProcess($pathName, $dest . DIRECTORY_SEPARATOR . $fileName, $pattern, $callback); } } elseif (!$pattern || ($pattern && 1 === preg_match($pattern, $fileName))) { $path = $dest . DIRECTORY_SEPARATOR . $fileName; $success = $success && call_user_func($callback, $pathName, $path); } } return $success; } /** * Resolves path to the internal optimizers. * * @throws IllegalStateException in case of absence internal optimizers directory */ private function resolveOptimizersPath(): ?string { $optimizersPath = $this->optimizersPath; // fallback if (empty($optimizersPath)) { $optimizersPath = __DIR__ . '/Optimizers'; } if (!is_dir($optimizersPath) || !is_readable($optimizersPath)) { throw new IllegalStateException('Unable to resolve internal optimizers directory.'); } return $optimizersPath; } /** * Resolves path to the internal prototypes. */ private function resolvePrototypesPath(): ?string { $prototypesPath = $this->prototypesPath; // fallback if (empty($prototypesPath)) { $prototypesPath = dirname(__DIR__) . '/prototypes'; } if (!is_dir($prototypesPath) || !is_readable($prototypesPath)) { throw new IllegalStateException('Unable to resolve internal prototypes directory.'); } return $prototypesPath; } private function toUnixPaths(array $paths): array { return array_map( static fn(string $path): string => str_replace(DIRECTORY_SEPARATOR, '/', $path), $paths ); } } ?>