refactor download

This commit is contained in:
crazywhalecc
2023-04-30 12:42:19 +08:00
parent 117cd93e8f
commit 0bed76da11
30 changed files with 901 additions and 673 deletions

View File

@@ -125,4 +125,12 @@ class Config
}
return self::$ext;
}
public static function getSources(): array
{
if (self::$source === null) {
self::$source = FileSystem::loadConfigArray('source');
}
return self::$source;
}
}

View File

@@ -148,6 +148,37 @@ class Downloader
return [$source['url'] . end($versions), end($versions), key($versions)];
}
/**
* @throws DownloaderException
* @throws RuntimeException
* @throws FileSystemException
*/
public static function downloadFile(string $name, string $url, string $filename, ?string $move_path = null): void
{
logger()->debug("Downloading {$url}");
pcntl_signal(SIGINT, function () use ($filename) {
if (file_exists(DOWNLOAD_PATH . '/' . $filename)) {
logger()->warning('Deleting download file: ' . $filename);
unlink(DOWNLOAD_PATH . '/' . $filename);
}
});
self::curlDown(url: $url, path: DOWNLOAD_PATH . "/{$filename}");
pcntl_signal(SIGINT, SIG_IGN);
logger()->debug("Locking {$filename}");
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path]);
}
public static function lockSource(string $name, array $data): void
{
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
$lock[$name] = $data;
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
}
/**
* 通过链接下载资源到本地并解压
*
@@ -183,25 +214,32 @@ class Downloader
}
}
public static function downloadGit(string $name, string $url, string $branch, ?string $path = null): void
public static function downloadGit(string $name, string $url, string $branch, ?string $move_path = null): void
{
if ($path !== null) {
$path = SOURCE_PATH . "/{$path}";
} else {
$path = DOWNLOAD_PATH . "/{$name}";
}
$download_path = DOWNLOAD_PATH . "/{$name}";
if (file_exists($download_path)) {
logger()->notice("{$name} git source already fetched");
} else {
logger()->debug("cloning {$name} source");
$check = !defined('DEBUG_MODE') ? ' -q' : '';
f_passthru(
'git clone' . $check .
' --config core.autocrlf=false ' .
"--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\""
);
FileSystem::removeDir($download_path);
}
logger()->debug("cloning {$name} source");
$check = !defined('DEBUG_MODE') ? ' -q' : '';
pcntl_signal(SIGINT, function () use ($download_path) {
if (is_dir($download_path)) {
logger()->warning('Removing path ' . $download_path);
FileSystem::removeDir($download_path);
}
});
f_passthru(
'git clone' . $check .
' --config core.autocrlf=false ' .
"--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\""
);
pcntl_signal(SIGINT, SIG_IGN);
// Lock
logger()->debug("Locking git source {$name}");
self::lockSource($name, ['source_type' => 'dir', 'dirname' => $name, 'move_path' => $move_path]);
/*
// 复制目录过去
if ($path !== $download_path) {
$dst_path = FileSystem::convertPath($path);
@@ -215,64 +253,78 @@ class Downloader
f_passthru('cp -r "' . $src_path . '" "' . $dst_path . '"');
break;
}
}
}*/
}
/**
* 拉取资源
*
* @param string $name 资源名称
* @param array $source 资源参数,包含 type、path、rev、url、filename、regex、license
* @param null|array $source 资源参数,包含 type、path、rev、url、filename、regex、license
* @throws DownloaderException
* @throws RuntimeException
* @throws FileSystemException
* @throws RuntimeException
*/
public static function fetchSource(string $name, array $source): void
public static function downloadSource(string $name, ?array $source = null): void
{
// 如果是 git 类型,且没有设置 path那我就需要指定一下 path
if ($source['type'] === 'git' && !isset($source['path'])) {
$source['path'] = $name;
if ($source === null) {
$source = Config::getSource($name);
}
// 避免重复 fetch
if (!isset($source['path']) && is_dir(FileSystem::convertPath(DOWNLOAD_PATH . "/{$name}")) || isset($source['path']) && is_dir(FileSystem::convertPath(SOURCE_PATH . "/{$source['path']}"))) {
logger()->notice("{$name} source already extracted");
return;
// load lock file
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
// If lock file exists, skip downloading
if (isset($lock[$name])) {
if ($lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename'])) {
logger()->notice("source [{$name}] already downloaded: " . $lock[$name]['filename']);
return;
}
if ($lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])) {
logger()->notice("source [{$name}] already downloaded: " . $lock[$name]['dirname']);
return;
}
}
try {
switch ($source['type']) {
case 'bitbuckettag': // 从 BitBucket 的 Tag 拉取
[$url, $filename] = self::getLatestBitbucketTag($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'ghtar': // 从 GitHub 的 TarBall 拉取
[$url, $filename] = self::getLatestGithubTarball($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'ghtagtar': // 根据 GitHub 的 Tag 拉取相应版本的 Tar
[$url, $filename] = self::getLatestGithubTarball($name, $source, 'tags');
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'ghrel': // 通过 GitHub Release 来拉取
[$url, $filename] = self::getLatestGithubRelease($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'filelist': // 通过网站提供的 filelist 使用正则提取后拉取
[$url, $filename] = self::getFromFileList($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'url': // 通过直链拉取
$url = $source['url'];
$filename = $source['filename'] ?? basename($source['url']);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'git': // 通过拉回 Git 仓库的形式拉取
self::downloadGit($name, $source['url'], $source['rev'], $source['path'] ?? null);
break;
case 'custom':
case 'custom': // 自定义,可能是通过复杂 API 形式获取的文件,需要手写 crawler
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\\store\\source');
foreach ($classes as $class) {
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
(new $class())->fetch();
break;
}
}
break;

View File

@@ -6,10 +6,11 @@ namespace SPC\store;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
class FileSystem
{
private static array $_extract_hook = [];
/**
* @throws FileSystemException
*/
@@ -118,20 +119,16 @@ class FileSystem
public static function copyDir(string $from, string $to): void
{
$iterator = new \RecursiveIteratorIterator(new RecursiveDirectoryIterator($from, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_FILEINFO), \RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $item) {
/**
* @var \SplFileInfo $item
*/
$target = $to . substr($item->getPathname(), strlen($from));
if ($item->isDir()) {
logger()->info("mkdir {$target}");
f_mkdir($target, recursive: true);
} else {
logger()->info("copying {$item} to {$target}");
@f_mkdir(dirname($target), recursive: true);
copy($item->getPathname(), $target);
}
$dst_path = FileSystem::convertPath($to);
$src_path = FileSystem::convertPath($from);
switch (PHP_OS_FAMILY) {
case 'Windows':
f_passthru('xcopy "' . $src_path . '" "' . $dst_path . '" /s/e/v/y/i');
break;
case 'Linux':
case 'Darwin':
f_passthru('cp -r "' . $src_path . '" "' . $dst_path . '"');
break;
}
}
@@ -143,41 +140,56 @@ class FileSystem
* @throws FileSystemException
* @throws RuntimeException
*/
public static function extractSource(string $name, string $filename): void
public static function extractSource(string $name, string $filename, ?string $move_path = null): void
{
// if source hook is empty, load it
if (self::$_extract_hook === []) {
SourcePatcher::init();
}
if ($move_path !== null) {
$move_path = SOURCE_PATH . '/' . $move_path;
}
logger()->info("extracting {$name} source");
try {
if (PHP_OS_FAMILY !== 'Windows') {
if (f_mkdir(directory: SOURCE_PATH . "/{$name}", recursive: true) !== true) {
$target = $move_path ?? (SOURCE_PATH . "/{$name}");
// Git source, just move
if (is_dir($filename)) {
self::copyDir($filename, $target);
self::emitSourceExtractHook($name);
return;
}
if (PHP_OS_FAMILY === 'Darwin' || PHP_OS_FAMILY === 'Linux') {
if (f_mkdir(directory: $target, recursive: true) !== true) {
throw new FileSystemException('create ' . $name . 'source dir failed');
}
switch (self::extname($filename)) {
case 'xz':
case 'txz':
f_passthru("tar -xf {$filename} -C " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1");
// f_passthru("cat {$filename} | xz -d | tar -x -C " . SOURCE_PATH . "/{$name} --strip-components 1");
break;
case 'gz':
case 'tgz':
f_passthru("tar -xzf {$filename} -C " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1");
break;
case 'bz2':
f_passthru("tar -xjf {$filename} -C " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1");
break;
case 'zip':
f_passthru("unzip {$filename} -d " . SOURCE_PATH . "/{$name}");
f_passthru("unzip {$filename} -d {$target}");
break;
// case 'zstd':
// case 'zst':
// passthru('cat ' . $filename . ' | zstd -d | tar -x -C ".SOURCE_PATH . "/' . $name . ' --strip-components 1', $ret);
// break;
case 'tar':
f_passthru("tar -xf {$filename} -C " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1");
break;
default:
throw new FileSystemException('unknown archive format: ' . $filename);
}
} else {
} elseif (PHP_OS_FAMILY === 'Windows') {
// find 7z
$_7zExe = self::findCommandPath('7z', [
'C:\Program Files\7-Zip-Zstandard',
@@ -201,13 +213,13 @@ class FileSystem
case 'gz':
case 'tgz':
case 'bz2':
f_passthru("\"{$_7zExe}\" x -so {$filename} | tar -f - -x -C " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("\"{$_7zExe}\" x -so {$filename} | tar -f - -x -C {$target} --strip-components 1");
break;
case 'tar':
f_passthru("tar -xf {$filename} -C " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1");
break;
case 'zip':
f_passthru("\"{$_7zExe}\" x {$filename} -o" . SOURCE_PATH . "/{$name}");
f_passthru("\"{$_7zExe}\" x {$filename} -o{$target}");
break;
default:
throw new FileSystemException("unknown archive format: {$filename}");
@@ -421,4 +433,18 @@ class FileSystem
}
self::createDir($dir_name);
}
public static function addSourceExtractHook(string $name, callable $callback)
{
self::$_extract_hook[$name][] = $callback;
}
private static function emitSourceExtractHook(string $name)
{
foreach ((self::$_extract_hook[$name] ?? []) as $hook) {
if ($hook($name) === true) {
logger()->info('Patched source [' . $name . '] after extracted');
}
}
}
}

View File

@@ -0,0 +1,212 @@
<?php
declare(strict_types=1);
namespace SPC\store;
use SPC\builder\BuilderBase;
use SPC\builder\linux\LinuxBuilder;
use SPC\builder\macos\MacOSBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\util\Util;
class SourcePatcher
{
public static function init()
{
FileSystem::addSourceExtractHook('swow', [SourcePatcher::class, 'patchSwow']);
FileSystem::addSourceExtractHook('micro', [SourcePatcher::class, 'patchMicro']);
FileSystem::addSourceExtractHook('openssl', [SourcePatcher::class, 'patchOpenssl3']);
FileSystem::addSourceExtractHook('openssl', [SourcePatcher::class, 'patchOpenssl11Darwin']);
}
public static function patchPHPBuildconf(BuilderBase $builder): void
{
if ($builder->getExt('curl')) {
logger()->info('patching before-configure for curl checks');
$file1 = "AC_DEFUN([PHP_CHECK_LIBRARY], [\n $3\n])";
$files = FileSystem::readFile(SOURCE_PATH . '/php-src/ext/curl/config.m4');
$file2 = 'AC_DEFUN([PHP_CHECK_LIBRARY], [
save_old_LDFLAGS=$LDFLAGS
ac_stuff="$5"
save_ext_shared=$ext_shared
ext_shared=yes
PHP_EVAL_LIBLINE([$]ac_stuff, LDFLAGS)
AC_CHECK_LIB([$1],[$2],[
LDFLAGS=$save_old_LDFLAGS
ext_shared=$save_ext_shared
$3
],[
LDFLAGS=$save_old_LDFLAGS
ext_shared=$save_ext_shared
unset ac_cv_lib_$1[]_$2
$4
])dnl
])';
file_put_contents(SOURCE_PATH . '/php-src/ext/curl/config.m4', $file1 . "\n" . $files . "\n" . $file2);
}
// if ($builder->getExt('pdo_sqlite')) {
// FileSystem::replaceFile()
// }
}
public static function patchSwow(): bool
{
if (Util::getPHPVersionID() >= 80000) {
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('cd ' . SOURCE_PATH . '/php-src/ext && mklink /D swow swow-src\ext');
} else {
f_passthru('cd ' . SOURCE_PATH . '/php-src/ext && ln -s swow-src/ext swow');
}
return true;
}
return false;
}
public static function patchPHPConfigure(BuilderBase $builder): void
{
$frameworks = $builder instanceof MacOSBuilder ? ' ' . $builder->getFrameworks(true) . ' ' : '';
$patch = [];
if ($curl = $builder->getExt('curl')) {
$patch[] = ['curl check', '/-lcurl/', $curl->getLibFilesString() . $frameworks];
}
if ($bzip2 = $builder->getExt('bz2')) {
$patch[] = ['bzip2 check', '/-lbz2/', $bzip2->getLibFilesString() . $frameworks];
}
if ($pdo_sqlite = $builder->getExt('pdo_sqlite')) {
$patch[] = ['pdo_sqlite linking', '/sqlite3_column_table_name=yes/', 'sqlite3_column_table_name=no'];
}
$patch[] = ['disable capstone', '/have_capstone="yes"/', 'have_capstone="no"'];
foreach ($patch as $item) {
logger()->info('Patching configure: ' . $item[0]);
FileSystem::replaceFile(SOURCE_PATH . '/php-src/configure', REPLACE_FILE_PREG, $item[1], $item[2]);
}
}
public static function patchUnixLibpng(): void
{
FileSystem::replaceFile(
SOURCE_PATH . '/libpng/configure',
REPLACE_FILE_STR,
'-lz',
BUILD_LIB_PATH . '/libz.a'
);
}
public static function patchCurlMacOS(): void
{
FileSystem::replaceFile(
SOURCE_PATH . '/curl/CMakeLists.txt',
REPLACE_FILE_PREG,
'/NOT COREFOUNDATION_FRAMEWORK/m',
'FALSE'
);
FileSystem::replaceFile(
SOURCE_PATH . '/curl/CMakeLists.txt',
REPLACE_FILE_PREG,
'/NOT SYSTEMCONFIGURATION_FRAMEWORK/m',
'FALSE'
);
}
public static function patchMicro(): bool
{
if (!file_exists(SOURCE_PATH . '/php-src/sapi/micro/php_micro.c')) {
return false;
}
$ver_file = SOURCE_PATH . '/php-src/main/php_version.h';
if (!file_exists($ver_file)) {
throw new FileSystemException('Patch failed, cannot find php source files');
}
$version_h = FileSystem::readFile(SOURCE_PATH . '/php-src/main/php_version.h');
preg_match('/#\s*define\s+PHP_MAJOR_VERSION\s+(\d+)\s+#\s*define\s+PHP_MINOR_VERSION\s+(\d+)\s+/m', $version_h, $match);
// $ver = "{$match[1]}.{$match[2]}";
$major_ver = $match[1] . $match[2];
if ($major_ver === '74') {
return false;
}
$check = !defined('DEBUG_MODE') ? ' -q' : '';
// f_passthru('cd ' . SOURCE_PATH . '/php-src && git checkout' . $check . ' HEAD');
$patch_list = [
'static_opcache',
'static_extensions_win32',
'cli_checks',
'disable_huge_page',
'vcruntime140',
'win32',
'zend_stream',
];
$patch_list = array_merge($patch_list, match (PHP_OS_FAMILY) {
'Windows' => [
'cli_static',
],
'Darwin' => [
'macos_iconv',
],
default => [],
});
$patches = [];
$serial = ['80', '81', '82'];
foreach ($patch_list as $patchName) {
if (file_exists(SOURCE_PATH . "/php-src/sapi/micro/patches/{$patchName}.patch")) {
$patches[] = "sapi/micro/patches/{$patchName}.patch";
continue;
}
for ($i = array_search($major_ver, $serial, true); $i >= 0; --$i) {
$tryMajMin = $serial[$i];
if (!file_exists(SOURCE_PATH . "/php-src/sapi/micro/patches/{$patchName}_{$tryMajMin}.patch")) {
continue;
}
$patches[] = "sapi/micro/patches/{$patchName}_{$tryMajMin}.patch";
continue 2;
}
throw new RuntimeException("failed finding patch {$patchName}");
}
$patchesStr = str_replace('/', DIRECTORY_SEPARATOR, implode(' ', $patches));
f_passthru(
'cd ' . SOURCE_PATH . '/php-src && ' .
(PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . $patchesStr . ' | patch -p1'
);
return true;
}
public static function patchOpenssl3(): bool
{
if (file_exists(SOURCE_PATH . '/openssl/VERSION.dat') && Util::getPHPVersionID() >= 80000) {
$openssl_c = file_get_contents(SOURCE_PATH . '/php-src/ext/openssl/openssl.c');
$openssl_c = preg_replace('/REGISTER_LONG_CONSTANT\s*\(\s*"OPENSSL_SSLV23_PADDING"\s*.+;/', '', $openssl_c);
file_put_contents(SOURCE_PATH . '/php-src/ext/openssl/openssl.c', $openssl_c);
return true;
}
return false;
}
public static function patchOpenssl11Darwin(): bool
{
if (PHP_OS_FAMILY === 'Darwin' && !file_exists(SOURCE_PATH . '/openssl/VERSION.dat') && file_exists(SOURCE_PATH . '/openssl/test/v3ext.c')) {
FileSystem::replaceFile(
SOURCE_PATH . '/openssl/test/v3ext.c',
REPLACE_FILE_STR,
'#include <stdio.h>',
'#include <stdio.h>' . PHP_EOL . '#include <string.h>'
);
return true;
}
return false;
}
public static function patchPHPAfterConfigure(BuilderBase $param)
{
if ($param instanceof LinuxBuilder) {
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_STRLCPY 1$/m', '');
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_STRLCAT 1$/m', '');
}
}
}

View File

@@ -6,7 +6,6 @@ namespace SPC\store\source;
use JetBrains\PhpStorm\ArrayShape;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\Downloader;
@@ -16,13 +15,12 @@ class PhpSource extends CustomSourceBase
/**
* @throws DownloaderException
* @throws FileSystemException
* @throws RuntimeException
*/
public function fetch()
{
$major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.1';
Downloader::fetchSource('php-src', self::getLatestPHPInfo($major));
Downloader::downloadSource('php-src', self::getLatestPHPInfo($major));
}
/**

View File

@@ -12,7 +12,7 @@ class PostgreSQLSource extends CustomSourceBase
public function fetch()
{
Downloader::fetchSource('postgresql', self::getLatestInfo());
Downloader::downloadSource('postgresql', self::getLatestInfo());
}
public function getLatestInfo(): array