From 063b55ae0da9bab3db65776fac8f697588ad618b Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 21 Mar 2023 00:25:46 +0800 Subject: [PATCH] add brotli,bzip2,curl,zlib for linux --- ext-support.md | 6 +- src/SPC/builder/BuilderProvider.php | 12 +- src/SPC/builder/linux/LinuxBuilder.php | 333 ++++++++++++++++++ src/SPC/builder/linux/SystemUtil.php | 241 +++++++++++++ .../linux/library/LinuxLibraryBase.php | 135 +++++++ src/SPC/builder/linux/library/brotli.php | 59 ++++ src/SPC/builder/linux/library/bzip2.php | 47 +++ src/SPC/builder/linux/library/curl.php | 163 +++++++++ src/SPC/builder/linux/library/zlib.php | 47 +++ .../builder/traits/UnixSystemUtilTrait.php | 4 + 10 files changed, 1038 insertions(+), 9 deletions(-) create mode 100644 src/SPC/builder/linux/LinuxBuilder.php create mode 100644 src/SPC/builder/linux/SystemUtil.php create mode 100644 src/SPC/builder/linux/library/LinuxLibraryBase.php create mode 100644 src/SPC/builder/linux/library/brotli.php create mode 100644 src/SPC/builder/linux/library/bzip2.php create mode 100644 src/SPC/builder/linux/library/curl.php create mode 100644 src/SPC/builder/linux/library/zlib.php diff --git a/ext-support.md b/ext-support.md index 6bcfd343..a609a54d 100644 --- a/ext-support.md +++ b/ext-support.md @@ -7,11 +7,11 @@ | | Linux | macOS | Windows | |------------|-------|--------------------------------------------------------------------|---------| -| bcmath | | yes | | -| bz2 | | untested | | +| bcmath | yes | yes | | +| bz2 | yes | untested | | | calendar | | yes | | | ctype | | yes | | -| curl | | yes | | +| curl | yes | yes | | | date | | yes | | | dom | | untested | | | event | | | | diff --git a/src/SPC/builder/BuilderProvider.php b/src/SPC/builder/BuilderProvider.php index 02f3d524..c9008ceb 100644 --- a/src/SPC/builder/BuilderProvider.php +++ b/src/SPC/builder/BuilderProvider.php @@ -31,12 +31,12 @@ class BuilderProvider cxx: $input->getOption('cxx'), arch: $input->getOption('arch'), ), - // 'Linux' => new LinuxBuilder( - // cc: $input->getOption('cc'), - // cxx: $input->getOption('cxx'), - // arch: $input->getOption('arch'), - // ), - default => throw new RuntimeException('Current OS is not supported yet'), + 'Linux' => new LinuxBuilder( + cc: $input->getOption('cc'), + cxx: $input->getOption('cxx'), + arch: $input->getOption('arch'), + ), + default => throw new RuntimeException('Current OS "' . PHP_OS_FAMILY . '" is not supported yet'), }; } } diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php new file mode 100644 index 00000000..d5c4cef5 --- /dev/null +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -0,0 +1,333 @@ +cc = $cc ?? 'musl-gcc'; + $this->cxx = $cxx ?? 'g++'; + $this->arch = $arch ?? php_uname('m'); + $this->gnu_arch = arch2gnu($this->arch); + $this->libc = 'musl'; // SystemUtil::selectLibc($this->cc); + + // 根据 CPU 线程数设置编译进程数 + $this->concurrency = SystemUtil::getCpuCount(); + // 设置 cflags + $this->arch_c_flags = SystemUtil::getArchCFlags($this->cc, $this->arch); + $this->arch_cxx_flags = SystemUtil::getArchCFlags($this->cxx, $this->arch); + $this->tune_c_flags = SystemUtil::checkCCFlags(SystemUtil::getTuneCFlags($this->arch), $this->cc); + // 设置 cmake + $this->cmake_toolchain_file = SystemUtil::makeCmakeToolchainFile( + os: 'Linux', + target_arch: $this->arch, + cflags: $this->arch_c_flags, + cc: $this->cc, + cxx: $this->cxx + ); + // 设置 pkgconfig + $this->pkgconf_env = 'PKG_CONFIG_PATH="' . BUILD_LIB_PATH . '/pkgconfig"'; + // 设置 configure 依赖的环境变量 + $this->configure_env = + $this->pkgconf_env . ' ' . + "CC='{$this->cc}' " . + "CXX='{$this->cxx}' " . + (php_uname('m') === $this->arch ? '' : "CFLAGS='{$this->arch_c_flags}'"); + // 交叉编译依赖的,TODO + if (php_uname('m') !== $this->arch) { + $this->cross_compile_prefix = SystemUtil::getCrossCompilePrefix($this->cc, $this->arch); + logger()->info('using cross compile prefix: ' . $this->cross_compile_prefix); + $this->configure_env .= " CROSS_COMPILE='{$this->cross_compile_prefix}'"; + } + + $missing = []; + foreach (self::REQUIRED_COMMANDS as $cmd) { + if (SystemUtil::findCommand($cmd) === null) { + $missing[] = $cmd; + } + } + if (!empty($missing)) { + throw new RuntimeException('missing system commands: ' . implode(', ', $missing)); + } + + // 创立 pkg-config 和放头文件的目录 + f_mkdir(BUILD_LIB_PATH . '/pkgconfig', recursive: true); + f_mkdir(BUILD_INCLUDE_PATH, recursive: true); + } + + public function makeAutoconfArgs(string $name, array $libSpecs): string + { + $ret = ''; + foreach ($libSpecs as $libName => $arr) { + $lib = $this->getLib($libName); + + $arr = $arr ?? []; + + $disableArgs = $arr[0] ?? null; + $prefix = $arr[1] ?? null; + if ($lib instanceof LinuxLibraryBase) { + logger()->info("{$name} \033[32;1mwith\033[0;1m {$libName} support"); + $ret .= $lib->makeAutoconfEnv($prefix) . ' '; + } else { + logger()->info("{$name} \033[31;1mwithout\033[0;1m {$libName} support"); + $ret .= ($disableArgs ?? "--with-{$libName}=no") . ' '; + } + } + return rtrim($ret); + } + + /** + * @throws RuntimeException + * @throws FileSystemException + */ + public function buildPHP(int $build_micro_rule = BUILD_MICRO_NONE, bool $with_clean = false, bool $bloat = false) + { + if (!$bloat) { + $extra_libs = implode(' ', $this->getAllStaticLibFiles()); + } else { + logger()->info('bloat linking'); + $extra_libs = implode( + ' ', + array_map( + fn ($x) => "-Xcompiler {$x}", + array_filter($this->getAllStaticLibFiles()) + ) + ); + } + + $envs = $this->pkgconf_env . ' ' . + "CC='{$this->cc}' " . + "CXX='{$this->cxx}' "; + $cflags = $this->arch_c_flags; + $use_lld = ''; + + switch ($this->libc) { + case 'musl_wrapper': + case 'glibc': + $cflags .= ' -static-libgcc -I"' . BUILD_INCLUDE_PATH . '"'; + break; + case 'musl': + if (str_ends_with($this->cc, 'clang') && SystemUtil::findCommand('lld')) { + $use_lld = '-Xcompiler -fuse-ld=lld'; + } + break; + default: + throw new RuntimeException('libc ' . $this->libc . ' is not implemented yet'); + } + + $envs = "{$envs} CFLAGS='{$cflags}' LIBS='-ldl -lpthread'"; + + Patcher::patchPHPBeforeConfigure($this); + + f_passthru( + $this->set_x . ' && ' . + 'cd ' . SOURCE_PATH . '/php-src && ' . + './buildconf --force' + ); + + Patcher::patchPHPConfigure($this); + + f_passthru( + $this->set_x . ' && ' . + 'cd ' . SOURCE_PATH . '/php-src && ' . + './configure ' . + '--prefix= ' . + '--with-valgrind=no ' . + '--enable-shared=no ' . + '--enable-static=yes ' . + "--host={$this->gnu_arch}-unknown-linux " . + '--disable-all ' . + '--disable-cgi ' . + '--disable-phpdbg ' . + '--enable-cli ' . + '--enable-micro=all-static ' . + ($this->zts ? '--enable-zts' : '') . ' ' . + $this->makeExtensionArgs() . ' ' . + $envs + ); + + $extra_libs .= $this->generateExtraLibs(); + + file_put_contents('/tmp/comment', $this->note_section); + + if ($with_clean) { + logger()->info('cleaning up'); + f_passthru( + $this->set_x . ' && ' . + 'cd ' . SOURCE_PATH . '/php-src && ' . + 'make clean' + ); + } + + if ($bloat) { + logger()->info('bloat linking'); + $extra_libs = "-Wl,--whole-archive {$extra_libs} -Wl,--no-whole-archive"; + } + + switch ($build_micro_rule) { + case BUILD_MICRO_NONE: + logger()->info('building cli'); + $this->buildCli($extra_libs, $use_lld); + break; + case BUILD_MICRO_ONLY: + logger()->info('building micro'); + $this->buildMicro($extra_libs, $use_lld, $cflags); + break; + case BUILD_MICRO_BOTH: + logger()->info('building cli and micro'); + $this->buildCli($extra_libs, $use_lld); + $this->buildMicro($extra_libs, $use_lld, $cflags); + break; + } + + if (php_uname('m') === $this->arch) { + $this->sanityCheck($build_micro_rule); + } + + if ($this->phar_patched) { + f_passthru('cd ' . SOURCE_PATH . '/php-src && patch -p1 -R < sapi/micro/patches/phar.patch'); + } + } + + /** + * @throws RuntimeException + */ + public function buildCli(string $extra_libs, string $use_lld): void + { + f_passthru( + $this->set_x . ' && ' . + 'cd ' . SOURCE_PATH . '/php-src && ' . + 'sed -i "s|//lib|/lib|g" Makefile && ' . + "make -j{$this->concurrency} " . + 'EXTRA_CFLAGS="-g -Os -fno-ident ' . implode(' ', array_map(fn ($x) => "-Xcompiler {$x}", $this->tune_c_flags)) . '" ' . + "EXTRA_LIBS=\"{$extra_libs}\" " . + "EXTRA_LDFLAGS_PROGRAM='{$use_lld}" . + ' -all-static' . + "' " . + 'cli && ' . + 'cd sapi/cli && ' . + "{$this->cross_compile_prefix}objcopy --only-keep-debug php php.debug && " . + 'elfedit --output-osabi linux php && ' . + "{$this->cross_compile_prefix}strip --strip-all php && " . + "{$this->cross_compile_prefix}objcopy --update-section .comment=/tmp/comment --add-gnu-debuglink=php.debug --remove-section=.note php" + ); + } + + /** + * @throws RuntimeException + */ + public function buildMicro(string $extra_libs, string $use_lld, string $cflags): void + { + if ($this->getExt('phar')) { + $this->phar_patched = true; + try { + f_passthru('cd ' . SOURCE_PATH . '/php-src && patch -p1 < sapi/micro/patches/phar.patch'); + } catch (RuntimeException $e) { + logger()->error('failed to patch phat due to patch exit with code ' . $e->getCode()); + $this->phar_patched = false; + } + } + + $vars = [ + 'EXTRA_CFLAGS' => quote('-g -Os -fno-ident ' . implode(' ', array_map(fn ($x) => "-Xcompiler {$x}", $this->tune_c_flags))), + 'EXTRA_LIBS' => quote($extra_libs), + 'EXTRA_LDFLAGS_PROGRAM' => quote("{$cflags} {$use_lld}" . ' -all-static', "'"), + 'POST_MICRO_BUILD_COMMANDS' => quote( + "sh -xc '" . + 'cd sapi/micro && ' . + "{$this->cross_compile_prefix}objcopy --only-keep-debug micro.sfx micro.sfx.debug && " . + 'elfedit --output-osabi linux micro.sfx && ' . + "{$this->cross_compile_prefix}strip --strip-all micro.sfx && " . + "{$this->cross_compile_prefix}objcopy --update-section .comment=/tmp/comment --add-gnu-debuglink=micro.sfx.debug --remove-section=.note micro.sfx'" + ), + ]; + $var_cmdline = ''; + foreach ($vars as $k => $v) { + $var_cmdline .= $k . '=' . $v . ' '; + } + + f_passthru( + $this->set_x . ' && ' . + 'cd ' . SOURCE_PATH . '/php-src && ' . + 'sed -i "s|//lib|/lib|g" Makefile && ' . + "make -j{$this->concurrency} " . + $var_cmdline . + 'micro' + ); + } + + /** + * @throws RuntimeException + */ + private function generateExtraLibs(): string + { + if ($this->libc === 'glibc') { + $glibc_libs = [ + 'rt', + 'm', + 'c', + 'pthread', + 'dl', + 'nsl', + 'anl', + // 'crypt', + 'resolv', + 'util', + ]; + $makefile = file_get_contents(SOURCE_PATH . '/php-src/Makefile'); + preg_match('/^EXTRA_LIBS\s*=\s*(.*)$/m', $makefile, $matches); + if (!$matches) { + throw new RuntimeException('failed to find EXTRA_LIBS in Makefile'); + } + $_extra_libs = []; + foreach (array_filter(explode(' ', $matches[1])) as $used) { + foreach ($glibc_libs as $libName) { + if ("-l{$libName}" === $used && !in_array("-l{$libName}", $_extra_libs, true)) { + array_unshift($_extra_libs, "-l{$libName}"); + } + } + } + return ' ' . implode(' ', $_extra_libs); + } + return ''; + } +} diff --git a/src/SPC/builder/linux/SystemUtil.php b/src/SPC/builder/linux/SystemUtil.php new file mode 100644 index 00000000..a2e4a789 --- /dev/null +++ b/src/SPC/builder/linux/SystemUtil.php @@ -0,0 +1,241 @@ +debug('Choose cc'); + if (self::findCommand('clang')) { + logger()->info('using clang'); + return 'clang'; + } + if (self::findCommand('gcc')) { + logger()->info('using gcc'); + return 'gcc'; + } + throw new RuntimeException('no supported cc found'); + } + + /** + * 查找并选择编译器命令 + * + * @throws RuntimeException + */ + public static function selectCXX(): string + { + logger()->debug('Choose cxx'); + if (self::findCommand('clang++')) { + logger()->info('using clang++'); + return 'clang++'; + } + if (self::findCommand('g++')) { + logger()->info('using g++'); + return 'g++'; + } + return self::selectCC(); + } + + #[ArrayShape(['dist' => 'mixed|string', 'ver' => 'mixed|string'])] + public static function getOSRelease(): array + { + $ret = [ + 'dist' => 'unknown', + 'ver' => 'unknown', + ]; + switch (true) { + case file_exists('/etc/os-release'): + $lines = file('/etc/os-release'); + foreach ($lines as $line) { + if (preg_match('/^ID=(.*)$/', $line, $matches)) { + $ret['dist'] = $matches[1]; + } + if (preg_match('/^VERSION_ID=(.*)$/', $line, $matches)) { + $ret['ver'] = $matches[1]; + } + } + $ret['dist'] = trim($ret['dist'], '"\''); + $ret['ver'] = trim($ret['ver'], '"\''); + if (strcasecmp($ret['dist'], 'centos') === 0) { + $ret['dist'] = 'redhat'; + } + break; + case file_exists('/etc/centos-release'): + $lines = file('/etc/centos-release'); + goto rh; + case file_exists('/etc/redhat-release'): + $lines = file('/etc/redhat-release'); + rh: + foreach ($lines as $line) { + if (preg_match('/release\s+(\d+(\.\d+)*)/', $line, $matches)) { + $ret['dist'] = 'redhat'; + $ret['ver'] = $matches[1]; + } + } + break; + } + return $ret; + } + + public static function getCpuCount(): int + { + $ncpu = 1; + + if (is_file('/proc/cpuinfo')) { + $cpuinfo = file_get_contents('/proc/cpuinfo'); + preg_match_all('/^processor/m', $cpuinfo, $matches); + $ncpu = count($matches[0]); + } + + return $ncpu; + } + + /** + * @throws RuntimeException + */ + public static function getCCType(string $cc): string + { + return match (true) { + str_ends_with($cc, 'c++'), str_ends_with($cc, 'cc'), str_ends_with($cc, 'g++'), str_ends_with($cc, 'gcc') => 'gcc', + $cc === 'clang++', $cc === 'clang', str_starts_with($cc, 'musl-clang') => 'clang', + default => throw new RuntimeException("unknown cc type: {$cc}"), + }; + } + + /** + * @throws RuntimeException + */ + public static function getArchCFlags(string $cc, string $arch): string + { + if (php_uname('m') === $arch) { + return ''; + } + return match (static::getCCType($cc)) { + 'clang' => match ($arch) { + 'x86_64' => '--target=x86_64-unknown-linux', + 'arm64', 'aarch64' => '--target=arm64-unknown-linux', + default => throw new RuntimeException('unsupported arch: ' . $arch), + }, + 'gcc' => '', + default => throw new RuntimeException('cc compiler ' . $cc . ' is not supported'), + }; + } + + /** + * @throws RuntimeException + */ + public static function getTuneCFlags(string $arch): array + { + return match ($arch) { + 'x86_64' => [ + '-march=corei7', + '-mtune=core-avx2', + ], + 'arm64', 'aarch64' => [], + default => throw new RuntimeException('unsupported arch: ' . $arch), + }; + } + + public static function checkCCFlags(array $flags, string $cc): array + { + return array_filter($flags, fn ($flag) => static::checkCCFlag($flag, $cc)); + } + + public static function checkCCFlag(string $flag, string $cc): string + { + $ret = 0; + f_exec("echo | {$cc} -E -x c - {$flag}", $dummy, $ret); + if ($ret != 0) { + return ''; + } + return $flag; + } + + /** + * @throws RuntimeException + */ + public static function getCrossCompilePrefix(string $cc, string $arch): string + { + return match (static::getCCType($cc)) { + // guessing clang toolchains + 'clang' => match ($arch) { + 'x86_64' => 'x86_64-linux-gnu-', + 'arm64', 'aarch64' => 'aarch64-linux-gnu-', + default => throw new RuntimeException('unsupported arch: ' . $arch), + }, + // remove gcc postfix + 'gcc' => str_replace('-cc', '', str_replace('-gcc', '', $cc)) . '-', + default => throw new RuntimeException('unsupported cc'), + }; + } + + public static function findStaticLib(string $name): ?array + { + $paths = getenv('LIBPATH'); + if (!$paths) { + $paths = '/lib:/lib64:/usr/lib:/usr/lib64:/usr/local/lib:/usr/local/lib64'; + } + foreach (explode(':', $paths) as $path) { + if (file_exists("{$path}/{$name}")) { + return ["{$path}", "{$name}"]; + } + } + return null; + } + + public static function findStaticLibs(array $names): ?array + { + $ret = []; + foreach ($names as $name) { + $path = static::findStaticLib($name); + if (!$path) { + logger()->warning("static library {$name} not found"); + return null; + } + $ret[] = $path; + } + return $ret; + } + + public static function findHeader(string $name): ?array + { + $paths = getenv('INCLUDEPATH'); + if (!$paths) { + $paths = '/include:/usr/include:/usr/local/include'; + } + foreach (explode(':', $paths) as $path) { + if (file_exists("{$path}/{$name}") || is_dir("{$path}/{$name}")) { + return ["{$path}", "{$name}"]; + } + } + return null; + } + + public static function findHeaders(array $names): ?array + { + $ret = []; + foreach ($names as $name) { + $path = static::findHeader($name); + if (!$path) { + logger()->warning("header {$name} not found"); + return null; + } + $ret[] = $path; + } + return $ret; + } +} diff --git a/src/SPC/builder/linux/library/LinuxLibraryBase.php b/src/SPC/builder/linux/library/LinuxLibraryBase.php new file mode 100644 index 00000000..c9a94076 --- /dev/null +++ b/src/SPC/builder/linux/library/LinuxLibraryBase.php @@ -0,0 +1,135 @@ + true,代表依赖 curl 但可选 + */ + protected array $dep_names; + + public function __construct(protected LinuxBuilder $builder) + { + parent::__construct(); + } + + public function getBuilder(): BuilderBase + { + return $this->builder; + } + + /** + * @throws RuntimeException + */ + public function tryBuild(bool $force_build = false): int + { + // 传入 true,表明直接编译 + if ($force_build) { + $this->build(); + return BUILD_STATUS_OK; + } + + // 看看这些库是不是存在,如果不存在,则调用编译并返回结果状态 + foreach ($this->getStaticLibs() as $name) { + if (!file_exists(BUILD_LIB_PATH . "/{$name}")) { + $this->tryBuild(true); + return BUILD_STATUS_OK; + } + } + // 头文件同理 + foreach ($this->getHeaders() as $name) { + if (!file_exists(BUILD_INCLUDE_PATH . "/{$name}")) { + $this->tryBuild(true); + return BUILD_STATUS_OK; + } + } + // 到这里说明所有的文件都存在,就跳过编译 + return BUILD_STATUS_ALREADY; + } + + protected function makeFakePkgconfs() + { + $workspace = BUILD_ROOT_PATH; + if ($workspace === '/') { + $workspace = ''; + } + foreach ($this->pkgconfs as $name => $content) { + file_put_contents(BUILD_LIB_PATH . "/pkgconfig/{$name}", "prefix={$workspace}\n" . $content); + } + } + + /** + * @throws RuntimeException + */ + private function _make(bool $forceBuild = false, bool $fresh = false) + { + if ($forceBuild || php_uname('m') !== $this->builder->arch) { + $this->build(); + return; + } + $static_lib_patches = SystemUtil::findStaticLibs($this->static_libs); + $header_patches = SystemUtil::findHeaders($this->headers); + if (!$static_lib_patches || !$header_patches) { + $this->build(); + } else { + if ($this->builder->libc === 'musl_wrapper') { + logger()->warning('libc type may not match, this may cause strange symbol missing'); + } + $this->copyExist($static_lib_patches, $header_patches); + } + $this->fixPkgConfigs(); + } + + private function fixPkgConfigs() + { + foreach ($this->pkgconfs as $name => $_) { + Patcher::patchLinuxPkgConfig(BUILD_LIB_PATH . "/pkgconfig/{$name}"); + } + } + + /** + * @throws RuntimeException + */ + private function copyExist(array $static_lib_patches, array $header_patches): void + { + if (!$static_lib_patches || !$header_patches) { + throw new RuntimeException('??? staticLibPathes or headerPathes is null'); + } + logger()->info('using system ' . static::NAME); + foreach ($static_lib_patches as [$path, $staticLib]) { + @f_mkdir(BUILD_LIB_PATH . '/' . dirname($staticLib), recursive: true); + logger()->info("copy {$path}/{$staticLib} to " . BUILD_LIB_PATH . "/{$staticLib}"); + copy("{$path}/{$staticLib}", BUILD_LIB_PATH . '/' . $staticLib); + } + foreach ($header_patches as [$path, $header]) { + @f_mkdir(BUILD_INCLUDE_PATH . '/' . dirname($header), recursive: true); + logger()->info("copy {$path}/{$header} to " . BUILD_INCLUDE_PATH . "/{$header}"); + if (is_dir("{$path}/{$header}")) { + FileSystem::copyDir("{$path}/{$header}", BUILD_INCLUDE_PATH . "/{$header}"); + } else { + copy("{$path}/{$header}", BUILD_INCLUDE_PATH . "/{$header}"); + } + } + $this->makeFakePkgconfs(); + } +} diff --git a/src/SPC/builder/linux/library/brotli.php b/src/SPC/builder/linux/library/brotli.php new file mode 100644 index 00000000..4bc21d56 --- /dev/null +++ b/src/SPC/builder/linux/library/brotli.php @@ -0,0 +1,59 @@ + + * + * lwmbs is licensed under Mulan PSL v2. You can use this + * software according to the terms and conditions of the + * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, + * WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * + * See the Mulan PSL v2 for more details. + */ + +declare(strict_types=1); + +namespace SPC\builder\linux\library; + +use SPC\exception\RuntimeException; + +class brotli extends LinuxLibraryBase +{ + public const NAME = 'brotli'; + + /** + * @throws RuntimeException + */ + public function build() + { + [$lib, $include, $destdir] = SEPARATED_PATH; + f_passthru( + "{$this->builder->set_x} && " . + "cd {$this->source_dir} && " . + 'rm -rf build && ' . + 'mkdir -p build && ' . + 'cd build && ' . + "{$this->builder->configure_env} " . ' cmake ' . + // '--debug-find ' . + '-DCMAKE_BUILD_TYPE=Release ' . + '-DBUILD_SHARED_LIBS=OFF ' . + '-DCMAKE_INSTALL_PREFIX=/ ' . + "-DCMAKE_INSTALL_LIBDIR={$lib} " . + "-DCMAKE_INSTALL_INCLUDEDIR={$include} " . + "-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " . + '.. && ' . + "cmake --build . -j {$this->builder->concurrency} --target brotlicommon-static && " . + "cmake --build . -j {$this->builder->concurrency} --target brotlidec-static && " . + "cmake --build . -j {$this->builder->concurrency} --target brotlienc-static && " . + 'cp libbrotlidec-static.a ' . BUILD_LIB_PATH . ' && ' . + 'cp libbrotlienc-static.a ' . BUILD_LIB_PATH . ' && ' . + 'cp libbrotlicommon-static.a ' . BUILD_LIB_PATH . ' && ' . + 'cp -r ../c/include/brotli ' . BUILD_INCLUDE_PATH + ); + } +} diff --git a/src/SPC/builder/linux/library/bzip2.php b/src/SPC/builder/linux/library/bzip2.php new file mode 100644 index 00000000..bf59d740 --- /dev/null +++ b/src/SPC/builder/linux/library/bzip2.php @@ -0,0 +1,47 @@ + + * + * lwmbs is licensed under Mulan PSL v2. You can use this + * software according to the terms and conditions of the + * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, + * WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * + * See the Mulan PSL v2 for more details. + */ + +declare(strict_types=1); + +namespace SPC\builder\linux\library; + +use SPC\exception\RuntimeException; + +class bzip2 extends LinuxLibraryBase +{ + public const NAME = 'bzip2'; + + protected array $dep_names = []; + + /** + * @throws RuntimeException + */ + public function build() + { + f_passthru( + $this->builder->set_x . ' && ' . + "cd {$this->source_dir} && " . + "make {$this->builder->configure_env} PREFIX='" . BUILD_ROOT_PATH . "' clean" . ' && ' . + "make -j{$this->builder->concurrency} {$this->builder->configure_env} PREFIX='" . BUILD_ROOT_PATH . "' libbz2.a" . ' && ' . + // make install may fail when cross-compiling, so we copy files. + 'cp libbz2.a ' . BUILD_LIB_PATH . ' && ' . + 'cp bzlib.h ' . BUILD_INCLUDE_PATH + ); + $this->makeFakePkgconfs(); + } +} diff --git a/src/SPC/builder/linux/library/curl.php b/src/SPC/builder/linux/library/curl.php new file mode 100644 index 00000000..8032242a --- /dev/null +++ b/src/SPC/builder/linux/library/curl.php @@ -0,0 +1,163 @@ + + * + * lwmbs is licensed under Mulan PSL v2. You can use this + * software according to the terms and conditions of the + * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, + * WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * + * See the Mulan PSL v2 for more details. + */ + +declare(strict_types=1); + +namespace SPC\builder\linux\library; + +use SPC\exception\RuntimeException; + +class curl extends LinuxLibraryBase +{ + public const NAME = 'curl'; + + protected array $static_libs = ['libcurl.a']; + + protected array $headers = ['curl']; + + protected array $pkgconfs = [ + 'libcurl.pc' => <<<'EOF' +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include +supported_protocols="DICT FILE FTP FTPS GOPHER GOPHERS HTTP HTTPS IMAP IMAPS MQTT POP3 POP3S RTSP SCP SFTP SMB SMBS SMTP SMTPS TELNET TFTP" +supported_features="AsynchDNS GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets alt-svc brotli libz zstd" + +Name: libcurl +URL: https://curl.se/ +Description: Library to transfer files with ftp, http, etc. +Version: 7.83.0 +Libs: -L${libdir} -lcurl +Libs.private: -lnghttp2 -lidn2 -lssh2 -lssh2 -lpsl -lssl -lcrypto -lssl -lcrypto -lgssapi_krb5 -lzstd -lbrotlidec -lz +Cflags: -I${includedir} +EOF + ]; + + protected array $dep_names = [ + 'zlib' => false, + 'libssh2' => true, + 'brotli' => true, + 'nghttp2' => true, + 'zstd' => true, + 'openssl' => true, + 'idn2' => true, + 'psl' => true, + ]; + + public function getStaticLibFiles(string $style = 'autoconf', bool $recursive = true): string + { + $libs = parent::getStaticLibFiles($style, $recursive); + if ($this->builder->getLib('openssl')) { + $libs .= ' -ldl -lpthread'; + } + return $libs; + } + + /** + * @throws RuntimeException + */ + public function build() + { + $extra = ''; + // lib:openssl + $openssl = $this->builder->getLib('openssl'); + if ($openssl instanceof LinuxLibraryBase) { + $extra .= '-DCURL_USE_OPENSSL=ON '; + } else { + $extra .= '-DCURL_USE_OPENSSL=OFF -DCURL_ENABLE_SSL=OFF '; + } + // lib:zlib + $zlib = $this->builder->getLib('zlib'); + if ($zlib instanceof LinuxLibraryBase) { + $extra .= '-DZLIB_LIBRARY="' . $zlib->getStaticLibFiles(style: 'cmake') . '" ' . + '-DZLIB_INCLUDE_DIR=' . BUILD_INCLUDE_PATH . ' '; + } + // lib:libssh2 + $libssh2 = $this->builder->getLib('libssh2'); + if ($libssh2 instanceof LinuxLibraryBase) { + $extra .= '-DLIBSSH2_LIBRARY="' . $libssh2->getStaticLibFiles(style: 'cmake') . '" ' . + '-DLIBSSH2_INCLUDE_DIR="' . BUILD_INCLUDE_PATH . '" '; + } else { + $extra .= '-DCURL_USE_LIBSSH2=OFF '; + } + // lib:brotli + $brotli = $this->builder->getLib('brotli'); + if ($brotli) { + $extra .= '-DCURL_BROTLI=ON ' . + '-DBROTLIDEC_LIBRARY="' . realpath(BUILD_LIB_PATH . '/libbrotlidec-static.a') . ';' . realpath(BUILD_LIB_PATH . '/libbrotlicommon-static.a') . '" ' . + '-DBROTLICOMMON_LIBRARY="' . realpath(BUILD_LIB_PATH . '/libbrotlicommon-static.a') . '" ' . + '-DBROTLI_INCLUDE_DIR="' . BUILD_INCLUDE_PATH . '" '; + } else { + $extra .= '-DCURL_BROTLI=OFF '; + } + // lib:nghttp2 + $nghttp2 = $this->builder->getLib('nghttp2'); + if ($nghttp2 instanceof LinuxLibraryBase) { + $extra .= '-DUSE_NGHTTP2=ON ' . + '-DNGHTTP2_LIBRARY="' . $nghttp2->getStaticLibFiles(style: 'cmake') . '" ' . + '-DNGHTTP2_INCLUDE_DIR="' . BUILD_INCLUDE_PATH . '" '; + } else { + $extra .= '-DUSE_NGHTTP2=OFF '; + } + // lib:ldap + $ldap = $this->builder->getLib('ldap'); + if ($ldap instanceof LinuxLibraryBase) { + // $extra .= '-DCURL_DISABLE_LDAP=OFF '; + // TODO: LDAP support + throw new RuntimeException('LDAP support is not implemented yet'); + } + $extra .= '-DCURL_DISABLE_LDAP=ON '; + // lib:zstd + $zstd = $this->builder->getLib('zstd'); + if ($zstd instanceof LinuxLibraryBase) { + $extra .= '-DCURL_ZSTD=ON ' . + '-DZstd_LIBRARY="' . $zstd->getStaticLibFiles(style: 'cmake') . '" ' . + '-DZstd_INCLUDE_DIR="' . BUILD_INCLUDE_PATH . '" '; + } else { + $extra .= '-DCURL_ZSTD=OFF '; + } + // lib:idn2 + $idn2 = $this->builder->getLib('idn2'); + $extra .= $idn2 instanceof LinuxLibraryBase ? '-DUSE_LIBIDN2=ON ' : '-DUSE_LIBIDN2=OFF '; + // lib:psl + $libpsl = $this->builder->getLib('psl'); + $extra .= $libpsl instanceof LinuxLibraryBase ? '-DCURL_USE_LIBPSL=ON ' : '-DCURL_USE_LIBPSL=OFF '; + + [$lib, $include, $destdir] = SEPARATED_PATH; + // compile! + f_passthru( + $this->builder->set_x . ' && ' . + "cd {$this->source_dir} && " . + 'rm -rf build && ' . + 'mkdir -p build && ' . + 'cd build && ' . + "{$this->builder->configure_env} " . ' cmake ' . + // '--debug-find ' . + '-DCMAKE_BUILD_TYPE=Release ' . + '-DBUILD_SHARED_LIBS=OFF ' . + $extra . + '-DCMAKE_INSTALL_PREFIX=/ ' . + "-DCMAKE_INSTALL_LIBDIR={$lib} " . + "-DCMAKE_INSTALL_INCLUDEDIR={$include} " . + "-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " . + '.. && ' . + "make -j{$this->builder->concurrency} && " . + 'make install DESTDIR="' . $destdir . '"' + ); + } +} diff --git a/src/SPC/builder/linux/library/zlib.php b/src/SPC/builder/linux/library/zlib.php new file mode 100644 index 00000000..1c3871c1 --- /dev/null +++ b/src/SPC/builder/linux/library/zlib.php @@ -0,0 +1,47 @@ + + * + * lwmbs is licensed under Mulan PSL v2. You can use this + * software according to the terms and conditions of the + * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, + * WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * + * See the Mulan PSL v2 for more details. + */ + +declare(strict_types=1); + +namespace SPC\builder\linux\library; + +use SPC\exception\RuntimeException; + +class zlib extends LinuxLibraryBase +{ + public const NAME = 'zlib'; + + /** + * @throws RuntimeException + */ + public function build() + { + [,,$destdir] = SEPARATED_PATH; + + f_passthru( + $this->builder->set_x . ' && ' . + "cd {$this->source_dir} && " . + "{$this->builder->configure_env} ./configure " . + '--static ' . + '--prefix= && ' . // use prefix=/ + 'make clean && ' . + "make -j{$this->builder->concurrency} && " . + 'make install DESTDIR=' . $destdir + ); + } +} diff --git a/src/SPC/builder/traits/UnixSystemUtilTrait.php b/src/SPC/builder/traits/UnixSystemUtilTrait.php index 3ac83d69..1af617ee 100644 --- a/src/SPC/builder/traits/UnixSystemUtilTrait.php +++ b/src/SPC/builder/traits/UnixSystemUtilTrait.php @@ -44,6 +44,10 @@ SET(CMAKE_C_FLAGS "{$cflags}") SET(CMAKE_CXX_FLAGS "{$cflags}") SET(CMAKE_FIND_ROOT_PATH "{$root}") CMAKE; + // 有时候系统的 cmake 找不到 ar 命令,真奇怪 + if (PHP_OS_FAMILY === 'Linux') { + $toolchain .= "\nSET(CMAKE_AR \"ar\")"; + } file_put_contents(SOURCE_PATH . '/toolchain.cmake', $toolchain); return realpath(SOURCE_PATH . '/toolchain.cmake'); }