Author: Shivam Mathur (shivammathur) Date: 2024-08-12T17:04:41+05:30 Commit: https://github.com/php/web-downloads/commit/1e5da29076b4a62c4e087cb53265526ed9c1129b Raw diff: https://github.com/php/web-downloads/commit/1e5da29076b4a62c4e087cb53265526ed9c1129b.diff
Add support for PHP builds Changed paths: M src/PeclHandler.php M src/PhpHandler.php M src/WinlibsHandler.php Diff: diff --git a/src/PeclHandler.php b/src/PeclHandler.php index 2cfea4a..03bb7eb 100644 --- a/src/PeclHandler.php +++ b/src/PeclHandler.php @@ -43,11 +43,11 @@ protected function execute(array $data): void */ private function fetchExtension(string $extension, string $ref, string $url, string $token): void { - $filepath = "/tmp/$extension-$ref-" . strtotime('now') . ".zip"; + $filepath = "/tmp/$extension-$ref-" . hash('sha256', $url) . strtotime('now') . ".zip"; FetchArtifact::handle($url, $filepath, $token); - if(!file_exists($filepath) || !mime_content_type($filepath) === 'application/zip') { + if(!file_exists($filepath) || mime_content_type($filepath) !== 'application/zip') { throw new Exception('Failed to fetch the extension'); } diff --git a/src/PhpHandler.php b/src/PhpHandler.php index 617c915..1c02964 100644 --- a/src/PhpHandler.php +++ b/src/PhpHandler.php @@ -2,20 +2,290 @@ namespace App; +use DateTimeImmutable; +use Exception; +use ZipArchive; + class PhpHandler extends BaseHandler { + protected function validate(array $data): bool + { + $validator = new Validator([ + 'url' => 'required|url', + 'token' => 'required|string', + ]); + + $validator->validate($data); + + $valid = $validator->isValid(); + + if(!$valid) { + http_response_code(400); + echo 'Invalid request: ' . $validator; + } + + return $valid; + } - public function handle(): void + protected function execute(array $data): void { + try { + extract($data); + $this->fetchPhpBuild($url, $token); + } catch (Exception $exception) { + http_response_code(500); + echo 'Error: ' . $exception->getMessage(); + } } - protected function validate(array $data): bool + /** + * @throws Exception + */ + private function fetchPhpBuild(string $url, string $token): void + { + $hash = hash('sha256', $url) . strtotime('now'); + + $filepath = "/tmp/php-" . $hash . ".tar.gz"; + + FetchArtifact::handle($url, $filepath, $token); + + if(!file_exists($filepath) || mime_content_type($filepath) !== 'application/zip') { + throw new Exception('Failed to fetch the PHP build'); + } + + $tempDirectory = "/tmp/php-" . $hash; + + if(is_dir($tempDirectory)) { + rmdir($tempDirectory); + } + mkdir($tempDirectory, 0755, true); + + $zip = new ZipArchive(); + + if ($zip->open($filepath) === TRUE) { + if($zip->extractTo($tempDirectory) === FALSE) { + throw new Exception('Failed to extract the extension build'); + } + $zip->close(); + } else { + throw new Exception('Failed to extract the extension'); + } + + unlink($filepath); + + $destinationDirectory = $this->getDestinationDirectory($tempDirectory); + + $this->moveBuild($tempDirectory, $destinationDirectory); + + $this->generateListing($destinationDirectory); + } + + private function getDestinationDirectory(string $tempDirectory): string { - return true; + $testPackFile = basename(glob($tempDirectory . '/php-test-pack-*.zip')[0]); + $testPackFileName = str_replace('.zip', '', $testPackFile); + $version = explode('-', $testPackFileName)[3]; + return $_ENV['BUILDS_DIRECTORY'] . (preg_match('/^\d+\.\d+\.\d+$/', $version) ? '/releases' : '/qa'); } - protected function execute(array $data): void + /** + * @throws Exception + */ + private function moveBuild(string $tempDirectory, string $destinationDirectory): void { - // + $files = glob($tempDirectory . '/*'); + if($files) { + $version = $this->getFileVersion($files[0]); + foreach ($files as $file) { + $fileName = basename($file); + $destination = $destinationDirectory . '/' . $fileName; + rename($file, $destination); + } + rmdir($tempDirectory); + $this->copyBuildsToArchive($destinationDirectory, $version); + } else { + throw new Exception('No builds found in the artifact'); + } + } + + private function copyBuildsToArchive(string $directory, string $version): void + { + $version_short = substr($version, 0, 3); + $files = glob($directory . '/php*-' . $version_short . '-*.zip'); + foreach ($files as $file) { + $fileVersion = $this->getFileVersion($file); + if($fileVersion) { + copy($directory . '/' . basename($file), $directory . '/archive/' . $file); + if(version_compare($fileVersion, $version) < 0) { + unlink($file); + } + } + } + } + + private function getFileVersion(string $file): string + { + $file = preg_replace('/^php-((debug|devel|test)-pack)?/', '', $file); + return explode('-', $file)[0]; + } + + /** + * @throws Exception + */ + private function generateListing(string $directory): void + { + $builds = glob($directory . '/php-[678].*[0-9]-latest.zip'); + if (empty($builds)) { + $builds = glob($directory . '/php-[678].*[0-9].zip'); + } + + $releases = []; + $sha256sums = $this->getSha256Sums($directory); + foreach ($builds as $file) { + $file_ori = $file; + $mtime = date('Y-M-d H:i:s', filemtime($file)); + + $parts = $this->parseFileName(basename($file)); + $key = ($parts['nts'] ? 'nts-' : 'ts-') . $parts['vc'] . '-' . $parts['arch']; + $version_short = $parts['version_short']; + if (!isset($releases['version'])) { + $releases[$version_short]['version'] = $parts['version']; + } + $releases[$version_short][$key]['mtime'] = $mtime; + $releases[$version_short][$key]['zip'] = [ + 'path' => $file_ori, + 'size' => $this->bytes2string(filesize($file_ori)), + 'sha256' => $sha256sums[strtolower($file_ori)] + ]; + $namingPattern = $parts['version'] . ($parts['nts'] ? '-' . $parts['nts'] : '') . '-Win32-' . $parts['vc'] . '-' . $parts['arch'] . ($parts['ts'] ? '-' . $parts['ts'] : ''); + $build_types = [ + 'source' => 'php-' . $parts['version'] . '-src.zip', + 'debug_pack' => 'php-debug-pack-' . $namingPattern . '.zip', + 'devel_pack' => 'php-devel-pack-' . $namingPattern . '.zip', + 'installer' => 'php-' . $namingPattern . '.msi', + 'test_pack' => 'php-test-pack-' . $parts['version'] . '.zip', + ]; + foreach($build_types as $type => $fileName) { + $filePath = $directory . '/' . $fileName; + if (file_exists($filePath)) { + $releases[$version_short][$type] = [ + 'path' => $fileName, + 'size' => $this->bytes2string(filesize($filePath)) + ]; + } + } + } + + $this->updateReleasesJson($releases, $directory); + if($directory === $_ENV['BUILDS_DIRECTORY'] . '/releases') { + $this->updateLatestBuilds($releases, $directory); + } + } + + /** + * @throws Exception + */ + private function updateReleasesJson(array $releases, string $directory): void + { + foreach ($releases as &$release) { + foreach ($release as &$build_type) { + if (! is_array($build_type) || ! isset($build_type['mtime'])) { + continue; + } + + try { + $date = new DateTimeImmutable($build_type['mtime']); + $build_type['mtime'] = $date->format('c'); + } catch (Exception $exception) { + throw new Exception('Failed to generate releases.json: ' . $exception->getMessage()); + } + } + unset($build_type); + } + unset($release); + + file_put_contents( + $directory . '/releases.json', + json_encode($releases, JSON_PRETTY_PRINT) + ); + } + + private function updateLatestBuilds($releases, $directory): void + { + foreach ($releases as $versionShort => $release) { + $latestFileName = str_replace($release['version'], $versionShort, $release['path']); + $latestFileName = str_replace('.zip', '-latest.zip', $latestFileName); + copy($directory . '/' . $release['path'], $directory . '/latest/' . $latestFileName); + } + } + + private function getSha256Sums($directory): array + { + $result = []; + $sha_file = fopen("$directory/sha256sum.txt", 'w'); + foreach (scandir($directory) as $filename) { + if (pathinfo($filename, PATHINFO_EXTENSION) !== 'zip') { + continue; + } + $sha256 = hash_file('sha256', "$directory/$filename"); + fwrite($sha_file, "$sha256 *$filename\n"); + $result[strtolower(basename($filename))] = $sha256; + } + fclose($sha_file); + return $result; + } + + private function bytes2string(int $size): float + { + $sizes = ['YB', 'ZB', 'EB', 'PB', 'TB', 'GB', 'MB', 'kB', 'B']; + + $total = count($sizes); + + while($total-- && $size > 1024) $size /= 1024; + + return round($size, 2) . $sizes[$total]; + } + + private function parseFileName($fileName): array + { + $fileName = str_replace(['-Win32', '.zip'], ['', ''], $fileName); + + $parts = explode('-', $fileName); + if (is_numeric($parts[2]) || $parts[2] == 'dev') { + $version = $parts[1] . '-' . $parts[2]; + $nts = $parts[3] == 'nts' ? 'nts' : false; + if ($nts) { + $vc = $parts[4]; + $arch = $parts[5]; + } else { + $vc = $parts[3]; + $arch = $parts[4]; + } + } elseif ($parts[2] == 'nts') { + $nts = 'nts'; + $version = $parts[1]; + $vc = $parts[3]; + $arch = $parts[4]; + } else { + $nts = false; + $version = $parts[1]; + $vc = $parts[2]; + $arch = $parts[3]; + } + if (is_numeric($vc)) { + $vc = 'VC6'; + $arch = 'x86'; + } + $t = count($parts) - 1; + $ts = is_numeric($parts[$t]) ? $parts[$t] : false; + + return [ + 'version' => $version, + 'version_short' => substr($version, 0, 3), + 'nts' => $nts, + 'vc' => $vc, + 'arch' => $arch, + 'ts' => $ts + ]; } } \ No newline at end of file diff --git a/src/WinlibsHandler.php b/src/WinlibsHandler.php index c541f82..63684b9 100644 --- a/src/WinlibsHandler.php +++ b/src/WinlibsHandler.php @@ -4,11 +4,6 @@ class WinlibsHandler extends BaseHandler { - - public function handle(): void - { - } - protected function validate(array $data): bool { return true;