From 147c3ee499d12ec1c2f323d485ffcea66f6511e5 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 15 Oct 2023 13:07:13 +0800 Subject: [PATCH] add basic FreeBSD support for utils --- src/SPC/builder/BuilderProvider.php | 2 + src/SPC/builder/freebsd/BSDBuilder.php | 249 ++++++++++++++++++ src/SPC/builder/freebsd/SystemUtil.php | 46 ++++ .../freebsd/library/BSDLibraryBase.php | 27 ++ src/SPC/builder/freebsd/library/pkgconfig.php | 15 ++ src/SPC/builder/freebsd/library/zlib.php | 12 + src/SPC/doctor/item/BSDToolCheckList.php | 66 +++++ src/SPC/doctor/item/OSCheckList.php | 2 +- src/SPC/store/Config.php | 2 + src/SPC/store/FileSystem.php | 2 +- src/globals/functions.php | 1 + 11 files changed, 422 insertions(+), 2 deletions(-) create mode 100644 src/SPC/builder/freebsd/BSDBuilder.php create mode 100644 src/SPC/builder/freebsd/SystemUtil.php create mode 100644 src/SPC/builder/freebsd/library/BSDLibraryBase.php create mode 100644 src/SPC/builder/freebsd/library/pkgconfig.php create mode 100644 src/SPC/builder/freebsd/library/zlib.php create mode 100644 src/SPC/doctor/item/BSDToolCheckList.php diff --git a/src/SPC/builder/BuilderProvider.php b/src/SPC/builder/BuilderProvider.php index b0578ac5..552af229 100644 --- a/src/SPC/builder/BuilderProvider.php +++ b/src/SPC/builder/BuilderProvider.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace SPC\builder; +use SPC\builder\freebsd\BSDBuilder; use SPC\builder\linux\LinuxBuilder; use SPC\builder\macos\MacOSBuilder; use SPC\exception\FileSystemException; @@ -31,6 +32,7 @@ class BuilderProvider // ), 'Darwin' => new MacOSBuilder($input->getOptions()), 'Linux' => new LinuxBuilder($input->getOptions()), + 'BSD' => new BSDBuilder($input->getOptions()), default => throw new WrongUsageException('Current OS "' . PHP_OS_FAMILY . '" is not supported yet'), }; } diff --git a/src/SPC/builder/freebsd/BSDBuilder.php b/src/SPC/builder/freebsd/BSDBuilder.php new file mode 100644 index 00000000..1ad00565 --- /dev/null +++ b/src/SPC/builder/freebsd/BSDBuilder.php @@ -0,0 +1,249 @@ +options = $options; + + // ---------- set necessary options ---------- + // set C Compiler (default: clang) + $this->setOptionIfNotExist('cc', 'clang'); + // set C++ Composer (default: clang++) + $this->setOptionIfNotExist('cxx', 'clang++'); + // set arch (default: current) + $this->setOptionIfNotExist('arch', php_uname('m')); + $this->setOptionIfNotExist('gnu-arch', arch2gnu($this->getOption('arch'))); + + // ---------- set necessary compile environments ---------- + // concurrency + $this->concurrency = SystemUtil::getCpuCount(); + // cflags + $this->arch_c_flags = SystemUtil::getArchCFlags($this->getOption('arch')); + $this->arch_cxx_flags = SystemUtil::getArchCFlags($this->getOption('arch')); + // cmake toolchain + $this->cmake_toolchain_file = SystemUtil::makeCmakeToolchainFile('BSD', $this->getOption('arch'), $this->arch_c_flags); + // configure environment + $this->configure_env = SystemUtil::makeEnvVarString([ + 'PKG_CONFIG' => BUILD_ROOT_PATH . '/bin/pkg-config', + 'PKG_CONFIG_PATH' => BUILD_LIB_PATH . '/pkgconfig/', + 'CC' => $this->getOption('cc'), + 'CXX' => $this->getOption('cxx'), + 'CFLAGS' => "{$this->arch_c_flags} -Wimplicit-function-declaration -Os", + 'LIBS' => '-ldl -lpthread', + 'PATH' => BUILD_ROOT_PATH . '/bin:' . getenv('PATH'), + ]); + + // create pkgconfig and include dir (some libs cannot create them automatically) + f_mkdir(BUILD_LIB_PATH . '/pkgconfig', recursive: true); + f_mkdir(BUILD_INCLUDE_PATH, recursive: true); + } + + /** + * Just start to build statically linked php binary + * + * @param int $build_target build target + * @throws FileSystemException + * @throws RuntimeException + * @throws WrongUsageException + */ + public function buildPHP(int $build_target = BUILD_TARGET_NONE): void + { + // ---------- Update extra-libs ---------- + $extra_libs = $this->getOption('extra-libs', ''); + // add libc++, some extensions or libraries need it (C++ cannot be linked statically) + $extra_libs .= (empty($extra_libs) ? '' : ' ') . ($this->hasCppExtension() ? '-lc++ ' : ''); + if (!$this->getOption('bloat', false)) { + $extra_libs .= (empty($extra_libs) ? '' : ' ') . implode(' ', $this->getAllStaticLibFiles()); + } else { + logger()->info('bloat linking'); + $extra_libs .= (empty($extra_libs) ? '' : ' ') . implode(' ', array_map(fn ($x) => "-Wl,-force_load,{$x}", array_filter($this->getAllStaticLibFiles()))); + } + $this->setOption('extra-libs', $extra_libs); + + SourcePatcher::patchBeforeBuildconf($this); + + shell()->cd(SOURCE_PATH . '/php-src')->exec('./buildconf --force'); + + SourcePatcher::patchBeforeConfigure($this); + + $json_74 = $this->getPHPVersionID() < 80000 ? '--enable-json ' : ''; + $zts = $this->getOption('enable-zts', false) ? '--enable-zts --disable-zend-signals ' : ''; + + $enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI; + $enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM; + $enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; + $enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; + + shell()->cd(SOURCE_PATH . '/php-src') + ->exec( + './configure ' . + '--prefix= ' . + '--with-valgrind=no ' . // Not detect memory leak + '--enable-shared=no ' . + '--enable-static=yes ' . + "CFLAGS='{$this->arch_c_flags} -Werror=unknown-warning-option' " . + '--disable-all ' . + '--disable-cgi ' . + '--disable-phpdbg ' . + ($enableCli ? '--enable-cli ' : '--disable-cli ') . + ($enableFpm ? '--enable-fpm ' : '--disable-fpm ') . + ($enableEmbed ? '--enable-embed=static ' : '--disable-embed ') . + ($enableMicro ? '--enable-micro ' : '--disable-micro ') . + $json_74 . + $zts . + $this->makeExtensionArgs() . ' ' . + $this->configure_env + ); + + SourcePatcher::patchBeforeMake($this); + + $this->cleanMake(); + + if ($enableCli) { + logger()->info('building cli'); + $this->buildCli(); + } + if ($enableFpm) { + logger()->info('building fpm'); + $this->buildFpm(); + } + if ($enableMicro) { + logger()->info('building micro'); + $this->buildMicro(); + } + if ($enableEmbed) { + logger()->info('building embed'); + if ($enableMicro) { + FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'OVERALL_TARGET =', 'OVERALL_TARGET = libphp.la'); + } + $this->buildEmbed(); + } + + if (php_uname('m') === $this->getOption('arch')) { + $this->sanityCheck($build_target); + } + } + + /** + * Build cli sapi + * + * @throws RuntimeException + * @throws FileSystemException + */ + public function buildCli(): void + { + $vars = SystemUtil::makeEnvVarString([ + 'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size + 'EXTRA_LIBS' => "{$this->getOption('extra-libs')} /usr/lib/libm.a", + ]); + + $shell = shell()->cd(SOURCE_PATH . '/php-src'); + $shell->exec('sed -ie "s|//lib|/lib|g" Makefile'); + $shell->exec("make -j{$this->concurrency} {$vars} cli"); + if (!$this->getOption('no-strip', false)) { + $shell->exec('strip sapi/cli/php'); + } + $this->deployBinary(BUILD_TARGET_CLI); + } + + /** + * Build phpmicro sapi + * + * @throws FileSystemException|RuntimeException + */ + public function buildMicro(): void + { + if ($this->getPHPVersionID() < 80000) { + throw new RuntimeException('phpmicro only support PHP >= 8.0!'); + } + if ($this->getExt('phar')) { + $this->phar_patched = true; + SourcePatcher::patchMicro(['phar']); + } + + $enable_fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : ''; + $vars = [ + // with debug information, optimize for size, remove identifiers, patch fake cli for micro + 'EXTRA_CFLAGS' => '-g -Os' . $enable_fake_cli, + // link resolv library (macOS needs it) + 'EXTRA_LIBS' => "{$this->getOption('extra-libs')} /usr/lib/libm.a", + ]; + if (!$this->getOption('no-strip', false)) { + shell()->cd(SOURCE_PATH . '/php-src/sapi/micro')->exec('strip --strip-all micro.sfx'); + } + $vars = SystemUtil::makeEnvVarString($vars); + + shell()->cd(SOURCE_PATH . '/php-src') + ->exec("make -j{$this->concurrency} {$vars} micro"); + $this->deployBinary(BUILD_TARGET_MICRO); + + if ($this->phar_patched) { + SourcePatcher::patchMicro(['phar'], true); + } + } + + /** + * Build fpm sapi + * + * @throws RuntimeException + * @throws FileSystemException + */ + public function buildFpm(): void + { + $vars = SystemUtil::makeEnvVarString([ + 'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size + 'EXTRA_LIBS' => "{$this->getOption('extra-libs')} /usr/lib/libm.a", // link resolv library (macOS needs it) + ]); + + $shell = shell()->cd(SOURCE_PATH . '/php-src'); + $shell->exec("make -j{$this->concurrency} {$vars} fpm"); + if (!$this->getOption('no-strip', false)) { + $shell->exec('strip sapi/fpm/php-fpm'); + } + $this->deployBinary(BUILD_TARGET_FPM); + } + + public function buildEmbed(): void + { + $vars = SystemUtil::makeEnvVarString([ + 'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size + 'EXTRA_LIBS' => "{$this->getOption('extra-libs')} /usr/lib/libm.a", // link resolv library (macOS needs it) + ]); + + shell() + ->cd(SOURCE_PATH . '/php-src') + ->exec('make INSTALL_ROOT=' . BUILD_ROOT_PATH . " -j{$this->concurrency} {$vars} install") + // Workaround for https://github.com/php/php-src/issues/12082 + ->exec('rm -Rf ' . BUILD_ROOT_PATH . '/lib/php-o') + ->exec('mkdir ' . BUILD_ROOT_PATH . '/lib/php-o') + ->cd(BUILD_ROOT_PATH . '/lib/php-o') + ->exec('ar x ' . BUILD_ROOT_PATH . '/lib/libphp.a') + ->exec('rm ' . BUILD_ROOT_PATH . '/lib/libphp.a') + ->exec('ar rcs ' . BUILD_ROOT_PATH . '/lib/libphp.a *.o') + ->exec('rm -Rf ' . BUILD_ROOT_PATH . '/lib/php-o'); + } +} diff --git a/src/SPC/builder/freebsd/SystemUtil.php b/src/SPC/builder/freebsd/SystemUtil.php new file mode 100644 index 00000000..13aaafcc --- /dev/null +++ b/src/SPC/builder/freebsd/SystemUtil.php @@ -0,0 +1,46 @@ +execWithResult('sysctl -n hw.ncpu'); + if ($ret !== 0) { + throw new RuntimeException('Failed to get cpu count'); + } + + return (int) $output[0]; + } + + /** + * Get Target Arch CFlags + * + * @param string $arch Arch Name + * @return string return Arch CFlags string + * @throws WrongUsageException + */ + public static function getArchCFlags(string $arch): string + { + return match ($arch) { + 'amd64', 'x86_64' => '--target=x86_64-unknown-freebsd', + 'arm64','aarch64' => '--target=aarch-unknown-freebsd', + default => throw new WrongUsageException('unsupported arch: ' . $arch), + }; + } +} diff --git a/src/SPC/builder/freebsd/library/BSDLibraryBase.php b/src/SPC/builder/freebsd/library/BSDLibraryBase.php new file mode 100644 index 00000000..6267fbbe --- /dev/null +++ b/src/SPC/builder/freebsd/library/BSDLibraryBase.php @@ -0,0 +1,27 @@ +builder; + } +} diff --git a/src/SPC/builder/freebsd/library/pkgconfig.php b/src/SPC/builder/freebsd/library/pkgconfig.php new file mode 100644 index 00000000..62bd8ace --- /dev/null +++ b/src/SPC/builder/freebsd/library/pkgconfig.php @@ -0,0 +1,15 @@ +findCommand($cmd) === null) { + $missing[] = $cmd; + } + } + if (!empty($missing)) { + return CheckResult::fail('missing system commands: ' . implode(', ', $missing), 'build-tools-bsd', [$missing]); + } + return CheckResult::ok(); + } + + #[AsFixItem('build-tools-bsd')] + public function fixBuildTools(array $missing): bool + { + if (get_current_user() !== 'root') { + $prefix = 'sudo '; + logger()->warning('Current user is not root, using sudo for running command'); + } else { + $prefix = ''; + } + try { + shell(true)->exec("ASSUME_ALWAYS_YES=yes {$prefix} pkg install -y " . implode(' ', $missing)); + } catch (RuntimeException) { + return false; + } + return true; + } +} diff --git a/src/SPC/doctor/item/OSCheckList.php b/src/SPC/doctor/item/OSCheckList.php index 5d074793..7344ecda 100644 --- a/src/SPC/doctor/item/OSCheckList.php +++ b/src/SPC/doctor/item/OSCheckList.php @@ -15,7 +15,7 @@ class OSCheckList #[AsCheckItem('if current OS are supported', level: 999)] public function checkOS(): ?CheckResult { - if (!in_array(PHP_OS_FAMILY, ['Darwin', 'Linux'])) { + if (!in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'BSD'])) { return CheckResult::fail('Current OS is not supported'); } return CheckResult::ok(PHP_OS_FAMILY . ' ' . php_uname('m') . ', supported'); diff --git a/src/SPC/store/Config.php b/src/SPC/store/Config.php index 3a449dfd..01328be2 100644 --- a/src/SPC/store/Config.php +++ b/src/SPC/store/Config.php @@ -54,6 +54,7 @@ class Config 'Windows' => ['-windows', '-win', ''], 'Darwin' => ['-macos', '-unix', ''], 'Linux' => ['-linux', '-unix', ''], + 'BSD' => ['-freebsd', '-bsd', '-unix', ''], default => throw new WrongUsageException('OS ' . PHP_OS_FAMILY . ' is not supported'), }; foreach ($m_key as $v) { @@ -98,6 +99,7 @@ class Config 'Windows' => ['-windows', '-win', ''], 'Darwin' => ['-macos', '-unix', ''], 'Linux' => ['-linux', '-unix', ''], + 'BSD' => ['-freebsd', '-bsd', '-unix', ''], default => throw new WrongUsageException('OS ' . PHP_OS_FAMILY . ' is not supported'), }; foreach ($m_key as $v) { diff --git a/src/SPC/store/FileSystem.php b/src/SPC/store/FileSystem.php index 9cd32df1..192134da 100644 --- a/src/SPC/store/FileSystem.php +++ b/src/SPC/store/FileSystem.php @@ -163,7 +163,7 @@ class FileSystem self::emitSourceExtractHook($name); return; } - if (PHP_OS_FAMILY === 'Darwin' || PHP_OS_FAMILY === 'Linux') { + if (in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'BSD'])) { if (f_mkdir(directory: $target, recursive: true) !== true) { throw new FileSystemException('create ' . $name . 'source dir failed'); } diff --git a/src/globals/functions.php b/src/globals/functions.php index 7f411624..c9ef849a 100644 --- a/src/globals/functions.php +++ b/src/globals/functions.php @@ -60,6 +60,7 @@ function osfamily2dir(): string 'Windows', 'WINNT', 'Cygwin' => 'windows', 'Darwin' => 'macos', 'Linux' => 'linux', + 'BSD' => 'freebsd', default => throw new WrongUsageException('Not support os: ' . PHP_OS_FAMILY), }; }