PHPエクステンションをCMakeする

# PHP extensionをCMakeを使ってビルドする

## 構成
```
sample/
    CMakeLists.txt
    config.cmake
    config.h.in
    php_sample.h
    sample.c
    test.php
```

## 各ファイル

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.5.1)

project(sample-ext)

set(CMAKE_C_STANDARD_REQUIRED ON)

set(CMAKE_C_FLAGS "-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 -DPHP_ATOM_INC -DHAVE_CONFIG_H -O3 -funroll-loops")

execute_process(COMMAND "php-config" "--prefix" OUTPUT_VARIABLE PHP_BASE_PATH OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND "php-config" "--include-dir" OUTPUT_VARIABLE PHP_INCLUDE_PATH OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND "php-config" "--extension-dir" OUTPUT_VARIABLE EXTENSION_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)

set(COMPILE_DL_SAMPLE 1)
set(HAVE_SAMPLE 1)
include(config.cmake)

include_directories(
    "${PHP_INCLUDE_PATH}"
    "${PHP_INCLUDE_PATH}/main"
    "${PHP_INCLUDE_PATH}/TSRM"
    "${PHP_INCLUDE_PATH}/Zend"
    "${PHP_INCLUDE_PATH}/ext"
    "${PHP_INCLUDE_PATH}/ext/date/lib"
    ${CMAKE_BINARY_DIR})

add_library(sample-shared SHARED 
	${CMAKE_CURRENT_SOURCE_DIR}/sample.c)

set_target_properties(sample-shared PROPERTIES LINK_FLAGS "-Wl,-rpath,${PHP_BASE_PATH}/lib")
set_target_properties(sample-shared PROPERTIES PREFIX "" OUTPUT_NAME sample) 

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sample.so 
    DESTINATION ${EXTENSION_DIR}
    PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE 
    GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
```

```cmake:config.cmake
find_path(DLFCN_H_PATH dlfcn.h PATHS /usr /usr/local PATH_SUFFIXES include)
find_path(INTTYPES_H_PATH inttypes.h PATHS /usr /usr/local PATH_SUFFIXES include)
find_path(MEMORY_H_PATH memory.h PATHS /usr /usr/local PATH_SUFFIXES include)
find_path(STDINT_H_PATH stdint.h PATHS /usr /usr/local PATH_SUFFIXES include)
find_path(STDLIB_H_PATH stdlib.h PATHS /usr /usr/local PATH_SUFFIXES include)
find_path(STRINGS_H_PATH strings.h PATHS /usr /usr/local PATH_SUFFIXES include)
find_path(STRING_H_PATH string.h PATHS /usr /usr/local PATH_SUFFIXES include)
find_path(SYS_STAT_H_PATH sys/stat.h PATHS /usr /usr/local PATH_SUFFIXES include)
find_path(SYS_TYPES_H_PATH sys/types.h PATHS /usr /usr/local PATH_SUFFIXES include)
find_path(UNISTD_H_PATH unistd.h PATHS /usr /usr/local PATH_SUFFIXES include)

if (DLFCN_H_PATH)
    set(HAVE_DLFCN_H 1)
endif (DLFCN_H_PATH)

if (INTTYPES_H_PATH)
    set(HAVE_INTTYPES_H 1)
endif (INTTYPES_H_PATH)

if (MEMORY_H_PATH)
    set(HAVE_MEMORY_H 1)
endif (MEMORY_H_PATH)

if (STDINT_H_PATH)
    set(HAVE_STDINT_H 1)
endif (STDINT_H_PATH)

if (STDLIB_H_PATH)
    set(HAVE_STDLIB_H 1)
endif (STDLIB_H_PATH)

if (STRINGS_H_PATH)
    set(HAVE_STRINGS_H 1)
endif (STRINGS_H_PATH)

if (STRING_H_PATH)
    set(HAVE_STRING_H 1)
endif (STRING_H_PATH)

if (SYS_STAT_H_PATH)
    set(HAVE_SYS_STAT_H 1)
endif (SYS_STAT_H_PATH)

if (SYS_TYPES_H_PATH)
    set(HAVE_SYS_TYPES_H 1)
endif (SYS_TYPES_H_PATH)

if (UNISTD_H_PATH)
    set(HAVE_UNISTD_H 1)
endif (UNISTD_H_PATH)


set(PACKAGE_BUGREPORT "")
set(PACKAGE_NAME "")
set(PACKAGE_STRING "")
set(PACKAGE_TARNAME "")
set(PACKAGE_URL "")
set(PACKAGE_VERSION "")

if (DLFCN_H_PATH AND
    INTTYPES_H_PATH AND
    MEMORY_H_PATH AND
    STDINT_H_PATH AND
    STDLIB_H_PATH AND
    STRINGS_H_PATH AND
    STRING_H_PATH AND
    SYS_STAT_H_PATH AND
    SYS_TYPES_H_PATH AND
    UNISTD_H_PATH)
    set(STDC_HEADERS 1)
endif (DLFCN_H_PATH AND
    INTTYPES_H_PATH AND
    MEMORY_H_PATH AND
    STDINT_H_PATH AND
    STDLIB_H_PATH AND
    STRINGS_H_PATH AND
    STRING_H_PATH AND
    SYS_STAT_H_PATH AND
    SYS_TYPES_H_PATH AND
    UNISTD_H_PATH)

configure_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
    ${CMAKE_BINARY_DIR}/config.h)
```

```c:config.h.in
#cmakedefine COMPILE_DL_SAMPLE @COMPILE_DL_SAMPLE@
#cmakedefine HAVE_DLFCN_H @HAVE_DLFCN_H@
#cmakedefine HAVE_INTTYPES_H @HAVE_INTTYPES_H@
#cmakedefine HAVE_MEMORY_H @HAVE_MEMORY_H@
#cmakedefine HAVE_SAMPLE @HAVE_SAMPLE@
#cmakedefine HAVE_STDINT_H @HAVE_STDINT_H@
#cmakedefine HAVE_STDLIB_H @HAVE_STDLIB_H@
#cmakedefine HAVE_STRINGS_H @HAVE_STRINGS_H@
#cmakedefine HAVE_STRING_H @HAVE_STRING_H@
#cmakedefine HAVE_SYS_STAT_H @HAVE_SYS_STAT_H@
#cmakedefine HAVE_SYS_TYPES_H @HAVE_SYS_TYPES_H@
#cmakedefine HAVE_UNISTD_H @HAVE_UNISTD_H@
#cmakedefine NO_MINUS_C_MINUS_O @NO_MINUS_C_MINUS_O@
#define PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@"
#define PACKAGE_NAME "@PACKAGE_NAME@"
#define PACKAGE_STRING "@PACKAGE_STRING@"
#define PACKAGE_TARNAME "@PACKAGE_TARNAME@"
#define PACKAGE_URL "@PACKAGE_URL@"
#define PACKAGE_VERSION "@PACKAGE_VERSION@"
#cmakedefine STDC_HEADERS @STDC_HEADERS@
```
上記は`phpize`と`./configure`が担当している部分ですが多分こんな感じです。
これがないとエラーになったので、`config.h`はコピペした方が良いかもしれません。

```c:php_sample.h
#ifndef PHP_SAMPLE_H
# define PHP_SAMPLE_H

extern zend_module_entry sample_module_entry;
# define phpext_sample_ptr &sample_module_entry

# define PHP_SAMPLE_VERSION "0.1.0"

# if defined(ZTS) && defined(COMPILE_DL_SAMPLE)
ZEND_TSRMLS_CACHE_EXTERN()
# endif

#endif	/* PHP_SAMPLE_H */
```
```c:sample.c
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "php.h"
#include "ext/standard/info.h"
#include "php_sample.h"

/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
	ZEND_PARSE_PARAMETERS_START(0, 0) \
	ZEND_PARSE_PARAMETERS_END()
#endif

/* {{{ void sample_test1()
 */
PHP_FUNCTION(sample_test1)
{
	ZEND_PARSE_PARAMETERS_NONE();

	php_printf("The extension %s is loaded and working!\r\n", "sample");
}
/* }}} */

/* {{{ string sample_test2( [ string $var ] )
 */
PHP_FUNCTION(sample_test2)
{
	char *var = "World";
	size_t var_len = sizeof("World") - 1;
	zend_string *retval;

	ZEND_PARSE_PARAMETERS_START(0, 1)
		Z_PARAM_OPTIONAL
		Z_PARAM_STRING(var, var_len)
	ZEND_PARSE_PARAMETERS_END();

	retval = strpprintf(0, "Hello %s", var);

	RETURN_STR(retval);
}
/* }}}*/

/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(sample)
{
#if defined(ZTS) && defined(COMPILE_DL_SAMPLE)
	ZEND_TSRMLS_CACHE_UPDATE();
#endif

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(sample)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "sample support", "enabled");
	php_info_print_table_end();
}
/* }}} */

/* {{{ arginfo
 */
ZEND_BEGIN_ARG_INFO(arginfo_sample_test1, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO(arginfo_sample_test2, 0)
	ZEND_ARG_INFO(0, str)
ZEND_END_ARG_INFO()
/* }}} */

/* {{{ sample_functions[]
 */
static const zend_function_entry sample_functions[] = {
	PHP_FE(sample_test1,		arginfo_sample_test1)
	PHP_FE(sample_test2,		arginfo_sample_test2)
	PHP_FE_END
};
/* }}} */

/* {{{ sample_module_entry
 */
zend_module_entry sample_module_entry = {
	STANDARD_MODULE_HEADER,
	"sample",					/* Extension name */
	sample_functions,			/* zend_function_entry */
	NULL,							/* PHP_MINIT - Module initialization */
	NULL,							/* PHP_MSHUTDOWN - Module shutdown */
	PHP_RINIT(sample),			/* PHP_RINIT - Request initialization */
	NULL,							/* PHP_RSHUTDOWN - Request shutdown */
	PHP_MINFO(sample),			/* PHP_MINFO - Module info */
	PHP_SAMPLE_VERSION,		/* Version */
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_SAMPLE
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(sample)
#endif
```
こちらはスケルトンそのままです。

```php:test.php
<?php
sample_test1();
```

## ビルド
```
$ mkdir build
$ cd build
$ cmake ..
$ make -j
```
## 動作確認
```
$ php -d extension=build/sample.so -f test.php
The extension sample is loaded and working!
```

## phpize
`sample.pb.cc` のようなファイル名だと`IFS`コマンドで分割している箇所でおかしくなるのでCMakeのサンプルがあったので調整してみました。
動作確認の引数をいつも忘れるので備忘録です。:relaxed: