mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-21 15:55:36 +08:00
Fix extracting hosted type for zip archive caused overwriting
This commit is contained in:
@@ -242,7 +242,10 @@ class ArtifactExtractor
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger()->info("Extracting binary [{$name}] to {$target_path}...");
|
logger()->info("Extracting binary [{$name}] to {$target_path}...");
|
||||||
$this->doStandardExtract($name, $cache_info, $target_path);
|
// When a binary artifact targets the shared buildroot, merge into it instead of wiping it.
|
||||||
|
// Wiping buildroot would destroy files installed by packages processed earlier in the build queue.
|
||||||
|
$merge = (FileSystem::convertPath($target_path) === FileSystem::convertPath(BUILD_ROOT_PATH));
|
||||||
|
$this->doStandardExtract($name, $cache_info, $target_path, $merge);
|
||||||
|
|
||||||
$artifact->emitAfterBinaryExtract($target_path, $platform);
|
$artifact->emitAfterBinaryExtract($target_path, $platform);
|
||||||
logger()->debug("Emitted after-binary-extract hooks for [{$name}]");
|
logger()->debug("Emitted after-binary-extract hooks for [{$name}]");
|
||||||
@@ -256,8 +259,10 @@ class ArtifactExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard extraction: extract entire archive to target directory.
|
* Standard extraction: extract entire archive to target directory.
|
||||||
|
*
|
||||||
|
* @param bool $merge when true, merge extracted files into existing target dir instead of wiping it
|
||||||
*/
|
*/
|
||||||
protected function doStandardExtract(string $name, array $cache_info, string $target_path): void
|
protected function doStandardExtract(string $name, array $cache_info, string $target_path, bool $merge = false): void
|
||||||
{
|
{
|
||||||
$source_file = $this->cache->getCacheFullPath($cache_info);
|
$source_file = $this->cache->getCacheFullPath($cache_info);
|
||||||
$cache_type = $cache_info['cache_type'];
|
$cache_type = $cache_info['cache_type'];
|
||||||
@@ -265,7 +270,7 @@ class ArtifactExtractor
|
|||||||
// Validate source file exists before extraction
|
// Validate source file exists before extraction
|
||||||
$this->validateSourceFile($name, $source_file, $cache_type);
|
$this->validateSourceFile($name, $source_file, $cache_type);
|
||||||
|
|
||||||
$this->extractWithType($cache_type, $source_file, $target_path);
|
$this->extractWithType($cache_type, $source_file, $target_path, $merge);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -443,10 +448,10 @@ class ArtifactExtractor
|
|||||||
* @param string $source_file Path to source file or directory
|
* @param string $source_file Path to source file or directory
|
||||||
* @param string $target_path Target extraction path
|
* @param string $target_path Target extraction path
|
||||||
*/
|
*/
|
||||||
protected function extractWithType(string $cache_type, string $source_file, string $target_path): void
|
protected function extractWithType(string $cache_type, string $source_file, string $target_path, bool $merge = false): void
|
||||||
{
|
{
|
||||||
match ($cache_type) {
|
match ($cache_type) {
|
||||||
'archive' => $this->extractArchive($source_file, $target_path),
|
'archive' => $this->extractArchive($source_file, $target_path, $merge),
|
||||||
'file' => $this->copyFile($source_file, $target_path),
|
'file' => $this->copyFile($source_file, $target_path),
|
||||||
'git' => FileSystem::copyDir(FileSystem::convertPath($source_file), $target_path),
|
'git' => FileSystem::copyDir(FileSystem::convertPath($source_file), $target_path),
|
||||||
'local' => symlink(FileSystem::convertPath($source_file), $target_path),
|
'local' => symlink(FileSystem::convertPath($source_file), $target_path),
|
||||||
@@ -458,8 +463,10 @@ class ArtifactExtractor
|
|||||||
* Extract archive file to target directory.
|
* Extract archive file to target directory.
|
||||||
*
|
*
|
||||||
* Supports: tar, tar.gz, tgz, tar.bz2, tar.xz, txz, zip, exe
|
* Supports: tar, tar.gz, tgz, tar.bz2, tar.xz, txz, zip, exe
|
||||||
|
*
|
||||||
|
* @param bool $merge when true, merge zip contents into existing target dir instead of wiping it
|
||||||
*/
|
*/
|
||||||
protected function extractArchive(string $filename, string $target): void
|
protected function extractArchive(string $filename, string $target, bool $merge = false): void
|
||||||
{
|
{
|
||||||
$target = FileSystem::convertPath($target);
|
$target = FileSystem::convertPath($target);
|
||||||
$filename = FileSystem::convertPath($filename);
|
$filename = FileSystem::convertPath($filename);
|
||||||
@@ -476,7 +483,7 @@ class ArtifactExtractor
|
|||||||
'Windows' => match ($extname) {
|
'Windows' => match ($extname) {
|
||||||
'tar' => default_shell()->executeTarExtract($filename, $target, 'none'),
|
'tar' => default_shell()->executeTarExtract($filename, $target, 'none'),
|
||||||
'xz', 'txz', 'gz', 'tgz', 'bz2' => default_shell()->execute7zExtract($filename, $target),
|
'xz', 'txz', 'gz', 'tgz', 'bz2' => default_shell()->execute7zExtract($filename, $target),
|
||||||
'zip' => $this->unzipWithStrip($filename, $target),
|
'zip' => $this->unzipWithStrip($filename, $target, $merge),
|
||||||
'exe' => $this->copyFile($filename, $target),
|
'exe' => $this->copyFile($filename, $target),
|
||||||
default => throw new FileSystemException("Unknown archive format: {$filename}"),
|
default => throw new FileSystemException("Unknown archive format: {$filename}"),
|
||||||
},
|
},
|
||||||
@@ -485,7 +492,7 @@ class ArtifactExtractor
|
|||||||
'gz', 'tgz' => default_shell()->executeTarExtract($filename, $target, 'gz'),
|
'gz', 'tgz' => default_shell()->executeTarExtract($filename, $target, 'gz'),
|
||||||
'bz2' => default_shell()->executeTarExtract($filename, $target, 'bz2'),
|
'bz2' => default_shell()->executeTarExtract($filename, $target, 'bz2'),
|
||||||
'xz', 'txz' => default_shell()->executeTarExtract($filename, $target, 'xz'),
|
'xz', 'txz' => default_shell()->executeTarExtract($filename, $target, 'xz'),
|
||||||
'zip' => $this->unzipWithStrip($filename, $target),
|
'zip' => $this->unzipWithStrip($filename, $target, $merge),
|
||||||
'exe' => $this->copyFile($filename, $target),
|
'exe' => $this->copyFile($filename, $target),
|
||||||
default => throw new FileSystemException("Unknown archive format: {$filename}"),
|
default => throw new FileSystemException("Unknown archive format: {$filename}"),
|
||||||
},
|
},
|
||||||
@@ -496,7 +503,7 @@ class ArtifactExtractor
|
|||||||
/**
|
/**
|
||||||
* Unzip file with stripping top-level directory.
|
* Unzip file with stripping top-level directory.
|
||||||
*/
|
*/
|
||||||
protected function unzipWithStrip(string $zip_file, string $extract_path): bool
|
protected function unzipWithStrip(string $zip_file, string $extract_path, bool $merge = false): bool
|
||||||
{
|
{
|
||||||
$temp_dir = FileSystem::convertPath(sys_get_temp_dir() . '/spc_unzip_' . bin2hex(random_bytes(16)));
|
$temp_dir = FileSystem::convertPath(sys_get_temp_dir() . '/spc_unzip_' . bin2hex(random_bytes(16)));
|
||||||
$zip_file = FileSystem::convertPath($zip_file);
|
$zip_file = FileSystem::convertPath($zip_file);
|
||||||
@@ -517,15 +524,22 @@ class ArtifactExtractor
|
|||||||
throw new FileSystemException('Cannot scan unzip temp dir: ' . $temp_dir);
|
throw new FileSystemException('Cannot scan unzip temp dir: ' . $temp_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If extract path already exists, remove it
|
if (!$merge) {
|
||||||
if (is_dir($extract_path)) {
|
// Replace mode: wipe the target directory before extracting
|
||||||
FileSystem::removeDir($extract_path);
|
if (is_dir($extract_path)) {
|
||||||
|
FileSystem::removeDir($extract_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If only one dir, move its contents to extract_path
|
// If only one dir, move/merge its contents to extract_path
|
||||||
$subdir = FileSystem::convertPath("{$temp_dir}/{$contents[0]}");
|
$subdir = FileSystem::convertPath("{$temp_dir}/{$contents[0]}");
|
||||||
if (count($contents) === 1 && is_dir($subdir)) {
|
if (count($contents) === 1 && is_dir($subdir)) {
|
||||||
$this->moveFileOrDir($subdir, $extract_path);
|
if ($merge) {
|
||||||
|
$this->mergeDirContent($subdir, $extract_path);
|
||||||
|
FileSystem::removeDir($subdir);
|
||||||
|
} else {
|
||||||
|
$this->moveFileOrDir($subdir, $extract_path);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Else, if it contains only one dir, strip dir and copy other files
|
// Else, if it contains only one dir, strip dir and copy other files
|
||||||
$dircount = 0;
|
$dircount = 0;
|
||||||
@@ -550,26 +564,36 @@ class ArtifactExtractor
|
|||||||
throw new FileSystemException("Cannot scan unzip temp sub-dir: {$dir[0]}");
|
throw new FileSystemException("Cannot scan unzip temp sub-dir: {$dir[0]}");
|
||||||
}
|
}
|
||||||
foreach ($sub_contents as $sub_item) {
|
foreach ($sub_contents as $sub_item) {
|
||||||
$this->moveFileOrDir(
|
$src = FileSystem::convertPath("{$temp_dir}/{$dir[0]}/{$sub_item}");
|
||||||
FileSystem::convertPath("{$temp_dir}/{$dir[0]}/{$sub_item}"),
|
$dst = FileSystem::convertPath("{$extract_path}/{$sub_item}");
|
||||||
FileSystem::convertPath("{$extract_path}/{$sub_item}")
|
if ($merge && is_dir($src)) {
|
||||||
);
|
$this->mergeDirContent($src, $dst);
|
||||||
|
} else {
|
||||||
|
$this->moveFileOrDir($src, $dst);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($dir as $item) {
|
foreach ($dir as $item) {
|
||||||
$this->moveFileOrDir(
|
$src = FileSystem::convertPath("{$temp_dir}/{$item}");
|
||||||
FileSystem::convertPath("{$temp_dir}/{$item}"),
|
$dst = FileSystem::convertPath("{$extract_path}/{$item}");
|
||||||
FileSystem::convertPath("{$extract_path}/{$item}")
|
if ($merge) {
|
||||||
);
|
$this->mergeDirContent($src, $dst);
|
||||||
|
} else {
|
||||||
|
$this->moveFileOrDir($src, $dst);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move top-level files to extract_path
|
// Move or copy top-level files to extract_path
|
||||||
foreach ($top_files as $top_file) {
|
foreach ($top_files as $top_file) {
|
||||||
$this->moveFileOrDir(
|
$src = FileSystem::convertPath("{$temp_dir}/{$top_file}");
|
||||||
FileSystem::convertPath("{$temp_dir}/{$top_file}"),
|
$dst = FileSystem::convertPath("{$extract_path}/{$top_file}");
|
||||||
FileSystem::convertPath("{$extract_path}/{$top_file}")
|
if ($merge) {
|
||||||
);
|
FileSystem::createDir(dirname($dst));
|
||||||
|
copy($src, $dst);
|
||||||
|
} else {
|
||||||
|
$this->moveFileOrDir($src, $dst);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,6 +619,25 @@ class ArtifactExtractor
|
|||||||
return str_replace(array_keys($replacement), array_values($replacement), $path);
|
return str_replace(array_keys($replacement), array_values($replacement), $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function mergeDirContent(string $src_dir, string $dest_dir): void
|
||||||
|
{
|
||||||
|
FileSystem::createDir($dest_dir);
|
||||||
|
$items = FileSystem::scanDirFiles($src_dir, false, true, true);
|
||||||
|
if ($items === false || empty($items)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$src_item = FileSystem::convertPath("{$src_dir}/{$item}");
|
||||||
|
$dest_item = FileSystem::convertPath("{$dest_dir}/{$item}");
|
||||||
|
if (is_dir($src_item)) {
|
||||||
|
$this->mergeDirContent($src_item, $dest_item);
|
||||||
|
} else {
|
||||||
|
FileSystem::createDir(dirname($dest_item));
|
||||||
|
copy($src_item, $dest_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move file or directory, handling cross-device scenarios
|
* Move file or directory, handling cross-device scenarios
|
||||||
* Uses rename() if possible, falls back to copy+delete for cross-device moves
|
* Uses rename() if possible, falls back to copy+delete for cross-device moves
|
||||||
|
|||||||
Reference in New Issue
Block a user