Compare commits

...

17 Commits

Author SHA1 Message Date
crazywhalecc
97eff64b5b add micro:combine command 2023-07-20 01:15:28 +08:00
crazywhalecc
e522258693 fix libpng mac source bug 2023-07-20 01:15:05 +08:00
crazywhalecc
b84c68fe11 add doctor linux support 2023-07-17 21:44:17 +08:00
crazywhalecc
8505feaa66 fix createDir not working 2023-07-17 21:41:26 +08:00
crazywhalecc
41f49c20ff add --from-zip command to download locally 2023-07-17 21:36:50 +08:00
crazywhalecc
1912ae36e6 separate system tools list 2023-07-17 21:11:06 +08:00
crazywhalecc
08efc81cf0 add --auto-fix option 2023-07-17 21:10:49 +08:00
crazywhalecc
a3b09c69cc correct alpine build commands 2023-07-17 21:05:39 +08:00
crazywhalecc
5cf105c3a5 fix distro 2023-07-17 20:59:59 +08:00
crazywhalecc
7408781d13 add linux-header installer for alpine 2023-07-17 20:58:48 +08:00
crazywhalecc
0afc8ea2c3 add linux tool check 2023-07-17 20:31:42 +08:00
crazywhalecc
67b073776c add linux tool check 2023-07-17 20:29:02 +08:00
crazywhalecc
8b48cf7f70 add linux tool check 2023-07-17 20:28:08 +08:00
crazywhalecc
63287dd9c4 add linux tool check 2023-07-17 20:26:48 +08:00
crazywhalecc
fc7ac921e2 add document contributing guide 2023-07-17 18:39:34 +08:00
crazywhalecc
57e3193a2a add document 2023-07-17 18:01:45 +08:00
crazywhalecc
388f547878 add document 2023-07-17 17:59:44 +08:00
10 changed files with 289 additions and 118 deletions

View File

@@ -37,7 +37,11 @@ Here is the architecture support status, where `CI` represents support for GitHu
Currently supported PHP versions for compilation are: `7.4`, `8.0`, `8.1`, `8.2`.
## Usage
## Docs
docs here: <https://static-php-cli.zhamao.me>.
## Simple Usage
Please first select the extension you want to compile based on the extension list below.
@@ -49,7 +53,7 @@ If you don't compile yourself, you can download pre-compiled artifact from Actio
### Supported Extensions
[Supported Extension List](/ext-support.md)
[Supported Extension List](https://static-php-cli.zhamao.me/en/guide/extensions.html)
> If there is no extension you need here, you can submit an issue.
@@ -100,7 +104,7 @@ Basic usage for building php and micro with some extensions:
cd static-php-cli
composer update
chmod +x bin/spc
# Check system tool dependencies, fix them automatically (only support macOS) (TODO: Linux distro support)
# Check system tool dependencies, fix them automatically
./bin/spc doctor
# fetch all libraries
./bin/spc fetch --all
@@ -217,6 +221,8 @@ The basic principles for contributing are as follows:
camelCase and underscore formats should be followed, and mixing within the same module is prohibited.
- When compiling external libraries and creating patches, compatibility with different operating systems should be considered.
If you want to contribute document content, please go to [crazywhalecc/static-php-cli-docs](https://github.com/crazywhalecc/static-php-cli-docs).
## Sponsor this project
You can sponsor my project on [this page](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md).

View File

@@ -36,6 +36,10 @@ If you are using English, see [English README](README-en.md).
目前支持编译的 PHP 版本为:`7.4``8.0``8.1``8.2`
## 文档
点击这里查看文档:<https://static-php-cli.zhamao.me>。
## 使用
请先根据下方扩展列表选择你要编译的扩展。
@@ -48,7 +52,7 @@ If you are using English, see [English README](README-en.md).
### 支持的扩展情况
[扩展支持列表](/ext-support.md)
[扩展支持列表](https://static-php-cli.zhamao.me/zh/guide/extensions.html)
> 如果这里没有你需要的扩展,可以提交 Issue。
@@ -94,7 +98,7 @@ chmod +x bin/setup-runtime
cd static-php-cli
composer update
chmod +x bin/spc
# 检查环境依赖,并根据提示的命令安装缺失的编译工具(目前仅支持 macOSLinux 后续会支持)
# 检查环境依赖,并根据提示的命令安装缺失的编译工具
./bin/spc doctor
# 拉取所有依赖库
./bin/spc fetch --all
@@ -200,6 +204,8 @@ cat micro.sfx code.php > single-app && chmod +x single-app
另外,添加新扩展的贡献方式,可以参考下方 `进阶`
如果你想贡献文档内容,请到项目仓库 [crazywhalecc/static-php-cli-docs](https://github.com/crazywhalecc/static-php-cli-docs) 贡献。
## 赞助本项目
你可以在 [我的个人赞助页](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md) 支持我和我的项目。

View File

@@ -1,112 +1,3 @@
# Extension List
> - yes: supported and tested
> - untested: supported, but not tested
> - empty: not supported yet
> - no with issue link: not supported yet due to issue
> - partial with issue link: supported but not perfect due to issue
| | Linux | macOS | Windows |
|-----------------|---------------------------------------------------------------------|----------------------------------------------------------------|---------|
| apcu | yes, untested | yes, untested | |
| bcmath | yes | yes | |
| bz2 | yes | yes | |
| calendar | yes | yes | |
| ctype | yes | yes | |
| curl | yes | yes | |
| dba | yes | yes | |
| dom | yes | yes | |
| enchant | | | |
| event | yes | yes | |
| exif | yes | yes | |
| ffi | | yes, [docs]() | |
| filter | yes | yes | |
| fileinfo | yes | yes | |
| ftp | yes | yes | |
| gd | yes | yes | |
| gettext | | | |
| gmp | yes | yes | |
| iconv | yes | yes | |
| imagick | yes | yes | |
| inotify | yes | yes | |
| intl | [partial](https://github.com/crazywhalecc/static-php-cli/issues/66) | yes | |
| mbstring | yes | yes | |
| mbregex | yes | yes | |
| mcrypt | | [no](https://github.com/crazywhalecc/static-php-cli/issues/32) | |
| mongodb | yes | yes | |
| mysqli | yes | yes | |
| mysqlnd | yes | yes | |
| opcache | yes | yes | |
| openssl | yes | yes | |
| password-argon2 | | | |
| pcntl | yes | yes | |
| pdo | yes | yes | |
| pdo_mysql | yes | yes | |
| pdo_sqlite | yes | yes | |
| pdo_pgsql | | | |
| phar | yes | yes | |
| posix | yes | yes | |
| protobuf | yes | yes | |
| readline | yes, untested | yes, untested | |
| redis | yes | yes | |
| session | yes | yes | |
| shmop | yes | yes | |
| simplexml | yes | yes | |
| soap | yes | yes | |
| sockets | yes | yes | |
| sodium | yes | yes | |
| sqlite3 | yes | yes | |
| ssh2 | yes, untested | yes, untested | |
| swow | yes | yes | |
| swoole | [partial](https://github.com/crazywhalecc/static-php-cli/issues/51) | yes | |
| tokenizer | yes | yes | |
| xlswriter | yes | yes | |
| xml | yes | yes | |
| xmlreader | yes, untested | yes, untested | |
| xmlwriter | yes, untested | yes, untested | |
| zip | yes, untested | yes, untested | |
| zlib | yes | yes | |
| zstd | yes | yes | |
## Additional Requirements
- phpmicro requires PHP >= 8.0
- swoole >= 5.0 requires PHP >= 8.0
- swow requires PHP >= 8.0
## Typical Extension List Example
Here are some extension list example for different use.
- For general use: `"bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,ftp,filter,gd,iconv,xml,mbstring,mysqlnd,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,redis,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip"`
- For static-php-cli self: `"posix,pcntl,phar,tokenizer,iconv,zlib"`
- For static-php-cli self (with dev dependencies): `"posix,pcntl,phar,tokenizer,iconv,zlib,xml,dom,xmlwriter,xmlreader,fileinfo"`
- Minimum, with no extension: `""`
Compile with all supported extensions (exclude `swow`, `swoole`, because these will change the default behavior of php):
```bash
bin/spc build --build-all bcmath,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,iconv,mbregex,mbstring,mongodb,mysqli,mysqlnd,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,protobuf,redis,session,shmop,simplexml,soap,sockets,sqlite3,tokenizer,xml,xmlreader,xmlwriter,yaml,zip,zlib,zstd --with-libs=libjpeg,freetype,libwebp,libavif --debug
```
## Additional Libraries
Some extensions have soft dependencies, you can enable extra features by adding these libs using `--with-libs`.
For example, to compile with gd extension, with `libwebp, libgif, libavif, libjpeg, freetype` extra features:
```bash
bin/spc build gd --with-libs=libjpeg,freetype,libwebp,libavif --build-cli
```
> If you don't add them, your compilation will not enable these features.
## Limitations
- swow and swoole cannot be compiled at the same time.
- openssl needs manual configuration for ssl certificate. (TODO: I will write a wiki about it)
- some extensions need system configuration, e.g. `curl` and `openssl` will search ssl certificate on your system.
## Bugs and TODOs
See [#32](https://github.com/crazywhalecc/static-php-cli/issues/32).
See: [Docs - Extensions](https://static-php-cli.zhamao.me/en/guide/extensions.html)

View File

@@ -21,7 +21,7 @@ class LinuxBuilder extends BuilderBase
use UnixBuilderTrait;
/** @var string[] Linux 环境下编译依赖的命令 */
public const REQUIRED_COMMANDS = ['make', 'bison', 'flex', 'pkg-config', 'git', 'autoconf', 'automake', 'tar', 'unzip', /* 'xz', 好像不需要 */ 'gzip', 'bzip2', 'cmake'];
public const REQUIRED_COMMANDS = ['make', 'bison', 'flex', 'git', 'autoconf', 'automake', 'tar', 'unzip', /* 'xz', 好像不需要 */ 'gzip', 'bzip2', 'cmake'];
/** @var string 使用的 libc */
public string $libc;

View File

@@ -40,6 +40,7 @@ class libpng extends MacOSLibraryBase
};
shell()->cd($this->source_dir)
->exec('chmod +x ./configure')
->exec('chmod +x ./install-sh')
->exec(
"{$this->builder->configure_env} ./configure " .
"--host={$this->builder->gnu_arch}-apple-darwin " .

View File

@@ -10,11 +10,16 @@ use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand('doctor', 'Diagnose whether the current environment can compile normally')]
class DoctorCommand extends BaseCommand
{
public function configure()
{
$this->addOption('auto-fix', null, null, 'Automatically fix failed items (if possible)');
}
public function handle(): int
{
try {
$checker = new CheckListHandler($this->input, $this->output);
$checker->runCheck(FIX_POLICY_PROMPT);
$checker->runCheck($this->input->getOption('auto-fix') ? FIX_POLICY_AUTOFIX : FIX_POLICY_PROMPT);
$this->output->writeln('<info>Doctor check complete !</info>');
} catch (\Throwable $e) {
$this->output->writeln('<error>' . $e->getMessage() . '</error>');

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace SPC\command;
use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
@@ -18,6 +19,8 @@ use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand('download', 'Download required sources', ['fetch'])]
class DownloadCommand extends BaseCommand
{
use UnixSystemUtilTrait;
protected string $php_major_ver;
public function configure()
@@ -28,12 +31,13 @@ class DownloadCommand extends BaseCommand
$this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'version in major.minor format like 8.1', '8.1');
$this->addOption('clean', null, null, 'Clean old download cache and source before fetch');
$this->addOption('all', 'A', null, 'Fetch all sources that static-php-cli needed');
$this->addOption('from-zip', 'Z', InputOption::VALUE_REQUIRED, 'Fetch from zip archive');
}
public function initialize(InputInterface $input, OutputInterface $output)
{
// --all 等于 "" "",也就是所有东西都要下载
if ($input->getOption('all') || $input->getOption('clean')) {
if ($input->getOption('all') || $input->getOption('clean') || $input->getOption('from-zip')) {
$input->setArgument('sources', '');
}
parent::initialize($input, $output);
@@ -66,6 +70,44 @@ class DownloadCommand extends BaseCommand
return 0;
}
// --from-zip
if ($path = $this->getOption('from-zip')) {
if (!file_exists($path)) {
logger()->critical('File ' . $path . ' not exist or not a zip archive.');
return 1;
}
// remove old download files first
if (is_dir(DOWNLOAD_PATH)) {
logger()->warning('You are doing some operations that not recoverable: removing directories below');
logger()->warning(DOWNLOAD_PATH);
logger()->alert('I will remove these dir after 5 seconds !');
sleep(5);
f_passthru((PHP_OS_FAMILY === 'Windows' ? 'rmdir /s /q ' : 'rm -rf ') . DOWNLOAD_PATH);
}
// unzip command check
if (PHP_OS_FAMILY !== 'Windows' && !$this->findCommand('unzip')) {
logger()->critical('Missing unzip command, you need to install it first !');
logger()->critical('You can use "bin/spc doctor" command to check and install required tools');
return 1;
}
// create downloads
try {
if (PHP_OS_FAMILY !== 'Windows') {
f_passthru('mkdir ' . DOWNLOAD_PATH . ' && cd ' . DOWNLOAD_PATH . ' && unzip ' . escapeshellarg($path));
}
// Windows TODO
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
throw new RuntimeException('.lock.json not exist in "downloads/"');
}
} catch (RuntimeException $e) {
logger()->critical('Extract failed: ' . $e->getMessage());
return 1;
}
logger()->info('Extract success');
return 0;
}
// Define PHP major version
$ver = $this->php_major_ver = $this->getOption('with-php') ?? '8.1';
define('SPC_BUILD_PHP_VERSION', $ver);

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use SPC\store\FileSystem;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
#[AsCommand('micro:combine', 'Combine micro.sfx and php code together')]
class MicroCombineCommand extends BaseCommand
{
public function configure()
{
$this->addArgument('file', InputArgument::REQUIRED, 'The php or phar file to be combined');
$this->addOption('with-micro', 'M', InputOption::VALUE_REQUIRED, 'Customize your micro.sfx file');
$this->addOption('with-ini-set', 'I', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'ini to inject into micro.sfx when combining');
$this->addOption('with-ini-file', 'N', InputOption::VALUE_REQUIRED, 'ini file to inject into micro.sfx when combining');
$this->addOption('output', 'O', InputOption::VALUE_REQUIRED, 'Customize your output binary file name');
}
public function handle(): int
{
// 0. Initialize path variables
$internal = FileSystem::convertPath(BUILD_ROOT_PATH . '/bin/micro.sfx');
$micro_file = $this->input->getOption('with-micro');
$file = $this->getArgument('file');
$ini_set = $this->input->getOption('with-ini-set');
$ini_file = $this->input->getOption('with-ini-file');
$target_ini = [];
$output = $this->input->getOption('output') ?? 'my-app';
$ini_part = '';
// 1. Make sure specified micro.sfx file exists
if ($micro_file !== null && !file_exists($micro_file)) {
$this->output->writeln('<error>The micro.sfx file you specified is incorrect or does not exist!</error>');
return 1;
}
// 2. Make sure buildroot/bin/micro.sfx exists
if ($micro_file === null && !file_exists($internal)) {
$this->output->writeln('<error>You haven\'t compiled micro.sfx yet, please use "build" command and "--build-micro" to compile phpmicro first!</error>');
return 1;
}
// 3. Use buildroot/bin/micro.sfx
if ($micro_file === null) {
$micro_file = $internal;
}
// 4. Make sure php or phar file exists
if (!is_file(FileSystem::convertPath($file))) {
$this->output->writeln('<error>The file to combine does not exist!</error>');
return 1;
}
// 5. Confirm ini files (ini-set has higher priority)
if ($ini_file !== null) {
// Check file exist first
if (!file_exists($ini_file)) {
$this->output->writeln('<error>The ini file to combine does not exist! (' . $ini_file . ')</error>');
return 1;
}
$arr = parse_ini_file($ini_file);
if ($arr === false) {
$this->output->writeln('<error>Cannot parse ini file</error>');
return 1;
}
$target_ini = array_merge($target_ini, $arr);
}
// 6. Confirm ini sets
if ($ini_set !== []) {
foreach ($ini_set as $item) {
$arr = parse_ini_string($item);
if ($arr === false) {
$this->output->writeln('<error>--with-ini-set parse failed</error>');
return 1;
}
$target_ini = array_merge($target_ini, $arr);
}
}
// 7. Generate ini injection parts
if (!empty($target_ini)) {
$ini_str = $this->encodeINI($target_ini);
logger()->debug('Injecting ini parts: ' . PHP_EOL . $ini_str);
$ini_part = "\xfd\xf6\x69\xe6";
$ini_part .= pack('N', strlen($ini_str));
$ini_part .= $ini_str;
}
// 8. Combine !
$output = FileSystem::isRelativePath($output) ? (WORKING_DIR . '/' . $output) : $output;
$file_target = file_get_contents($micro_file) . $ini_part . file_get_contents($file);
$result = file_put_contents($output, $file_target);
if ($result === false) {
$this->output->writeln('<error>Combine failed.</error>');
return 1;
}
// 9. chmod +x
chmod($output, 0755);
$this->output->writeln('<info>Combine success! Binary file: ' . $output . '</info>');
return 0;
}
private function encodeINI(array $array): string
{
$res = [];
foreach ($array as $key => $val) {
if (is_array($val)) {
$res[] = "[{$key}]";
foreach ($val as $skey => $sval) {
$res[] = "{$skey}=" . (is_numeric($sval) ? $sval : '"' . $sval . '"');
}
} else {
$res[] = "{$key}=" . (is_numeric($val) ? $val : '"' . $val . '"');
}
}
return implode("\n", $res);
}
}

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace SPC\doctor\item;
use SPC\builder\linux\SystemUtil;
use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\doctor\AsCheckItem;
use SPC\doctor\AsFixItem;
use SPC\doctor\CheckResult;
use SPC\exception\RuntimeException;
class LinuxToolCheckList
{
use UnixSystemUtilTrait;
public const TOOLS_ALPINE = [
'make', 'bison', 'flex',
'git', 'autoconf', 'automake',
'tar', 'unzip', 'gzip',
'bzip2', 'cmake', 'gcc',
'g++', 'patch',
];
public const TOOLS_DEBIAN = [
'make', 'bison', 'flex',
'git', 'autoconf', 'automake',
'tar', 'unzip', 'gzip',
'bzip2', 'cmake', 'patch',
];
#[AsCheckItem('if necessary tools are installed', limit_os: 'Linux')]
public function checkCliTools(): ?CheckResult
{
$distro = SystemUtil::getOSRelease();
$required = match ($distro['dist']) {
'alpine' => self::TOOLS_ALPINE,
default => self::TOOLS_DEBIAN,
};
$missing = [];
foreach ($required as $cmd) {
if ($this->findCommand($cmd) === null) {
$missing[] = $cmd;
}
}
if (!empty($missing)) {
return match ($distro['dist']) {
'ubuntu', 'alpine', 'debian' => CheckResult::fail(implode(', ', $missing) . ' not installed on your system', 'install-linux-tools', [$distro, $missing]),
default => CheckResult::fail(implode(', ', $missing) . ' not installed on your system'),
};
}
return CheckResult::ok();
}
#[AsCheckItem('if necessary packages are installed', limit_os: 'Linux')]
public function checkSystemOSPackages(): ?CheckResult
{
$distro = SystemUtil::getOSRelease();
if ($distro['dist'] === 'alpine') {
// check linux-headers installation
if (!file_exists('/usr/include/linux/mman.h')) {
return CheckResult::fail('linux-headers not installed on your system', 'install-linux-tools', [$distro, ['linux-headers']]);
}
}
return CheckResult::ok();
}
#[AsFixItem('install-linux-tools')]
public function fixBuildTools(array $distro, array $missing): bool
{
$install_cmd = match ($distro['dist']) {
'ubuntu', 'debian' => 'apt install -y',
'alpine' => 'apk add',
default => throw new RuntimeException('Current linux distro is not supported for auto-install musl packages'),
};
$prefix = '';
if (get_current_user() !== 'root') {
$prefix = 'sudo ';
logger()->warning('Current user is not root, using sudo for running command');
}
try {
shell(true)->exec($prefix . $install_cmd . ' ' . implode(' ', $missing));
} catch (RuntimeException) {
return false;
}
return true;
}
}

View File

@@ -439,6 +439,20 @@ class FileSystem
self::$_extract_hook[$name][] = $callback;
}
/**
* Check whether the path is a relative path (judging according to whether the first character is "/")
*
* @param string $path Path
*/
public static function isRelativePath(string $path): bool
{
// 适配 Windows 的多盘符目录形式
if (DIRECTORY_SEPARATOR === '\\') {
return !(strlen($path) > 2 && ctype_alpha($path[0]) && $path[1] === ':');
}
return strlen($path) > 0 && $path[0] !== '/';
}
private static function emitSourceExtractHook(string $name)
{
foreach ((self::$_extract_hook[$name] ?? []) as $hook) {