From 2abe1e91584a1fb59f98a5a4d2d36327070e685f Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Thu, 9 Jan 2025 01:52:18 +0100 Subject: [PATCH 1/6] Rename PHPSystem This renames the weird PHPSystem to simply PHP, however it may clash with variables set by `project(PHP)`, so the `find_package()` is wrapped in the `block()` and propagates PHP_FOUND. The PHP_EXECUTABLE is cache variable, so it's available outside of the block as expected. --- cmake/CMakeLists.txt | 2 +- cmake/cmake/Requirements.cmake | 15 ++-- cmake/cmake/modules/FindPHP.cmake | 78 +++++++++++++++++++ cmake/cmake/modules/FindPHPSystem.cmake | 59 -------------- .../cmake/modules/PHP/AddCustomCommand.cmake | 18 +++-- .../PHP/AddCustomCommand/RunCommand.cmake | 22 ++++-- cmake/cmake/modules/PHP/Stubs.cmake | 12 +-- cmake/ext/phar/CMakeLists.txt | 13 ++-- cmake/pear/CMakeLists.txt | 11 ++- 9 files changed, 134 insertions(+), 96 deletions(-) create mode 100644 cmake/cmake/modules/FindPHP.cmake delete mode 100644 cmake/cmake/modules/FindPHPSystem.cmake diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 050edd11..08e47d15 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -62,7 +62,7 @@ block() endblock() # Rebuild all targets as needed. -if(NOT PHPSystem_EXECUTABLE) +if(NOT PHP_FOUND) include(PHP/Rebuild) endif() diff --git a/cmake/cmake/Requirements.cmake b/cmake/cmake/Requirements.cmake index a2690fbc..b5f49202 100644 --- a/cmake/cmake/Requirements.cmake +++ b/cmake/cmake/Requirements.cmake @@ -50,9 +50,12 @@ endif() find_package(Sendmail) ################################################################################ -# Find PHP installed on the system for generating stub files (*_arginfo.h), -# Zend/zend_vm_gen.php, ext/tokenizer/tokenizer_data_gen.php and similar where -# it can be used. Otherwise the built cli sapi is used at the build phase. -# Minimum supported version for gen_stub.php is PHP 7.4. -################################################################################ -find_package(PHPSystem 7.4) +# Find PHP installed on the system and set PHP_EXECUTABLE for development such +# as generating stubs (*_arginfo.h) with build/gen_stub.php, running PHP scripts +# Zend/zend_vm_gen.php and similar. Otherwise the sapi/cli executable will be +# used at the build phase, where possible. The minimum version should match the +# version required to run these PHP scripts. +################################################################################ +block(PROPAGATE PHP_FOUND) + find_package(PHP 7.4) +endblock() diff --git a/cmake/cmake/modules/FindPHP.cmake b/cmake/cmake/modules/FindPHP.cmake new file mode 100644 index 00000000..75835ef4 --- /dev/null +++ b/cmake/cmake/modules/FindPHP.cmake @@ -0,0 +1,78 @@ +#[=============================================================================[ +# FindPHP + +Find PHP on the system, if installed. + +## Result variables + +* `PHP_FOUND` - Whether the package has been found. +* `PHP_VERSION` - Package version, if found. + +## Cache variables + +* `PHP_EXECUTABLE` - PHP command-line executable, if available. +#]=============================================================================] + +include(FeatureSummary) +include(FindPackageHandleStandardArgs) + +set_package_properties( + PHP + PROPERTIES + URL "https://www.php.net" + DESCRIPTION "PHP: Hypertext Preprocessor" +) + +set(_phpRequiredVars PHP_EXECUTABLE) +set(_reason "") + +find_program( + PHP_EXECUTABLE + NAMES php + DOC "The path to the PHP executable" +) +mark_as_advanced(PHP_EXECUTABLE) + +if(NOT PHP_EXECUTABLE) + string(APPEND _reason "The php command-line executable could not be found. ") +endif() + +unset(PHP_VERSION) +block(PROPAGATE PHP_VERSION _reason _phpRequiredVars) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.29) + set(test IS_EXECUTABLE) + else() + set(test EXISTS) + endif() + + if(${test} ${PHP_EXECUTABLE}) + list(APPEND _phpRequiredVars PHP_VERSION) + + execute_process( + COMMAND ${PHP_EXECUTABLE} --version + OUTPUT_VARIABLE version + RESULT_VARIABLE result + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + + if(NOT result EQUAL 0) + string(APPEND _reason "Command '${PHP_EXECUTABLE} --version' failed. ") + elseif(version MATCHES "PHP ([0-9]+[0-9.]+[^ ]+) \\(cli\\)") + set(PHP_VERSION "${CMAKE_MATCH_1}") + else() + string(APPEND _reason "Invalid version format. ") + endif() + endif() +endblock() + +find_package_handle_standard_args( + PHP + REQUIRED_VARS ${_phpRequiredVars} + VERSION_VAR PHP_VERSION + HANDLE_VERSION_RANGE + REASON_FAILURE_MESSAGE "${_reason}" +) + +unset(_phpRequiredVars) +unset(_reason) diff --git a/cmake/cmake/modules/FindPHPSystem.cmake b/cmake/cmake/modules/FindPHPSystem.cmake deleted file mode 100644 index 7a1c948a..00000000 --- a/cmake/cmake/modules/FindPHPSystem.cmake +++ /dev/null @@ -1,59 +0,0 @@ -#[=============================================================================[ -# FindPHPSystem - -Find external PHP on the system, if installed. - -## Result variables - -* `PHPSystem_FOUND` - Whether the package has been found. -* `PHPSystem_VERSION` - Package version, if found. - -## Cache variables - -* `PHPSystem_EXECUTABLE` - PHP command-line tool, if available. -#]=============================================================================] - -include(FeatureSummary) -include(FindPackageHandleStandardArgs) - -set_package_properties( - PHPSystem - PROPERTIES - URL "https://www.php.net" - DESCRIPTION "PHP: Hypertext Preprocessor" -) - -set(_reason "") - -find_program( - PHPSystem_EXECUTABLE - NAMES php - DOC "Path to the PHP executable" -) - -block(PROPAGATE PHPSystem_VERSION) - if(PHPSystem_EXECUTABLE) - execute_process( - COMMAND "${PHPSystem_EXECUTABLE}" --version - OUTPUT_VARIABLE version - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - if(version MATCHES "PHP ([^ ]+) ") - set(PHPSystem_VERSION "${CMAKE_MATCH_1}") - endif() - endif() -endblock() - -mark_as_advanced(PHPSystem_EXECUTABLE) - -find_package_handle_standard_args( - PHPSystem - REQUIRED_VARS - PHPSystem_EXECUTABLE - VERSION_VAR PHPSystem_VERSION - HANDLE_VERSION_RANGE - REASON_FAILURE_MESSAGE "${_reason}" -) - -unset(_reason) diff --git a/cmake/cmake/modules/PHP/AddCustomCommand.cmake b/cmake/cmake/modules/PHP/AddCustomCommand.cmake index 84f10cb7..c31b9589 100644 --- a/cmake/cmake/modules/PHP/AddCustomCommand.cmake +++ b/cmake/cmake/modules/PHP/AddCustomCommand.cmake @@ -82,10 +82,10 @@ function(php_add_custom_command) set(verbatim "") endif() - if(PHPSystem_EXECUTABLE) + if(PHP_FOUND) add_custom_command( OUTPUT ${parsed_OUTPUT} - COMMAND ${PHPSystem_EXECUTABLE} ${parsed_PHP_COMMAND} + COMMAND ${PHP_EXECUTABLE} ${parsed_PHP_COMMAND} DEPENDS ${parsed_DEPENDS} COMMENT "${parsed_COMMENT}" ${verbatim} @@ -99,9 +99,12 @@ function(php_add_custom_command) endif() if(NOT CMAKE_CROSSCOMPILING) - set(PHP_EXECUTABLE "$") + set(phpExecutable "$") elseif(CMAKE_CROSSCOMPILING AND CMAKE_CROSSCOMPILING_EMULATOR) - set(PHP_EXECUTABLE "${CMAKE_CROSSCOMPILING_EMULATOR};$") + set( + phpExecutable + "${CMAKE_CROSSCOMPILING_EMULATOR};$" + ) else() return() endif() @@ -111,11 +114,10 @@ function(php_add_custom_command) add_custom_target( ${targetName} ALL COMMAND ${CMAKE_COMMAND} - -D "PHP_EXECUTABLE=${PHP_EXECUTABLE}" - -D "OUTPUT=${parsed_OUTPUT}" - -D "PHP_COMMAND=${parsed_PHP_COMMAND}" - -D "DEPENDS=${parsed_DEPENDS}" + -D "PHP_COMMAND=${phpExecutable};${parsed_PHP_COMMAND}" -D "COMMENT=${parsed_COMMENT}" + -D "DEPENDS=${parsed_DEPENDS}" + -D "OUTPUT=${parsed_OUTPUT}" -P ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/AddCustomCommand/RunCommand.cmake ${verbatim} ) diff --git a/cmake/cmake/modules/PHP/AddCustomCommand/RunCommand.cmake b/cmake/cmake/modules/PHP/AddCustomCommand/RunCommand.cmake index 6af28997..7c5b2804 100644 --- a/cmake/cmake/modules/PHP/AddCustomCommand/RunCommand.cmake +++ b/cmake/cmake/modules/PHP/AddCustomCommand/RunCommand.cmake @@ -4,14 +4,13 @@ dependent input source files and runs the command inside the execute_process(). Expected variables: -* PHP_EXECUTABLE -* PHP_COMMAND -* DEPENDS -* OUTPUT -* COMMENT +* COMMENT - String printed in the build output log. +* DEPENDS - A list of dependent files. +* OUTPUT - A list of output files to be produced. +* PHP_COMMAND - A list of command and its arguments. #]=============================================================================] -if(NOT CMAKE_SCRIPT_MODE_FILE OR NOT PHP_EXECUTABLE OR NOT PHP_COMMAND) +if(NOT CMAKE_SCRIPT_MODE_FILE OR NOT PHP_COMMAND) return() endif() @@ -45,7 +44,16 @@ if(COMMENT) ) endif() -execute_process(COMMAND ${PHP_EXECUTABLE} ${PHP_COMMAND}) +execute_process( + COMMAND ${PHP_COMMAND} + RESULT_VARIABLE result + ERROR_VARIABLE error +) + +if(NOT result EQUAL 0) + list(JOIN PHP_COMMAND " " command) + message(NOTICE "Command ended with non-zero status:\n ${command}\n${error}") +endif() # Update modification times of output files to not re-run the command on the # consecutive build runs. diff --git a/cmake/cmake/modules/PHP/Stubs.cmake b/cmake/cmake/modules/PHP/Stubs.cmake index 09d2c908..519fab92 100644 --- a/cmake/cmake/modules/PHP/Stubs.cmake +++ b/cmake/cmake/modules/PHP/Stubs.cmake @@ -15,7 +15,7 @@ function(_php_stubs_get_php_command result) # If PHP is not found on the system, the PHP cli SAPI will be used with the # tokenizer extension. if( - NOT PHPSystem_EXECUTABLE + NOT PHP_FOUND AND ( NOT TARGET PHP::sapi::cli OR (TARGET PHP::sapi::cli AND NOT TARGET PHP::ext::tokenizer) @@ -25,16 +25,16 @@ function(_php_stubs_get_php_command result) endif() # If external PHP is available, check for the required tokenizer extension. - if(PHPSystem_EXECUTABLE) + if(PHP_FOUND) execute_process( - COMMAND ${PHPSystem_EXECUTABLE} --ri tokenizer + COMMAND ${PHP_EXECUTABLE} --ri tokenizer RESULT_VARIABLE code OUTPUT_QUIET ERROR_QUIET ) if(code EQUAL 0) - set(${result} ${PHPSystem_EXECUTABLE}) + set(${result} ${PHP_EXECUTABLE}) return(PROPAGATE ${result}) endif() endif() @@ -113,7 +113,7 @@ block() $,INCLUDE,\.stub\.php$>,$> ) - if(PHPSystem_EXECUTABLE) + if(PHP_FOUND) add_dependencies(${target} php_stubs) endif() endforeach() @@ -128,7 +128,7 @@ block() ) set(targetOptions "") - if(NOT PHPSystem_EXECUTABLE) + if(NOT PHP_FOUND) set(targetOptions ALL DEPENDS ${targets}) endif() diff --git a/cmake/ext/phar/CMakeLists.txt b/cmake/ext/phar/CMakeLists.txt index 48140dbc..73b9f365 100644 --- a/cmake/ext/phar/CMakeLists.txt +++ b/cmake/ext/phar/CMakeLists.txt @@ -122,9 +122,12 @@ if(NOT TARGET PHP::sapi::cli) endif() if(NOT CMAKE_CROSSCOMPILING) - set(PHP_EXECUTABLE "$") + set(phpExecutable "$") elseif(CMAKE_CROSSCOMPILING AND CMAKE_CROSSCOMPILING_EMULATOR) - set(PHP_EXECUTABLE "${CMAKE_CROSSCOMPILING_EMULATOR};$") + set( + phpExecutable + "${CMAKE_CROSSCOMPILING_EMULATOR};$" + ) else() return() endif() @@ -148,7 +151,7 @@ cmake_path( add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/phar.php COMMAND - ${PHP_EXECUTABLE} + ${phpExecutable} -n ${pharSharedOptions} -d open_basedir= @@ -164,7 +167,7 @@ add_custom_command( add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/phar.phar COMMAND - ${PHP_EXECUTABLE} + ${phpExecutable} -n ${pharSharedOptions} -d open_basedir= @@ -240,7 +243,7 @@ set_property(SOURCE php_ext_phar_generated_phar PROPERTY SYMBOLIC TRUE) install(CODE " execute_process( COMMAND - ${PHP_EXECUTABLE} + ${phpExecutable} -n ${pharSharedOptions} -d open_basedir= diff --git a/cmake/pear/CMakeLists.txt b/cmake/pear/CMakeLists.txt index c9bdf0bb..19834eec 100644 --- a/cmake/pear/CMakeLists.txt +++ b/cmake/pear/CMakeLists.txt @@ -121,11 +121,14 @@ block() endforeach() endblock() -set(PHP_EXECUTABLE "") +set(phpExecutable "") if(NOT CMAKE_CROSSCOMPILING) - set(PHP_EXECUTABLE "$") + set(phpExecutable "$") elseif(CMAKE_CROSSCOMPILING AND CMAKE_CROSSCOMPILING_EMULATOR) - set(PHP_EXECUTABLE "${CMAKE_CROSSCOMPILING_EMULATOR};$") + set( + phpExecutable + "${CMAKE_CROSSCOMPILING_EMULATOR};$" + ) endif() php_install(CODE " @@ -133,7 +136,7 @@ php_install(CODE " set(phpPearInstallBinDir \"$\") set(phpPearCurrentSourceDir \"${CMAKE_CURRENT_SOURCE_DIR}\") set(phpPearCurrentBinaryDir \"${CMAKE_CURRENT_BINARY_DIR}\") - set(phpPearPhpExecutable \"${PHP_EXECUTABLE}\") + set(phpPearPhpExecutable \"${phpExecutable}\") set(phpExtensionDir \"$\") set(PHP_EXT_OPENSSL_SHARED ${PHP_EXT_OPENSSL_SHARED}) set(PHP_EXT_XML_SHARED ${PHP_EXT_XML_SHARED}) From ab0716291d16e9839a20e03519613e12c83992f8 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Fri, 10 Jan 2025 10:39:46 +0100 Subject: [PATCH 2/6] Extend FindPHP.cmake module This enables using the same find module in various scenarios: - building PHP extension with CMake - building application with embedded PHP - finding only PHP executable --- .github/workflows/ci.yaml | 16 + cmake/cmake/Requirements.cmake | 4 +- cmake/cmake/modules/FindPHP.cmake | 858 +++++++++++++++++- cmake/ext/skeleton/CMakeLists.txt.in | 23 +- cmake/ext/skeleton/cmake/Bootstrap.cmake | 10 - .../ext/skeleton/cmake/modules/FindPHP.cmake | 300 ------ 6 files changed, 860 insertions(+), 351 deletions(-) delete mode 100644 cmake/ext/skeleton/cmake/Bootstrap.cmake diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 50898216..0dbecb38 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -138,3 +138,19 @@ jobs: - name: Run tests run: | ctest --preset all-enabled + + - name: Install PHP + run: | + cd php-${{ matrix.php }} + cmake --install php-build/all-enabled + + - name: Setup shared standalone extension + run: | + cd php-${{ matrix.php }} + php ext/ext_skel.php --ext phantom + mkdir ext/phantom/cmake/modules + cp cmake/modules/FindPHP.cmake ext/phantom/cmake/modules + cmake -S ext/phantom -B ext/phantom/cmake-build + cmake --build ext/phantom/cmake-build -j + cmake --install ext/phantom/cmake-build + #php -d extension=phantom -m | grep phantom diff --git a/cmake/cmake/Requirements.cmake b/cmake/cmake/Requirements.cmake index b5f49202..7698e11d 100644 --- a/cmake/cmake/Requirements.cmake +++ b/cmake/cmake/Requirements.cmake @@ -56,6 +56,4 @@ find_package(Sendmail) # used at the build phase, where possible. The minimum version should match the # version required to run these PHP scripts. ################################################################################ -block(PROPAGATE PHP_FOUND) - find_package(PHP 7.4) -endblock() +find_package(PHP 7.4 COMPONENTS Interpreter) diff --git a/cmake/cmake/modules/FindPHP.cmake b/cmake/cmake/modules/FindPHP.cmake index 75835ef4..3476d40c 100644 --- a/cmake/cmake/modules/FindPHP.cmake +++ b/cmake/cmake/modules/FindPHP.cmake @@ -1,18 +1,350 @@ #[=============================================================================[ # FindPHP -Find PHP on the system, if installed. +Find PHP, the general-purpose scripting language, if it is installed on the +system. This module detects the PHP interpreter and related tools or components, +making them available for use in the build configuration. ## Result variables -* `PHP_FOUND` - Whether the package has been found. -* `PHP_VERSION` - Package version, if found. +This module defines the following result variables common to all PHP components: -## Cache variables +* `PHP_FOUND` - Whether the PHP and its requested components have been found. +* `PHP_FOUND_VERSION` - The PHP version found on the system. + +TODO: Move and refactor these two: +* `PHP_INCLUDE_DIRS` - List of required include directories when developing PHP + extensions or embedding PHP into application. +* `PHP_LIBRARIES` - List of libraries required to link when developing PHP + extensions or embedding PHP into application. + +## Components + +This module provides the following components: + +* `Interpreter` - The PHP interpreter, the command-line executable. +* `Development` - Component for building PHP extensions. +* `Embed` - The PHP embed component, a lightweight SAPI to embed PHP into + application using C bindings. + +Components can be specified using the standard CMake syntax: + +```cmake +find_package(PHP COMPONENTS ...) +``` + +If the `COMPONENTS` are not specified, module by default searches for the +`Interpreter` and `Development` components: + +```cmake +find_package(PHP) +``` + +### The `Interpreter` component + +When this component is found, it defines the following `IMPORTED` targets: + +* `PHP::Interpreter` - `IMPORTED` executable target with the `PHP_EXECUTABLE` + path that can be used in commands and similar. + +Cache variables of the `Interpreter` component: * `PHP_EXECUTABLE` - PHP command-line executable, if available. + +Result variables of the `Interpreter` component: + +* `PHP_Interpreter_FOUND` - Whether the PHP `Interpreter` component has been + found. + +### The `Development` component + +When this component is found, it defines the following `IMPORTED` targets: + +* `PHP::Development` - `IMPORTED` `INTERFACE` library with include directories + and configuration required for developing PHP extensions. + +Cache variables of the `Development` component: + +* `PHP_CONFIG_EXECUTABLE` - Path to the `php-config` development command-line + tool. +* `PHP_INCLUDE_DIR` - Directory containing PHP headers. + +Result variables of the `Development` component: + +* `PHP_Development_FOUND` - Whether the PHP `Development` component has been + found. +* `PHP_API_VERSION` - Internal PHP API version number (`PHP_API_VERSION` in + `
`). +* `PHP_ZEND_MODULE_API` - Internal API version number for PHP extensions + (`ZEND_MODULE_API_NO` in ``). These are most common PHP + extensions either built-in or loaded dynamically with the `extension` INI + directive. +* `PHP_ZEND_EXTENSION_API` - Internal API version number for Zend extensions + (`ZEND_EXTENSION_API_NO` in ``). Zend extensions are, + for example, opcache, debuggers, profilers and similar advanced extensions. + They are either built-in or dynamically loaded with the `zend_extension` INI + directive. +* `PHP_INSTALL_INCLUDEDIR` - Relative path to the `CMAKE_PREFIX_INSTALL` + containing PHP headers. +* `PHP_EXTENSION_DIR` - Path to the directory where shared extensions are + installed. + +### The `Embed` component + +When this component is found, it defines the following `IMPORTED` targets: + +* `PHP::Embed` - `IMPORTED` library with include directories and PHP Embed SAPI + library required to embed PHP into application. + +Cache variables of the `Embed` component: + +* `PHP_CONFIG_EXECUTABLE` - Path to the `php-config` development helper tool. +* `PHP_EMBED_INCLUDE_DIR` - Directory containing PHP headers. +* `PHP_EMBED_LIBRARY` - The path to the PHP Embed library (`libphp`). + +Result variables of the `Embed` component: + +* `PHP_Embed_FOUND` - Whether the PHP `Embed` component has been found. + +## Examples + +### Basic usage + +```cmake +# CMakeLists.txt + +# Find PHP: +find_package(PHP) + +# Or find only the PHP Interpreter component: +find_package(PHP COMPONENTS Interpreter) + +# The imported targets can be then linked to the target: +target_link_libraries(php_extension PRIVATE PHP::Development) +``` + +### How to use CMake in PHP extensions? + +Here is an example of a PHP extension called `phantom` for demonstration +purposes that uses CMake-based build system. + +To create a brand new PHP extension clone the `php-src` Git repository and run +the `ext_skel.php` helper script, which will create the `ext/phantom` extension: + +```sh +git clone https://github.com/php/php-src +cd php-src +./ext/ext_skel.php --ext phantom +``` + +Create a `cmake` and `cmake/modules` directories in the extension's top level +directory and add this `FindPHP.cmake` module to `cmake/modules`. + +Create a `CMakeLists.txt` in the extension's top level directory. For example: + +```cmake +# CMakeLists.txt + +# Set minimum required CMake version. +cmake_minimum_required(VERSION 3.25...3.31) + +# Append extension's local CMake modules. +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/modules) + +# Automatically include current source or build tree for the current target. +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Put the source or build tree include directories before other includes. +set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) + +# Set the extension's project metadata. +project( + php_ext_phantom + VERSION 0.1.0 + DESCRIPTION "Describe the extension" + HOMEPAGE_URL "https://example.org" + LANGUAGES C +) + +# Include CMake module to use cmake_dependent_option(). +include(CMakeDependentOption) + +# Include CMake module to use add_feature_info() and set_package_properties(). +include(FeatureSummary) + +# Boolean option that enables (ON) or disables (OFF) the extension. +option(PHP_EXT_PHANTOM "Enable the phantom extension" ON) + +# Extension features are visible in the configuration summary output. +add_feature_info( + "ext/phantom" + PHP_EXT_PHANTOM + "Describe the extension features" +) + +# Dependent boolean option that builds extension as a shared library. CMake's +# BUILD_SHARED_LIBS variable hides this option and builds extension as shared. +cmake_dependent_option( + PHP_EXT_PHANTOM_SHARED + "Build the phantom extension as a shared library" + OFF + [[PHP_EXT_PHANTOM AND NOT BUILD_SHARED_LIBS]] + OFF +) + +# If extension is disabled CMake configuration stops here. +if(NOT PHP_EXT_PHANTOM) + return() +endif() + +# Add a target to be built as a SHARED or STATIC library. +if(PHP_EXT_PHANTOM_SHARED) + add_library(php_ext_phantom SHARED) +else() + add_library(php_ext_phantom) +endif() + +# Add library target sources. +target_sources( + php_ext_phantom + PRIVATE + phantom.c + # If extension provides header(s) that will be consumed by other sources and + # should be installed, add header(s) to a file set. + PUBLIC + FILE_SET HEADERS + BASE_DIRS "${PHP_SOURCE_DIR}" + FILES + php_phantom.h +) + +# Find PHP on the system. +find_package(PHP REQUIRED) + +# Link the PHP::Development imported library which provides the PHP include +# directories and configuration for the extension. +target_link_libraries(php_ext_phantom PRIVATE PHP::Development) + +# Install files to system destinations with 'cmake --install'. +install( + TARGETS php_ext_phantom + ARCHIVE EXCLUDE_FROM_ALL + FILE_SET HEADERS +) +``` + +#### Dependencies in PHP extensions + +When PHP extension depends on other PHP extensions the best and most user +friendly way is to design the code from the beginning in a way that dependent +PHP extensions can be checked during the runtime. For example: + +```c +if (zend_hash_str_exists(&module_registry, "openssl", sizeof("openssl")-1)) { + // PHP extension "openssl" is enabled. +} else { + // PHP extension "openssl" is not enabled. +} +``` + +This makes extension more intuitive to enable and use by the end user. + +PHP extensions are often times built as shared, where checking C preprocessor +macros from header files may not be enough. A common but inconvenient approach +is to check for dependent PHP extensions at the compile time. For example: + +```c +#ifdef HAVE_OPENSSL_EXT +/* The PHP 'openssl' extension is available. */ +#endif + +#if defined(HAVE_OPENSSL_EXT) && defined(COMPILE_DL_OPENSSL) +/* The PHP 'openssl' extension is available but is built as shared. */ +#endif +``` + +This however, depends on whether the `HAVE_OPENSSL_EXT` is defined in the +`main/php_config.h` header file. It is unreliable and error prone method when +dependent extension is built as shared and not enabled at runtime. + +When extension dependency is required, adding a `ZEND_MOD_REQUIRED` entry +ensures that extensions are loaded in correct order. + +When extension depends on an external library, another CMake find module needs +to be used to locate that dependency on the system. See existing usages in the +CMake-based build system for more info. + +### Embedding PHP into application + +To build example application with embedded PHP, create `CMakeLists.txt`: + +```cmake +# CMakeLists.txt +cmake_minimum_required(VERSION 3.25...3.31) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/modules) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) + +project(ApplicationWithEmbeddedPhp C) + +add_executable(app app.c) + +# Find the PHP Embed component (PHP Embed SAPI) on the system. +find_package(PHP COMPONENTS Embed) + +# Link imported library provided by the PHP find module to the application. +target_link_libraries(app PRIVATE PHP::Embed) +``` + +Create `app.c`: + +```c +/* app.c */ +#include + +int main(int argc, char **argv) +{ + /* Invokes the Zend Engine initialization phase: SAPI (SINIT), modules + * (MINIT), and request (RINIT). It also opens a 'zend_try' block to catch + * a zend_bailout(). + */ + PHP_EMBED_START_BLOCK(argc, argv) + + php_printf( + "Number of functions loaded: %d\n", + zend_hash_num_elements(EG(function_table)) + ); + + /* Close the 'zend_try' block and invoke the shutdown phase: request + * (RSHUTDOWN), modules (MSHUTDOWN), and SAPI (SSHUTDOWN). + */ + PHP_EMBED_END_BLOCK() +} +``` + +Generate build system to a build directory: + +```sh +cmake -B build +``` + +Build application in parallel: + +```sh +cmake --build build -j +``` + +Run application executable: + +```sh +./build/app +``` #]=============================================================================] +cmake_minimum_required(VERSION 3.25...3.31) + include(FeatureSummary) include(FindPackageHandleStandardArgs) @@ -20,59 +352,523 @@ set_package_properties( PHP PROPERTIES URL "https://www.php.net" - DESCRIPTION "PHP: Hypertext Preprocessor" + DESCRIPTION "General-purpose scripting language" ) -set(_phpRequiredVars PHP_EXECUTABLE) +set(_phpRequiredVars "") set(_reason "") +set(PHP_FOUND_VERSION "") +set(PHP_INCLUDE_DIRS "") +set(PHP_LIBRARIES "") + +if(NOT PHP_FIND_COMPONENTS) + set(PHP_FIND_COMPONENTS "Interpreter" "Development") + set(PHP_FIND_REQUIRED_Interpreter TRUE) + set(PHP_FIND_REQUIRED_Development TRUE) +endif() + +################################################################################ +# The Interpreter component. +################################################################################ + +if("Interpreter" IN_LIST PHP_FIND_COMPONENTS) + find_program( + PHP_EXECUTABLE + NAMES php + DOC "The path to the PHP executable" + ) + mark_as_advanced(PHP_EXECUTABLE) + + if(NOT PHP_EXECUTABLE) + string(APPEND _reason "The php command-line executable could not be found. ") + endif() -find_program( - PHP_EXECUTABLE - NAMES php - DOC "The path to the PHP executable" + # Mark component as found or not and add required find module variables. + set(PHP_Interpreter_FOUND TRUE) + foreach(var IN ITEMS PHP_EXECUTABLE) + list(APPEND _phpRequiredVars ${var}) + if(NOT ${var}) + set(PHP_Interpreter_FOUND FALSE) + endif() + endforeach() +endif() + +################################################################################ +# Common configuration for Development and Embed components. +################################################################################ + +if( + "Development" IN_LIST PHP_FIND_COMPONENTS + OR "Embed" IN_LIST PHP_FIND_COMPONENTS ) -mark_as_advanced(PHP_EXECUTABLE) + # Try pkg-config. + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_PHP QUIET php) + endif() + + # Find php-config tool. + find_program( + PHP_CONFIG_EXECUTABLE + NAMES php-config + DOC "The path to the php-config development helper command-line tool" + ) + mark_as_advanced(PHP_CONFIG_EXECUTABLE) + + # Get PHP include directories. + if(PHP_CONFIG_EXECUTABLE) + execute_process( + COMMAND "${PHP_CONFIG_EXECUTABLE}" --includes + OUTPUT_VARIABLE PHP_INCLUDE_DIRS + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) -if(NOT PHP_EXECUTABLE) - string(APPEND _reason "The php command-line executable could not be found. ") + if(PHP_INCLUDE_DIRS) + separate_arguments(PHP_INCLUDE_DIRS NATIVE_COMMAND "${PHP_INCLUDE_DIRS}") + list(TRANSFORM PHP_INCLUDE_DIRS REPLACE "^-I" "") + endif() + endif() endif() -unset(PHP_VERSION) -block(PROPAGATE PHP_VERSION _reason _phpRequiredVars) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.29) - set(test IS_EXECUTABLE) - else() - set(test EXISTS) +################################################################################ +# The Development component. +################################################################################ + +if("Development" IN_LIST PHP_FIND_COMPONENTS) + find_path( + PHP_INCLUDE_DIR + NAMES main/php.h + HINTS + ${PC_PHP_INCLUDE_DIRS} + ${PHP_INCLUDE_DIRS} + DOC "Directory containing PHP headers" + ) + mark_as_advanced(PHP_INCLUDE_DIR) + + if(NOT PHP_INCLUDE_DIR) + string(APPEND _reason "The
header file not found. ") endif() - if(${test} ${PHP_EXECUTABLE}) - list(APPEND _phpRequiredVars PHP_VERSION) + # Get PHP_EXTENSION_DIR. + if(PHP_CONFIG_EXECUTABLE) + execute_process( + COMMAND "${PHP_CONFIG_EXECUTABLE}" --extension-dir + OUTPUT_VARIABLE PHP_EXTENSION_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + endif() + if(NOT PHP_EXTENSION_DIR AND PKG_CONFIG_FOUND) + pkg_get_variable(PHP_EXTENSION_DIR php extensiondir) + endif() + + # Get PHP API version number. + block(PROPAGATE PHP_API_VERSION) + if(EXISTS ${PHP_INCLUDE_DIR}/main/php.h) + set(regex "#[ \t]*define[ \t]+PHP_API_VERSION[ \t]+([0-9]+)") + file(STRINGS ${PHP_INCLUDE_DIR}/main/php.h _ REGEX "${regex}") + + if(CMAKE_VERSION VERSION_LESS 3.29) + string(REGEX MATCH "${regex}" _ "${_}") + endif() + + set(PHP_API_VERSION "${CMAKE_MATCH_1}") + endif() + endblock() + # Get PHP extensions API version number. + block(PROPAGATE PHP_ZEND_MODULE_API) + if(EXISTS ${PHP_INCLUDE_DIR}/Zend/zend_modules.h) + set(regex "#[ \t]*define[ \t]+ZEND_MODULE_API_NO[ \t]+([0-9]+)") + file(STRINGS ${PHP_INCLUDE_DIR}/Zend/zend_modules.h _ REGEX "${regex}") + + if(CMAKE_VERSION VERSION_LESS 3.29) + string(REGEX MATCH "${regex}" _ "${_}") + endif() + + set(PHP_ZEND_MODULE_API "${CMAKE_MATCH_1}") + endif() + endblock() + + # Get Zend extensions API version number. + block(PROPAGATE PHP_ZEND_EXTENSION_API) + if(EXISTS ${PHP_INCLUDE_DIR}/Zend/zend_extensions.h) + set(regex "#[ \t]*define[ \t]+ZEND_EXTENSION_API_NO[ \t]+([0-9]+)") + file(STRINGS ${PHP_INCLUDE_DIR}/Zend/zend_extensions.h _ REGEX "${regex}") + + if(CMAKE_VERSION VERSION_LESS 3.29) + string(REGEX MATCH "${regex}" _ "${_}") + endif() + + set(PHP_ZEND_EXTENSION_API "${CMAKE_MATCH_1}") + endif() + endblock() + + # Get relative PHP include directory path. + if(PHP_CONFIG_EXECUTABLE) execute_process( - COMMAND ${PHP_EXECUTABLE} --version - OUTPUT_VARIABLE version - RESULT_VARIABLE result + COMMAND "${PHP_CONFIG_EXECUTABLE}" --include-dir + OUTPUT_VARIABLE PHP_INSTALL_INCLUDEDIR OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET ) + execute_process( + COMMAND "${PHP_CONFIG_EXECUTABLE}" --prefix + OUTPUT_VARIABLE PHP_INSTALL_PREFIX + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + cmake_path( + RELATIVE_PATH + PHP_INSTALL_INCLUDEDIR + BASE_DIRECTORY "${PHP_INSTALL_PREFIX}" + ) + elseif(PC_PHP_PREFIX) + cmake_path( + RELATIVE_PATH + PC_PHP_PREFIX + BASE_DIRECTORY "${PHP_INSTALL_PREFIX}" + OUTPUT_VARIABLE PHP_INSTALL_INCLUDEDIR + ) + endif() - if(NOT result EQUAL 0) - string(APPEND _reason "Command '${PHP_EXECUTABLE} --version' failed. ") - elseif(version MATCHES "PHP ([0-9]+[0-9.]+[^ ]+) \\(cli\\)") - set(PHP_VERSION "${CMAKE_MATCH_1}") - else() - string(APPEND _reason "Invalid version format. ") + # Mark component as found or not found and add required find module variables. + set(PHP_Development_FOUND TRUE) + foreach( + var IN ITEMS + PHP_API_VERSION + PHP_EXTENSION_DIR + PHP_INCLUDE_DIR + PHP_ZEND_EXTENSION_API + PHP_ZEND_MODULE_API + ) + list(APPEND _phpRequiredVars ${var}) + if(NOT ${var}) + set(PHP_Development_FOUND FALSE) endif() + endforeach() +endif() + +################################################################################ +# The Embed component. +################################################################################ + +if("Embed" IN_LIST PHP_FIND_COMPONENTS) + # Try pkg-config. + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_PHP_EMBED QUIET php-embed) + endif() + + find_path( + PHP_EMBED_INCLUDE_DIR + NAMES sapi/embed/php_embed.h + HINTS + ${PC_PHP_INCLUDE_DIRS} + ${PC_PHP_EMBED_INCLUDE_DIRS} + ${PHP_INCLUDE_DIRS} + DOC "Directory containing PHP headers" + ) + mark_as_advanced(PHP_EMBED_INCLUDE_DIR) + if(NOT PHP_EMBED_INCLUDE_DIR) + string(APPEND _reason "The header file not found. ") + endif() + + find_library( + PHP_EMBED_LIBRARY + NAMES php + HINTS ${PC_PHP_EMBED_LIBRARY_DIRS} + DOC "The path to the libphp embed library" + ) + mark_as_advanced(PHP_EMBED_LIBRARY) + if(NOT PHP_EMBED_LIBRARY) + string(APPEND _reason "PHP library (libphp) not found. ") + else() + list(APPEND PHP_LIBRARIES ${PHP_EMBED_LIBRARY}) + endif() + + # Mark component as found or not and add required find module variables. + set(PHP_Embed_FOUND TRUE) + foreach(var IN ITEMS PHP_EMBED_INCLUDE_DIR PHP_EMBED_LIBRARY) + list(APPEND _phpRequiredVars ${var}) + if(NOT ${var}) + set(PHP_Embed_FOUND FALSE) + endif() + endforeach() +endif() + +################################################################################ +# Get PHP version. +################################################################################ + +block(PROPAGATE PHP_FOUND_VERSION _reason) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.29) + set(test IS_EXECUTABLE) + else() + set(test EXISTS) + endif() + + if(NOT PHP_FOUND_VERSION AND PHP_EXECUTABLE) + if(${test} ${PHP_EXECUTABLE}) + execute_process( + COMMAND ${PHP_EXECUTABLE} --version + OUTPUT_VARIABLE version + RESULT_VARIABLE result + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + + if(NOT result EQUAL 0) + string(APPEND _reason "Command '${PHP_EXECUTABLE} --version' failed. ") + elseif(version MATCHES "PHP ([0-9]+[0-9.]+[^ ]+) \\(cli\\)") + set(PHP_FOUND_VERSION "${CMAKE_MATCH_1}") + else() + string(APPEND _reason "Invalid version format. ") + endif() + endif() + endif() + + if(NOT PHP_FOUND_VERSION AND PHP_CONFIG_EXECUTABLE) + if(${test} ${PHP_CONFIG_EXECUTABLE}) + execute_process( + COMMAND ${PHP_CONFIG_EXECUTABLE} --versiona + OUTPUT_VARIABLE version + RESULT_VARIABLE result + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + + if(NOT result EQUAL 0) + string( + APPEND + _reason + "Command '${PHP_CONFIG_EXECUTABLE} --version' failed. " + ) + elseif(version MATCHES "([0-9]+[0-9.]+[^ \n]+)") + set(PHP_FOUND_VERSION "${CMAKE_MATCH_1}") + else() + string(APPEND _reason "Invalid version format. ") + endif() + endif() + endif() + + set(includeDir "") + if(NOT PHP_FOUND_VERSION AND PHP_INCLUDE_DIR) + set(includeDir ${PHP_INCLUDE_DIR}) + elseif(NOT PHP_FOUND_VERSION AND PHP_EMBED_INCLUDE_DIR) + set(includeDir ${PHP_EMBED_INCLUDE_DIR}) + endif() + + if( + NOT PHP_FOUND_VERSION + AND includeDir + AND EXISTS ${includeDir}/main/php_version.h + ) + set(regex "^[ \t]*#[ \t]*define[ \t]+PHP_VERSION[ \t]+\"([^\"]+)\"") + file( + STRINGS + ${includeDir}/main/php_version.h + result + REGEX "${regex}" + LIMIT_COUNT 1 + ) + if(CMAKE_VERSION VERSION_LESS 3.29) + string(REGEX MATCH "${regex}" _ "${result}") + endif() + set(PHP_FOUND_VERSION "${CMAKE_MATCH_1}") + endif() + + if( + NOT PHP_FOUND_VERSION + AND PC_PHP_VERSION + AND PHP_INCLUDE_DIR IN_LIST PC_PHP_INCLUDE_DIRS + ) + set(PHP_FOUND_VERSION ${PC_PHP_VERSION}) + elseif( + NOT PHP_FOUND_VERSION + AND PC_PHP_EMBED_VERSION + AND PHP_EMBED_INCLUDE_DIR IN_LIST PC_PHP_EMBED_INCLUDE_DIRS + ) + set(PHP_FOUND_VERSION ${PC_PHP_EMBED_VERSION}) endif() endblock() +if(PHP_FIND_VERSION AND NOT PHP_FOUND_VERSION) + string(APPEND _reason "The PHP version could not be determined. ") +endif() + +################################################################################ +# Handle package standard arguments. +################################################################################ + +# Move the most informative variable to the beginning of the list. It is part of +# the result message by find_package_handle_standard_args(). +if("PHP_EXECUTABLE" IN_LIST _phpRequiredVars) + list(PREPEND _phpRequiredVars "PHP_EXECUTABLE") +elseif("PHP_INCLUDE_DIR" IN_LIST _phpRequiredVars) + list(PREPEND _phpRequiredVars "PHP_INCLUDE_DIR") +elseif("PHP_EMBED_LIBRARY" IN_LIST _phpRequiredVars) + list(PREPEND _phpRequiredVars "PHP_EMBED_LIBRARY") +endif() +list(REMOVE_DUPLICATES _phpRequiredVars) + find_package_handle_standard_args( PHP REQUIRED_VARS ${_phpRequiredVars} - VERSION_VAR PHP_VERSION + VERSION_VAR PHP_FOUND_VERSION HANDLE_VERSION_RANGE + HANDLE_COMPONENTS REASON_FAILURE_MESSAGE "${_reason}" ) unset(_phpRequiredVars) unset(_reason) + +################################################################################ +# Configuration when PHP is found. +################################################################################ + +if(NOT PHP_FOUND) + return() +endif() + +################################################################################ +# Interpreter component configuration. +################################################################################ + +if("Interpreter" IN_LIST PHP_FIND_COMPONENTS AND NOT TARGET PHP::Interpreter) + add_executable(PHP::Interpreter IMPORTED) + set_target_properties( + PHP::Interpreter + PROPERTIES + IMPORTED_LOCATION "${PHP_EXECUTABLE}" + ) +endif() + +################################################################################ +# Development component configuration. +################################################################################ + +if("Development" IN_LIST PHP_FIND_COMPONENTS) + if(NOT TARGET PHP::Development) + add_library(PHP::Development INTERFACE IMPORTED) + + set_target_properties( + PHP::Development + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PHP_INCLUDE_DIRS}" + ) + + # Compile definitions for PHP extensions: + target_compile_definitions( + PHP::Development + INTERFACE + $<$,MODULE_LIBRARY;SHARED_LIBRARY>:ZEND_COMPILE_DL_EXT> + HAVE_CONFIG_H + ) + endif() + + cmake_language(DEFER CALL _php_extension_deferred_configuration) + function(_php_extension_deferred_configuration) + if(PROJECT_NAME MATCHES "^php_ext_(.+)$") + set(extension "${CMAKE_MATCH_1}") + else() + return() + endif() + + if(NOT TARGET php_ext_${extension}) + return() + endif() + + # Set target library filename prefix to empty "" instead of default "lib". + set_property(TARGET php_ext_${extension} PROPERTY PREFIX "") + + set_target_properties( + php_ext_${extension} + PROPERTIES + POSITION_INDEPENDENT_CODE ON + ) + + # Set target output filename to "". + get_target_property(output php_ext_${extension} OUTPUT_NAME) + if(NOT output) + set_property(TARGET php_ext_${extension} PROPERTY OUTPUT_NAME ${extension}) + endif() + + # Configure shared extension. + get_target_property(type php_ext_${extension} TYPE) + if(type MATCHES "^(MODULE|SHARED)_LIBRARY$") + # Set build-phase location for shared extensions. + get_target_property(location php_ext_${extension} LIBRARY_OUTPUT_DIRECTORY) + if(NOT location) + set_property( + TARGET php_ext_${extension} + PROPERTY LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/modules" + ) + endif() + endif() + + _php_extension_configure_header(${extension}) + endfunction() + + function(_php_extension_configure_header extension) + string(TOUPPER "COMPILE_DL_${extension}" macro) + + get_target_property(type php_ext_${extension} TYPE) + if(type MATCHES "^(MODULE|SHARED)_LIBRARY$") + set(${macro} TRUE) + else() + set(${macro} FALSE) + endif() + + # Prepare config.h template. + string( + JOIN + "" + template + "/* Define to 1 if the PHP extension '@extension@' is built as a dynamic " + "module. */\n" + "#cmakedefine ${macro} 1\n" + ) + + get_target_property(binaryDir php_ext_${extension} BINARY_DIR) + set(current "") + if(EXISTS ${binaryDir}/config.h) + file(READ ${binaryDir}/config.h current) + endif() + + string(STRIP "${template}\n${current}" config) + + # Finalize extension's config.h header file. + file(CONFIGURE OUTPUT ${binaryDir}/config.h CONTENT "${config}\n") + endfunction() +endif() + +################################################################################ +# Embed component configuration. +################################################################################ + +if("Embed" IN_LIST PHP_FIND_COMPONENTS AND NOT TARGET PHP::Embed) + if(IS_ABSOLUTE "${PHP_EMBED_LIBRARY}") + add_library(PHP::Embed UNKNOWN IMPORTED) + set_target_properties( + PHP::Embed + PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES C + IMPORTED_LOCATION "${PHP_EMBED_LIBRARY}" + ) + else() + add_library(PHP::Embed INTERFACE IMPORTED) + set_target_properties( + PHP::Embed + PROPERTIES + IMPORTED_LIBNAME "${PHP_EMBED_LIBRARY}" + ) + endif() + + set_target_properties( + PHP::Embed + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PHP_INCLUDE_DIRS}" + ) +endif() diff --git a/cmake/ext/skeleton/CMakeLists.txt.in b/cmake/ext/skeleton/CMakeLists.txt.in index 1a6a3bf9..a4d879ca 100644 --- a/cmake/ext/skeleton/CMakeLists.txt.in +++ b/cmake/ext/skeleton/CMakeLists.txt.in @@ -26,9 +26,15 @@ cmake_minimum_required(VERSION 3.25...3.31) # Add paths where include() and find_package() look for modules. list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/modules) +# Automatically include current source or build tree for the current target. +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Put the source or build tree include directories before other includes. +set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) + # Define the PHP extension project and its metadata. project( - %EXTNAME% + php_ext_%EXTNAME% VERSION 0.1.0 DESCRIPTION "Describe the extension" HOMEPAGE_URL "https://example.org" @@ -41,9 +47,12 @@ include(CMakeDependentOption) # Include CMake module to use add_feature_info() and set_package_properties(). include(FeatureSummary) +# Set GNU standard installation directories. +include(GNUInstallDirs) + # Boolean option that enables (ON) or disables (OFF) the extension. For example, # cmake -B -D PHP_EXT_%EXTNAMECAPS%=ON -option(PHP_EXT_%EXTNAMECAPS% "Enable the %EXTNAME% extension") +option(PHP_EXT_%EXTNAMECAPS% "Enable the %EXTNAME% extension" ${PROJECT_IS_TOP_LEVEL}) # Extension features are visible in the configuration summary output. add_feature_info( @@ -57,7 +66,7 @@ add_feature_info( cmake_dependent_option( PHP_EXT_%EXTNAMECAPS%_SHARED "Build the %EXTNAME% extension as a shared library" - OFF + ${PROJECT_IS_TOP_LEVEL} "PHP_EXT_%EXTNAMECAPS%;NOT BUILD_SHARED_LIBS" OFF ) @@ -105,12 +114,12 @@ target_compile_definitions( ZEND_ENABLE_STATIC_TSRMLS_CACHE ) -# Find PHP package on the system. +# Find PHP on the system. find_package(PHP REQUIRED) -# The PHP::PHP library contains INTERFACE include directories for the extension. -# Link PHP and extension targets. -target_link_libraries(php_ext_%EXTNAME% PRIVATE PHP::PHP) +# Link the PHP::Development imported library which provides the PHP include +# directories and configuration for the extension. +target_link_libraries(php_ext_%EXTNAME% PRIVATE PHP::Development) # Install files to system destinations when running 'cmake --install'. install( diff --git a/cmake/ext/skeleton/cmake/Bootstrap.cmake b/cmake/ext/skeleton/cmake/Bootstrap.cmake deleted file mode 100644 index ade252b9..00000000 --- a/cmake/ext/skeleton/cmake/Bootstrap.cmake +++ /dev/null @@ -1,10 +0,0 @@ -# Set GNU standard installation directories. -include(GNUInstallDirs) - -# Run at the end of the extension's configuration phase. -cmake_language(DEFER DIRECTORY ${CMAKE_SOURCE_DIR} CALL _php_hook_end_of_configure()) -function(_php_hook_end_of_configure) - message(STATUS "********PHP Bootstrap********") - message(STATUS "This is executed at the end of the extension's configuration.") - message(STATUS "********PHP Bootstrap********") -endfunction() diff --git a/cmake/ext/skeleton/cmake/modules/FindPHP.cmake b/cmake/ext/skeleton/cmake/modules/FindPHP.cmake index c13fc516..e69de29b 100644 --- a/cmake/ext/skeleton/cmake/modules/FindPHP.cmake +++ b/cmake/ext/skeleton/cmake/modules/FindPHP.cmake @@ -1,300 +0,0 @@ -#[=============================================================================[ -# FindPHP - -Find PHP. - -Components: - -* `php` - The PHP, general-purpose scripting language, component for building - extensions. -* `embed` - The PHP Embed SAPI component - A lightweight SAPI to embed PHP into - application using C bindings. - -Module defines the following `IMPORTED` target(s): - -* `PHP::php` - The PHP package `IMPORTED` target, if found. -* `PHP::embed` - The PHP embed SAPI, if found. - -## Result variables - -* `PHP_FOUND` - Whether the package has been found. -* `PHP_INCLUDE_DIRS` - Include directories needed to use this package. -* `PHP_LIBRARIES` - Libraries needed to link to the package library. -* `PHP_VERSION` - Package version, if found. -* `PHP_INSTALL_INCLUDEDIR` - Relative path to the `CMAKE_PREFIX_INSTALL` - containing PHP headers. -* `PHP_EXTENSION_DIR` - Path to the directory where shared extensions are - installed. -* `PHP_API_VERSION` - Internal PHP API version number (`PHP_API_VERSION` in - `
`). -* `PHP_ZEND_MODULE_API` - Internal API version number for PHP extensions - (`ZEND_MODULE_API_NO` in ``). These are most common PHP - extensions either built-in or loaded dynamically with the `extension` INI - directive. -* `PHP_ZEND_EXTENSION_API` - Internal API version number for Zend extensions - (`ZEND_EXTENSION_API_NO` in ``). Zend extensions are, - for example, opcache, debuggers, profilers and similar advanced extensions. - They are either built-in or dynamically loaded with the `zend_extension` INI - directive. - -## Cache variables - -* `PHP_CONFIG_EXECUTABLE` - Path to the php-config development helper tool. -* `PHP_INCLUDE_DIR` - Directory containing PHP headers. -* `PHP_EMBED_LIBRARY` - The path to the PHP Embed library. -* `PHP_EMBED_INCLUDE_DIR` - Directory containing PHP Embed header(s). - -Basic usage: - -```cmake -# Find PHP -find_package(PHP) - -# Find PHP embed component -find_package(PHP COMPONENTS embed) - -# Override where to find PHP -set(PHP_ROOT /path/to/php/installation) -find_package(PHP) -``` -#]=============================================================================] - -include(FeatureSummary) -include(FindPackageHandleStandardArgs) - -set_package_properties( - PHP - PROPERTIES - URL "https://www.php.net" - DESCRIPTION "General-purpose scripting language" -) - -set(_reason "") - -################################################################################ -# php-config -################################################################################ - -# Find php-config tool. -find_program( - PHP_CONFIG_EXECUTABLE - NAMES php-config - DOC "Path to the php-config development helper command-line tool" -) - -################################################################################ -# The PHP component. -################################################################################ - -# Try pkg-config. -find_package(PkgConfig QUIET) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_PHP QUIET php) -endif() - -# Get PHP include directories. -if(PHP_CONFIG_EXECUTABLE) - execute_process( - COMMAND "${PHP_CONFIG_EXECUTABLE}" --includes - OUTPUT_VARIABLE PHP_INCLUDE_DIRS - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - ) - - if(PHP_INCLUDE_DIRS) - separate_arguments(PHP_INCLUDE_DIRS NATIVE_COMMAND "${PHP_INCLUDE_DIRS}") - list(TRANSFORM PHP_INCLUDE_DIRS REPLACE "^-I" "") - endif() -endif() - -find_path( - PHP_INCLUDE_DIR - NAMES main/php_config.h - HINTS - ${PC_PHP_INCLUDE_DIRS} - ${PHP_INCLUDE_DIRS} - DOC "Directory containing PHP main binding headers" -) - -if(NOT PHP_INCLUDE_DIR) - string(APPEND _reason "main/php_config.h not found. ") -else() - set(PHP_php_FOUND TRUE) -endif() - -# Get relative PHP include directory path. -if(PHP_CONFIG_EXECUTABLE) - execute_process( - COMMAND "${PHP_CONFIG_EXECUTABLE}" --include-dir - OUTPUT_VARIABLE PHP_INSTALL_INCLUDEDIR - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - ) - execute_process( - COMMAND "${PHP_CONFIG_EXECUTABLE}" --prefix - OUTPUT_VARIABLE PHP_INSTALL_PREFIX - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - ) - cmake_path( - RELATIVE_PATH - PHP_INSTALL_INCLUDEDIR - BASE_DIRECTORY "${PHP_INSTALL_PREFIX}" - ) -elseif(PC_PHP_PREFIX) - cmake_path( - RELATIVE_PATH - PHP_INSTALL_INCLUDEDIR - BASE_DIRECTORY "${PHP_INSTALL_PREFIX}" - ) -endif() - -# Get PHP_EXTENSION_DIR. -if(PHP_CONFIG_EXECUTABLE) - execute_process( - COMMAND "${PHP_CONFIG_EXECUTABLE}" --extension-dir - OUTPUT_VARIABLE PHP_EXTENSION_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - ) -endif() -if(NOT PHP_EXTENSION_DIR AND PKG_CONFIG_FOUND) - pkg_get_variable(PHP_EXTENSION_DIR php extensiondir) -endif() - -################################################################################ -# PHP Embed component. -################################################################################ - -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_PHP_EMBED QUIET php-embed) -endif() - -find_library( - PHP_EMBED_LIBRARY - NAMES php - HINTS ${PC_PHP_EMBED_LIBRARY_DIRS} - DOC "The path to the libphp embed library" -) - -find_path( - PHP_EMBED_INCLUDE_DIR - NAMES sapi/embed/php_embed.h - HINTS - ${PC_PHP_EMBED_INCLUDE_DIRS} - ${PHP_INCLUDE_DIRS} - DOC "Directory containing PHP Embed SAPI header(s)" -) - -if(PHP_EMBED_LIBRARY AND PHP_EMBED_INCLUDE_DIR) - set(PHP_embed_FOUND TRUE) -elseif("embed" IN_LIST PHP_FIND_COMPONENTS) - if(NOT PHP_EMBED_INCLUDE_DIR) - string(APPEND _reason "sapi/embed/php_embed.h not found. ") - endif() - - if(NOT PHP_EMBED_LIBRARY) - string(APPEND _reason "PHP library (libphp) not found. ") - endif() -endif() - -################################################################################ -# Get PHP version and API numbers. -################################################################################ - -# Get PHP version. -block(PROPAGATE PHP_VERSION) - if(${PHP_INCLUDE_DIR}/main/php_version.h) - set(regex "^[ \t]*#[ \t]*define[ \t]+PHP_VERSION[ \t]+\"([^\"]+)\"[ \t]*$") - file( - STRINGS - ${PHP_INCLUDE_DIR}/main/php_version.h - result - REGEX "${regex}" - LIMIT_COUNT 1 - ) - if(result MATCHES "${regex}") - set(PHP_VERSION "${CMAKE_MATCH_1}") - endif() - endif() - - if( - NOT PHP_VERSION - AND PC_PHP_VERSION - AND PHP_INCLUDE_DIR IN_LIST PC_PHP_INCLUDE_DIRS - ) - set(PHP_VERSION ${PC_PHP_VERSION}) - endif() -endblock() - -# Get PHP API version number. -file(READ ${PHP_INCLUDE_DIR}/main/php.h _) -string(REGEX MATCH "#[ \t]*define[ \t]+PHP_API_VERSION[ \t]+([0-9]+)" _ "${_}") -set(PHP_API_VERSION "${CMAKE_MATCH_1}") - -# Get PHP extensions API version number. -file(READ ${PHP_INCLUDE_DIR}/Zend/zend_modules.h _) -string(REGEX MATCH "#[ \t]*define[ \t]+ZEND_MODULE_API_NO[ \t]+([0-9]+)" _ "${_}") -set(PHP_ZEND_MODULE_API "${CMAKE_MATCH_1}") - -# Get Zend extensions API version number. -file(READ ${PHP_INCLUDE_DIR}/Zend/zend_extensions.h _) -string(REGEX MATCH "#[ \t]*define[ \t]+ZEND_EXTENSION_API_NO[ \t]+([0-9]+)" _ "${_}") -set(PHP_ZEND_EXTENSION_API "${CMAKE_MATCH_1}") - -mark_as_advanced( - PHP_CONFIG_EXECUTABLE - PHP_INCLUDE_DIR - PHP_EMBED_LIBRARY - PHP_EMBED_INCLUDE_DIR -) - -################################################################################ -# Handle package standard arguments. -################################################################################ - -find_package_handle_standard_args( - PHP - REQUIRED_VARS - PHP_INCLUDE_DIR - PHP_API_VERSION - PHP_ZEND_MODULE_API - PHP_ZEND_EXTENSION_API - VERSION_VAR PHP_VERSION - HANDLE_VERSION_RANGE - HANDLE_COMPONENTS - REASON_FAILURE_MESSAGE "${_reason}" -) - -unset(_reason) - -if(NOT PHP_FOUND) - return() -endif() - -################################################################################ -# Post-find configuration. -################################################################################ - -set(PHP_LIBRARIES ${PHP_EMBED_LIBRARY}) - -if(NOT TARGET PHP::PHP) - add_library(PHP::PHP INTERFACE IMPORTED) - - set_target_properties( - PHP::PHP - PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${PHP_INCLUDE_DIRS}" - ) -endif() - -if(PHP_embed_FOUND AND NOT TARGET PHP::EMBED) - add_library(PHP::EMBED UNKNOWN IMPORTED) - - set_target_properties( - PHP::EMBED - PROPERTIES - IMPORTED_LOCATION "${PHP_EMBED_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${PHP_EMBED_INCLUDE_DIR}" - ) -endif() From 748e2e3d30d7e04363cf8b8627bcba102cd355b4 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Sun, 12 Jan 2025 08:44:54 +0100 Subject: [PATCH 3/6] Moving on --- cmake/cmake/modules/FindPHP.cmake | 49 +++++++++++++++++++--------- cmake/ext/skeleton/CMakeLists.txt.in | 2 +- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/cmake/cmake/modules/FindPHP.cmake b/cmake/cmake/modules/FindPHP.cmake index 3476d40c..399937cc 100644 --- a/cmake/cmake/modules/FindPHP.cmake +++ b/cmake/cmake/modules/FindPHP.cmake @@ -40,6 +40,10 @@ If the `COMPONENTS` are not specified, module by default searches for the find_package(PHP) ``` +The imported targets defined by components are only created when the +`CMAKE_ROLE` global property is `PROJECT`, which enables finding PHP, for +example, in command-line scripts. + ### The `Interpreter` component When this component is found, it defines the following `IMPORTED` targets: @@ -67,7 +71,7 @@ Cache variables of the `Development` component: * `PHP_CONFIG_EXECUTABLE` - Path to the `php-config` development command-line tool. -* `PHP_INCLUDE_DIR` - Directory containing PHP headers. +* `PHP_Development_INCLUDE_DIR` - Directory containing PHP headers. Result variables of the `Development` component: @@ -437,16 +441,16 @@ endif() if("Development" IN_LIST PHP_FIND_COMPONENTS) find_path( - PHP_INCLUDE_DIR + PHP_Development_INCLUDE_DIR NAMES main/php.h HINTS ${PC_PHP_INCLUDE_DIRS} ${PHP_INCLUDE_DIRS} DOC "Directory containing PHP headers" ) - mark_as_advanced(PHP_INCLUDE_DIR) + mark_as_advanced(PHP_Development_INCLUDE_DIR) - if(NOT PHP_INCLUDE_DIR) + if(NOT PHP_Development_INCLUDE_DIR) string(APPEND _reason "The
header file not found. ") endif() @@ -465,9 +469,9 @@ if("Development" IN_LIST PHP_FIND_COMPONENTS) # Get PHP API version number. block(PROPAGATE PHP_API_VERSION) - if(EXISTS ${PHP_INCLUDE_DIR}/main/php.h) + if(EXISTS ${PHP_Development_INCLUDE_DIR}/main/php.h) set(regex "#[ \t]*define[ \t]+PHP_API_VERSION[ \t]+([0-9]+)") - file(STRINGS ${PHP_INCLUDE_DIR}/main/php.h _ REGEX "${regex}") + file(STRINGS ${PHP_Development_INCLUDE_DIR}/main/php.h _ REGEX "${regex}") if(CMAKE_VERSION VERSION_LESS 3.29) string(REGEX MATCH "${regex}" _ "${_}") @@ -479,9 +483,9 @@ if("Development" IN_LIST PHP_FIND_COMPONENTS) # Get PHP extensions API version number. block(PROPAGATE PHP_ZEND_MODULE_API) - if(EXISTS ${PHP_INCLUDE_DIR}/Zend/zend_modules.h) + if(EXISTS ${PHP_Development_INCLUDE_DIR}/Zend/zend_modules.h) set(regex "#[ \t]*define[ \t]+ZEND_MODULE_API_NO[ \t]+([0-9]+)") - file(STRINGS ${PHP_INCLUDE_DIR}/Zend/zend_modules.h _ REGEX "${regex}") + file(STRINGS ${PHP_Development_INCLUDE_DIR}/Zend/zend_modules.h _ REGEX "${regex}") if(CMAKE_VERSION VERSION_LESS 3.29) string(REGEX MATCH "${regex}" _ "${_}") @@ -493,9 +497,9 @@ if("Development" IN_LIST PHP_FIND_COMPONENTS) # Get Zend extensions API version number. block(PROPAGATE PHP_ZEND_EXTENSION_API) - if(EXISTS ${PHP_INCLUDE_DIR}/Zend/zend_extensions.h) + if(EXISTS ${PHP_Development_INCLUDE_DIR}/Zend/zend_extensions.h) set(regex "#[ \t]*define[ \t]+ZEND_EXTENSION_API_NO[ \t]+([0-9]+)") - file(STRINGS ${PHP_INCLUDE_DIR}/Zend/zend_extensions.h _ REGEX "${regex}") + file(STRINGS ${PHP_Development_INCLUDE_DIR}/Zend/zend_extensions.h _ REGEX "${regex}") if(CMAKE_VERSION VERSION_LESS 3.29) string(REGEX MATCH "${regex}" _ "${_}") @@ -539,7 +543,7 @@ if("Development" IN_LIST PHP_FIND_COMPONENTS) var IN ITEMS PHP_API_VERSION PHP_EXTENSION_DIR - PHP_INCLUDE_DIR + PHP_Development_INCLUDE_DIR PHP_ZEND_EXTENSION_API PHP_ZEND_MODULE_API ) @@ -574,6 +578,7 @@ if("Embed" IN_LIST PHP_FIND_COMPONENTS) string(APPEND _reason "The header file not found. ") endif() + # TODO: When CMAKE_ROLE is not PROJECT, CMake doesn't set search paths. find_library( PHP_EMBED_LIBRARY NAMES php @@ -653,8 +658,8 @@ block(PROPAGATE PHP_FOUND_VERSION _reason) endif() set(includeDir "") - if(NOT PHP_FOUND_VERSION AND PHP_INCLUDE_DIR) - set(includeDir ${PHP_INCLUDE_DIR}) + if(NOT PHP_FOUND_VERSION AND PHP_Development_INCLUDE_DIR) + set(includeDir ${PHP_Development_INCLUDE_DIR}) elseif(NOT PHP_FOUND_VERSION AND PHP_EMBED_INCLUDE_DIR) set(includeDir ${PHP_EMBED_INCLUDE_DIR}) endif() @@ -732,11 +737,17 @@ if(NOT PHP_FOUND) return() endif() +get_property(_phpRole GLOBAL PROPERTY CMAKE_ROLE) + ################################################################################ # Interpreter component configuration. ################################################################################ -if("Interpreter" IN_LIST PHP_FIND_COMPONENTS AND NOT TARGET PHP::Interpreter) +if( + _phpRole STREQUAL "PROJECT" + AND "Interpreter" IN_LIST PHP_FIND_COMPONENTS + AND NOT TARGET PHP::Interpreter +) add_executable(PHP::Interpreter IMPORTED) set_target_properties( PHP::Interpreter @@ -749,7 +760,7 @@ endif() # Development component configuration. ################################################################################ -if("Development" IN_LIST PHP_FIND_COMPONENTS) +if(_phpRole STREQUAL "PROJECT" AND "Development" IN_LIST PHP_FIND_COMPONENTS) if(NOT TARGET PHP::Development) add_library(PHP::Development INTERFACE IMPORTED) @@ -848,7 +859,11 @@ endif() # Embed component configuration. ################################################################################ -if("Embed" IN_LIST PHP_FIND_COMPONENTS AND NOT TARGET PHP::Embed) +if( + _phpRole STREQUAL "PROJECT" + AND "Embed" IN_LIST PHP_FIND_COMPONENTS + AND NOT TARGET PHP::Embed +) if(IS_ABSOLUTE "${PHP_EMBED_LIBRARY}") add_library(PHP::Embed UNKNOWN IMPORTED) set_target_properties( @@ -872,3 +887,5 @@ if("Embed" IN_LIST PHP_FIND_COMPONENTS AND NOT TARGET PHP::Embed) INTERFACE_INCLUDE_DIRECTORIES "${PHP_INCLUDE_DIRS}" ) endif() + +unset(_phpRole) diff --git a/cmake/ext/skeleton/CMakeLists.txt.in b/cmake/ext/skeleton/CMakeLists.txt.in index a4d879ca..b8b374b1 100644 --- a/cmake/ext/skeleton/CMakeLists.txt.in +++ b/cmake/ext/skeleton/CMakeLists.txt.in @@ -47,7 +47,7 @@ include(CMakeDependentOption) # Include CMake module to use add_feature_info() and set_package_properties(). include(FeatureSummary) -# Set GNU standard installation directories. +# Define GNU standard installation directories. include(GNUInstallDirs) # Boolean option that enables (ON) or disables (OFF) the extension. For example, From 7336c043a83503ace3694975a6b8e6e3d6dc1f7b Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Mon, 13 Jan 2025 17:18:53 +0100 Subject: [PATCH 4/6] Fix .github files --- .github/workflows/ci.yaml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0dbecb38..7fc4e212 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -141,16 +141,17 @@ jobs: - name: Install PHP run: | - cd php-${{ matrix.php }} cmake --install php-build/all-enabled - name: Setup shared standalone extension run: | - cd php-${{ matrix.php }} - php ext/ext_skel.php --ext phantom - mkdir ext/phantom/cmake/modules - cp cmake/modules/FindPHP.cmake ext/phantom/cmake/modules - cmake -S ext/phantom -B ext/phantom/cmake-build - cmake --build ext/phantom/cmake-build -j - cmake --install ext/phantom/cmake-build - #php -d extension=phantom -m | grep phantom + php php-build/all-enabled/php-src/ext/ext_skel.php --ext phantom + mkdir -p php-build/all-enabled/php-src/ext/phantom/cmake/modules + cp cmake/cmake/modules/FindPHP.cmake \ + php-build/all-enabled/php-src/ext/phantom/cmake/modules + cmake \ + -S php-build/all-enabled/php-src/ext/phantom \ + -B php-build/all-enabled/php-src/ext/phantom/cmake-build + cmake --build php-build/all-enabled/php-src/ext/phantom/cmake-build -j + cmake --install php-build/all-enabled/php-src/ext/phantom/cmake-build + php -d extension=phantom -m | grep phantom From 5bdaa94eb54ffce8add54becb33879d6b50c9491 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Tue, 14 Jan 2025 19:27:01 +0100 Subject: [PATCH 5/6] Fix consecutive configuration phases --- cmake/cmake/modules/FindPHP.cmake | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmake/cmake/modules/FindPHP.cmake b/cmake/cmake/modules/FindPHP.cmake index 399937cc..de34d4f9 100644 --- a/cmake/cmake/modules/FindPHP.cmake +++ b/cmake/cmake/modules/FindPHP.cmake @@ -848,10 +848,11 @@ if(_phpRole STREQUAL "PROJECT" AND "Development" IN_LIST PHP_FIND_COMPONENTS) file(READ ${binaryDir}/config.h current) endif() - string(STRIP "${template}\n${current}" config) - # Finalize extension's config.h header file. - file(CONFIGURE OUTPUT ${binaryDir}/config.h CONTENT "${config}\n") + if(NOT current MATCHES "(#undef|#define) ${macro}") + string(STRIP "${template}\n${current}" config) + file(CONFIGURE OUTPUT ${binaryDir}/config.h CONTENT "${config}\n") + endif() endfunction() endif() From c97f6f4dfab2a3002f680cca32a17228a9eb9aa6 Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Wed, 15 Jan 2025 19:43:53 +0100 Subject: [PATCH 6/6] Add installation configuration This adds the config files for finding PHP when using find_package(PHP) without find module (the CONFIG mode). Further improvements and refactorings in the next commits... --- .github/workflows/ci.yaml | 12 +-- cmake/CMakeLists.txt | 3 + cmake/cmake/Requirements.cmake | 1 + cmake/cmake/installation/Install.cmake | 64 ++++++++++++++++ cmake/cmake/installation/PHPConfig.cmake.in | 49 +++++++++++++ cmake/cmake/modules/FindPHP.cmake | 7 ++ cmake/ext/bcmath/CMakeLists.txt | 6 ++ cmake/ext/intl/CMakeLists.txt | 2 + cmake/ext/skeleton/CMakeLists.txt.in | 2 +- cmake/main/CMakeLists.txt | 2 + cmake/sapi/apache2handler/CMakeLists.txt | 2 +- cmake/sapi/cgi/CMakeLists.txt | 5 +- cmake/sapi/cli/CMakeLists.txt | 81 +++++++++++++++------ cmake/sapi/embed/CMakeLists.txt | 18 ++++- cmake/sapi/fpm/CMakeLists.txt | 5 +- cmake/sapi/fuzzer/CMakeLists.txt | 2 +- cmake/sapi/litespeed/CMakeLists.txt | 5 +- cmake/sapi/phpdbg/CMakeLists.txt | 9 +-- 18 files changed, 223 insertions(+), 52 deletions(-) create mode 100644 cmake/cmake/installation/Install.cmake create mode 100644 cmake/cmake/installation/PHPConfig.cmake.in diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7fc4e212..f727c430 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -146,12 +146,8 @@ jobs: - name: Setup shared standalone extension run: | php php-build/all-enabled/php-src/ext/ext_skel.php --ext phantom - mkdir -p php-build/all-enabled/php-src/ext/phantom/cmake/modules - cp cmake/cmake/modules/FindPHP.cmake \ - php-build/all-enabled/php-src/ext/phantom/cmake/modules - cmake \ - -S php-build/all-enabled/php-src/ext/phantom \ - -B php-build/all-enabled/php-src/ext/phantom/cmake-build - cmake --build php-build/all-enabled/php-src/ext/phantom/cmake-build -j - cmake --install php-build/all-enabled/php-src/ext/phantom/cmake-build + cd php-build/all-enabled/php-src/ext/phantom + cmake -B cmake-build + cmake --build cmake-build -j + cmake --install cmake-build php -d extension=phantom -m | grep phantom diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 08e47d15..3bc0f538 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -61,6 +61,9 @@ block() endforeach() endblock() +# Configure PHP installation. +include(cmake/installation/Install.cmake) + # Rebuild all targets as needed. if(NOT PHP_FOUND) include(PHP/Rebuild) diff --git a/cmake/cmake/Requirements.cmake b/cmake/cmake/Requirements.cmake index 7698e11d..51addb09 100644 --- a/cmake/cmake/Requirements.cmake +++ b/cmake/cmake/Requirements.cmake @@ -57,3 +57,4 @@ find_package(Sendmail) # version required to run these PHP scripts. ################################################################################ find_package(PHP 7.4 COMPONENTS Interpreter) +set(CMAKE_DISABLE_FIND_PACKAGE_PHP TRUE) diff --git a/cmake/cmake/installation/Install.cmake b/cmake/cmake/installation/Install.cmake new file mode 100644 index 00000000..55cd68e3 --- /dev/null +++ b/cmake/cmake/installation/Install.cmake @@ -0,0 +1,64 @@ +include_guard(GLOBAL) + +if(NOT PROJECT_NAME STREQUAL "PHP") + message( + AUTHOR_WARNING + "${CMAKE_CURRENT_LIST_FILE} should be used in the project(PHP) scope." + ) + return() +endif() + +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + PHPConfigVersion.cmake + COMPATIBILITY AnyNewerVersion +) + +set(INSTALL_INCLUDE_DIR ${CMAKE_INSTALL_INCLUDEDIR}/${PHP_INCLUDE_PREFIX}) + +configure_package_config_file( + cmake/installation/PHPConfig.cmake.in + PHPConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PHP + PATH_VARS + INSTALL_INCLUDE_DIR +) + +add_library(php_development INTERFACE) +add_library(PHP::Development ALIAS php_development) +set_target_properties(php_development PROPERTIES EXPORT_NAME Development) +target_include_directories( + php_development + INTERFACE + $ + $ + $ + $ + $ + $ + $ + $ +) +set_target_properties(php_development PROPERTIES EXPORT_NAME Development) + +install( + TARGETS php_development + EXPORT PHP::Development +) + +install( + EXPORT PHP::Development + FILE PHP_Development.cmake + NAMESPACE PHP:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PHP + COMPONENT PHP::Development +) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/PHPConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/PHPConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PHP + COMPONENT PHP::Development +) diff --git a/cmake/cmake/installation/PHPConfig.cmake.in b/cmake/cmake/installation/PHPConfig.cmake.in new file mode 100644 index 00000000..35cff4c5 --- /dev/null +++ b/cmake/cmake/installation/PHPConfig.cmake.in @@ -0,0 +1,49 @@ +if(CMAKE_VERSION VERSION_LESS 3.25) + set( + ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE + "PHP ${${CMAKE_FIND_PACKAGE_NAME}_VERSION} requires CMake 3.25 or later." + ) + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) + return() +endif() + +cmake_minimum_required(VERSION 3.25...3.31) + +# Set PHP components. +if(${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS) + set( + ${CMAKE_FIND_PACKAGE_NAME}_components + ${${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS} + ) +else() + # No components given, look for default components. + set(${CMAKE_FIND_PACKAGE_NAME}_components Interpreter) +endif() + +# Ensure all required components are available before trying to load any. +foreach(component IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_components) + if( + ${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED_${component} + AND NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/PHP_${component}.cmake + ) + set( + ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE + "PHP doesn't have the required component installed: ${component}" + ) + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) + + return() + endif() +endforeach() + +foreach(component IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_components) + # All required components are known to exist. The OPTIONAL keyword allows the + # non-required components to be missing without error. + include(${CMAKE_CURRENT_LIST_DIR}/PHP_${component}.cmake OPTIONAL) +endforeach() + +@PACKAGE_INIT@ + +set_and_check(PHP_INCLUDE_DIR "@PACKAGE_INSTALL_INCLUDE_DIR@") + +check_required_components(${CMAKE_FIND_PACKAGE_NAME}) diff --git a/cmake/cmake/modules/FindPHP.cmake b/cmake/cmake/modules/FindPHP.cmake index de34d4f9..d8b71ff7 100644 --- a/cmake/cmake/modules/FindPHP.cmake +++ b/cmake/cmake/modules/FindPHP.cmake @@ -349,6 +349,13 @@ Run application executable: cmake_minimum_required(VERSION 3.25...3.31) +if( + CMAKE_PARENT_LIST_FILE STREQUAL "CMakeLists.txt" + AND PROJECT_NAME MATCHES "^php_ext_.+" +) + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/modules) +endif() + include(FeatureSummary) include(FindPackageHandleStandardArgs) diff --git a/cmake/ext/bcmath/CMakeLists.txt b/cmake/ext/bcmath/CMakeLists.txt index 59ad35d9..9ee6a6f6 100644 --- a/cmake/ext/bcmath/CMakeLists.txt +++ b/cmake/ext/bcmath/CMakeLists.txt @@ -20,11 +20,15 @@ Enable the extension. Build extension as shared. #]=============================================================================] +cmake_minimum_required(VERSION 3.25...3.31) + project( PhpExtensionBcMath LANGUAGES C ) +find_package(PHP COMPONENTS Development) + include(CMakeDependentOption) include(FeatureSummary) @@ -84,6 +88,8 @@ target_sources( target_compile_definitions(php_ext_bcmath PRIVATE ZEND_ENABLE_STATIC_TSRMLS_CACHE) +target_link_libraries(php_ext_bcmath PRIVATE PHP::Development) + set(HAVE_BCMATH TRUE) configure_file(cmake/config.h.in config.h) diff --git a/cmake/ext/intl/CMakeLists.txt b/cmake/ext/intl/CMakeLists.txt index 2f302d0e..0dd33713 100644 --- a/cmake/ext/intl/CMakeLists.txt +++ b/cmake/ext/intl/CMakeLists.txt @@ -21,6 +21,8 @@ Enable the extension. Build extension as shared. #]=============================================================================] +cmake_minimum_required(VERSION 3.25...3.31) + project( PhpExtensionIntl LANGUAGES C diff --git a/cmake/ext/skeleton/CMakeLists.txt.in b/cmake/ext/skeleton/CMakeLists.txt.in index b8b374b1..5c205fd7 100644 --- a/cmake/ext/skeleton/CMakeLists.txt.in +++ b/cmake/ext/skeleton/CMakeLists.txt.in @@ -115,7 +115,7 @@ target_compile_definitions( ) # Find PHP on the system. -find_package(PHP REQUIRED) +find_package(PHP REQUIRED COMPONENTS Development) # Link the PHP::Development imported library which provides the PHP include # directories and configuration for the extension. diff --git a/cmake/main/CMakeLists.txt b/cmake/main/CMakeLists.txt index 632e73d1..7706db65 100644 --- a/cmake/main/CMakeLists.txt +++ b/cmake/main/CMakeLists.txt @@ -415,6 +415,8 @@ install( ARCHIVE EXCLUDE_FROM_ALL FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PHP_INCLUDE_PREFIX}/main + COMPONENT PHP::Development FILE_SET generated DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PHP_INCLUDE_PREFIX}/main + COMPONENT PHP::Development ) diff --git a/cmake/sapi/apache2handler/CMakeLists.txt b/cmake/sapi/apache2handler/CMakeLists.txt index 846ed801..468f962e 100644 --- a/cmake/sapi/apache2handler/CMakeLists.txt +++ b/cmake/sapi/apache2handler/CMakeLists.txt @@ -78,7 +78,7 @@ set_package_properties( target_link_libraries( php_sapi_apache2handler PRIVATE - PHP::sapi + $ Apache::Apache ) diff --git a/cmake/sapi/cgi/CMakeLists.txt b/cmake/sapi/cgi/CMakeLists.txt index 5c1933a9..67f0f16b 100644 --- a/cmake/sapi/cgi/CMakeLists.txt +++ b/cmake/sapi/cgi/CMakeLists.txt @@ -41,7 +41,7 @@ target_compile_definitions(php_sapi_cgi PRIVATE ZEND_ENABLE_STATIC_TSRMLS_CACHE) target_link_libraries( php_sapi_cgi PRIVATE - PHP::sapi + $ $<$:ws2_32;kernel32;advapi32> ) @@ -55,8 +55,7 @@ set_target_properties( php_sapi_cgi PROPERTIES OUTPUT_NAME ${PHP_PROGRAM_PREFIX}php-cgi${PHP_PROGRAM_SUFFIX} - # TODO: Check if there's a better solution here: - ENABLE_EXPORTS TRUE + ENABLE_EXPORTS TRUE # TODO: Check if there's a better solution. PHP_CLI TRUE ) diff --git a/cmake/sapi/cli/CMakeLists.txt b/cmake/sapi/cli/CMakeLists.txt index 1b4523ca..27d9cf07 100644 --- a/cmake/sapi/cli/CMakeLists.txt +++ b/cmake/sapi/cli/CMakeLists.txt @@ -52,6 +52,10 @@ if(NOT PHP_SAPI_CLI) return() endif() +################################################################################ +# Configuration checks. +################################################################################ + check_symbol_exists(setproctitle "unistd.h;stdlib.h" HAVE_SETPROCTITLE) check_include_file(sys/pstat.h HAVE_SYS_PSTAT_H) @@ -74,6 +78,10 @@ else() message(CHECK_FAIL "no") endif() +################################################################################ +# The cli SAPI. +################################################################################ + add_executable(php_sapi_cli) add_executable(PHP::sapi::cli ALIAS php_sapi_cli) @@ -102,7 +110,7 @@ target_compile_definitions( target_link_libraries( php_sapi_cli PRIVATE - PHP::sapi + $ $<$:ws2_32;shell32> ) @@ -116,8 +124,8 @@ set_target_properties( php_sapi_cli PROPERTIES OUTPUT_NAME ${PHP_PROGRAM_PREFIX}php${PHP_PROGRAM_SUFFIX} - # TODO: Check if there's a better solution here: - ENABLE_EXPORTS TRUE + ENABLE_EXPORTS TRUE # TODO: Check if there's a better solution. + EXPORT_NAME Interpreter PHP_CLI TRUE ) @@ -130,6 +138,42 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows") ) endif() +# Man documentation. +block() + set(program_prefix "${PHP_PROGRAM_PREFIX}") + configure_file(php.1.in php.1 @ONLY) +endblock() + +install( + TARGETS php_sapi_cli + EXPORT PHP::Interpreter + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT PHP::Interpreter + FILE_SET HEADERS + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PHP_INCLUDE_PREFIX}/sapi/cli + COMPONENT PHP::Interpreter +) + +install( + EXPORT PHP::Interpreter + FILE PHP_Interpreter.cmake + NAMESPACE PHP:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PHP + COMPONENT PHP::Interpreter +) + +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/php.1 + RENAME ${PHP_PROGRAM_PREFIX}php${PHP_PROGRAM_SUFFIX}.1 + DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 + COMPONENT PHP::Interpreter +) + +################################################################################ +# The cli SAPI without console on Windows. +################################################################################ + if(PHP_SAPI_CLI_WIN_NO_CONSOLE) add_executable(php_sapi_cli_win) add_executable(PHP::sapi::cli_win ALIAS php_sapi_cli_win) @@ -147,6 +191,7 @@ if(PHP_SAPI_CLI_WIN_NO_CONSOLE) php_sapi_cli_win PROPERTIES OUTPUT_NAME ${PHP_PROGRAM_PREFIX}php-win${PHP_PROGRAM_SUFFIX} + EXPORT_NAME InterpreterNoConsole ) target_compile_definitions( @@ -158,7 +203,7 @@ if(PHP_SAPI_CLI_WIN_NO_CONSOLE) target_link_libraries( php_sapi_cli_win PRIVATE - PHP::sapi + $ shell32 ) @@ -167,26 +212,14 @@ if(PHP_SAPI_CLI_WIN_NO_CONSOLE) PRIVATE /stack:67108864 ) -endif() - -# Man documentation. -block() - set(program_prefix "${PHP_PROGRAM_PREFIX}") - configure_file(php.1.in php.1 @ONLY) -endblock() - -install( - TARGETS php_sapi_cli - RUNTIME - DESTINATION ${CMAKE_INSTALL_BINDIR} - FILE_SET HEADERS - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PHP_INCLUDE_PREFIX}/sapi/cli -) -install( - FILES ${CMAKE_CURRENT_BINARY_DIR}/php.1 - RENAME ${PHP_PROGRAM_PREFIX}php${PHP_PROGRAM_SUFFIX}.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 -) + install( + TARGETS php_sapi_cli_win + EXPORT PHP::Interpreter + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT PHP::Interpreter + ) +endif() configure_file(cmake/config.h.in config.h) diff --git a/cmake/sapi/embed/CMakeLists.txt b/cmake/sapi/embed/CMakeLists.txt index 1584d9ab..683cbcae 100644 --- a/cmake/sapi/embed/CMakeLists.txt +++ b/cmake/sapi/embed/CMakeLists.txt @@ -47,7 +47,7 @@ target_sources( target_link_libraries( php_sapi_embed PRIVATE - PHP::sapi + $ ) target_compile_definitions(php_sapi_embed PRIVATE ZEND_ENABLE_STATIC_TSRMLS_CACHE) @@ -56,8 +56,8 @@ set_target_properties( php_sapi_embed PROPERTIES OUTPUT_NAME libphp - # TODO: Check if there's a better solution here: - ENABLE_EXPORTS TRUE + ENABLE_EXPORTS TRUE # TODO: Check if there's a better solution. + EXPORT_NAME Embed PHP_CLI TRUE ) @@ -85,15 +85,27 @@ pkgconfig_generate_pc( install( TARGETS php_sapi_embed + EXPORT PHP::Embed RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT PHP::Embed LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT PHP::Embed FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PHP_INCLUDE_PREFIX}/sapi/embed ) +install( + EXPORT PHP::Embed + FILE PHP_Embed.cmake + NAMESPACE PHP:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PHP + COMPONENT PHP::Embed +) + install( FILES ${CMAKE_CURRENT_BINARY_DIR}/php-embed.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + COMPONENT PHP::Embed ) diff --git a/cmake/sapi/fpm/CMakeLists.txt b/cmake/sapi/fpm/CMakeLists.txt index 29376e50..1f5dd06e 100644 --- a/cmake/sapi/fpm/CMakeLists.txt +++ b/cmake/sapi/fpm/CMakeLists.txt @@ -235,15 +235,14 @@ target_compile_definitions(php_sapi_fpm PRIVATE ZEND_ENABLE_STATIC_TSRMLS_CACHE) target_link_libraries( php_sapi_fpm PRIVATE - PHP::sapi + $ ) set_target_properties( php_sapi_fpm PROPERTIES OUTPUT_NAME ${PHP_PROGRAM_PREFIX}php-fpm${PHP_PROGRAM_SUFFIX} - # TODO: Check if there's a better solution here: - ENABLE_EXPORTS TRUE + ENABLE_EXPORTS TRUE # TODO: Check if there's a better solution. ) ################################################################################ diff --git a/cmake/sapi/fuzzer/CMakeLists.txt b/cmake/sapi/fuzzer/CMakeLists.txt index 8393dd51..d435c5cc 100644 --- a/cmake/sapi/fuzzer/CMakeLists.txt +++ b/cmake/sapi/fuzzer/CMakeLists.txt @@ -139,7 +139,7 @@ set_target_properties( target_link_libraries( php_sapi_fuzzer PRIVATE - PHP::sapi + $ ) install(TARGETS php_sapi_fuzzer RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/cmake/sapi/litespeed/CMakeLists.txt b/cmake/sapi/litespeed/CMakeLists.txt index 412a5125..bf7dd428 100644 --- a/cmake/sapi/litespeed/CMakeLists.txt +++ b/cmake/sapi/litespeed/CMakeLists.txt @@ -46,15 +46,14 @@ target_sourceS( target_link_libraries( php_sapi_litespeed PRIVATE - PHP::sapi + $ ) set_target_properties( php_sapi_litespeed PROPERTIES OUTPUT_NAME ${PHP_PROGRAM_PREFIX}lsphp${PHP_PROGRAM_SUFFIX} - # TODO: Check if there's a better solution here. - ENABLE_EXPORTS TRUE + ENABLE_EXPORTS TRUE # TODO: Check if there's a better solution. ) install( diff --git a/cmake/sapi/phpdbg/CMakeLists.txt b/cmake/sapi/phpdbg/CMakeLists.txt index 6e1a495c..c1a586bd 100644 --- a/cmake/sapi/phpdbg/CMakeLists.txt +++ b/cmake/sapi/phpdbg/CMakeLists.txt @@ -138,7 +138,7 @@ target_compile_definitions( target_link_libraries( php_sapi_phpdbg PRIVATE - PHP::sapi + $ $<$:ws2_32;user32> ) @@ -152,8 +152,7 @@ set_target_properties( php_sapi_phpdbg PROPERTIES OUTPUT_NAME ${PHP_PROGRAM_PREFIX}phpdbg${PHP_PROGRAM_SUFFIX} - # TODO: Check if there's a better solution here: - ENABLE_EXPORTS TRUE + ENABLE_EXPORTS TRUE # TODO: Check if there's a better solution. PHP_CLI TRUE ) @@ -216,6 +215,7 @@ add_feature_info( ################################################################################ # TODO: Should readline support be also enabled like in the executable? +# TODO: Fix this better in the future (building with -fPIC etc). if(PHP_SAPI_PHPDBG_SHARED) add_library(php_sapi_phpdbg_shared SHARED) add_library(PHP::sapi::phpdbg_shared ALIAS php_sapi_phpdbg_shared) @@ -235,8 +235,7 @@ if(PHP_SAPI_PHPDBG_SHARED) target_link_libraries( php_sapi_phpdbg_shared PRIVATE - # TODO: fix this better in the future (building with -fPIC etc). - PHP::sapi + $ $<$:ws2_32;user32> ) endif()