Skip to content

Commit 2080407

Browse files
committed
Enhance Windows support by updating artifact configuration and improving extraction logic
1 parent dc05ad2 commit 2080407

21 files changed

+384
-183
lines changed

captainhook.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"enabled": true,
1212
"actions": [
1313
{
14-
"action": ".\\vendor\\bin\\php-cs-fixer fix --config=.php-cs-fixer.php --dry-run --diff {$STAGED_FILES|of-type:php}",
14+
"action": ".\\vendor\\bin\\php-cs-fixer fix --config=.php-cs-fixer.php --dry-run --diff {$STAGED_FILES|of-type:php} --sequential",
1515
"conditions": [
1616
{
1717
"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\OfType",

config/artifact.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@
8080
},
8181
"strawberry-perl": {
8282
"binary": {
83-
"windows-x86_64": "https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip"
83+
"windows-x86_64": {
84+
"type": "url",
85+
"url": "https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip",
86+
"extract": "{pkg_root_path}/strawberry-perl"
87+
}
8488
}
8589
},
8690
"upx": {

config/pkg.target.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
{
22
"vswhere": {
33
"type": "target",
4-
"artifact": "vswhere"
4+
"artifact": "vswhere",
5+
"static-bins@windows": [
6+
"vswhere.exe"
7+
]
58
},
69
"pkg-config": {
710
"type": "target",

src/StaticPHP/Artifact/Artifact.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,22 @@ public function isBinaryExtracted(?string $target_os = null, bool $compare_hash
167167
return false;
168168
}
169169

170-
// For standalone mode, check directory and hash
170+
// For standalone mode, check directory or file and hash
171171
$target_path = $extract_config['path'];
172172

173-
if (!is_dir($target_path)) {
174-
return false;
173+
// Check if target is a file or directory
174+
$is_file_target = !is_dir($target_path) && str_contains($target_path, '.');
175+
176+
if ($is_file_target) {
177+
// For single file extraction (e.g., vswhere.exe)
178+
if (!file_exists($target_path)) {
179+
return false;
180+
}
181+
} else {
182+
// For directory extraction
183+
if (!is_dir($target_path)) {
184+
return false;
185+
}
175186
}
176187

177188
if (!$compare_hash) {
@@ -320,7 +331,7 @@ public function getBinaryExtractConfig(array $cache_info = []): array
320331
* For merge mode, returns the base path.
321332
* For standalone mode, returns the specific directory.
322333
*/
323-
public function getBinaryDir(): string
334+
public function getBinaryDir(): ?string
324335
{
325336
$config = $this->getBinaryExtractConfig();
326337
return $config['path'];

src/StaticPHP/Artifact/ArtifactExtractor.php

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ protected function doSelectiveExtract(string $name, array $cache_info, array $fi
293293
// Process file mappings
294294
foreach ($file_map as $src_pattern => $dst_path) {
295295
$dst_path = $this->replacePathVariables($dst_path);
296-
$src_full = "{$temp_path}/{$src_pattern}";
296+
$src_full = FileSystem::convertPath("{$temp_path}/{$src_pattern}");
297297

298298
// Handle glob patterns
299299
if (str_contains($src_pattern, '*')) {
@@ -460,40 +460,36 @@ protected function extractArchive(string $filename, string $target): void
460460
$target = FileSystem::convertPath($target);
461461
$filename = FileSystem::convertPath($filename);
462462

463-
FileSystem::createDir($target);
464-
465-
if (PHP_OS_FAMILY === 'Windows') {
466-
// Use 7za.exe for Windows
467-
$is_txz = str_ends_with($filename, '.txz') || str_ends_with($filename, '.tar.xz');
468-
default_shell()->execute7zExtract($filename, $target, $is_txz);
469-
return;
470-
}
471-
472-
// Unix-like systems: determine compression type
473-
if (str_ends_with($filename, '.tar.gz') || str_ends_with($filename, '.tgz')) {
474-
default_shell()->executeTarExtract($filename, $target, 'gz');
475-
} elseif (str_ends_with($filename, '.tar.bz2')) {
476-
default_shell()->executeTarExtract($filename, $target, 'bz2');
477-
} elseif (str_ends_with($filename, '.tar.xz') || str_ends_with($filename, '.txz')) {
478-
default_shell()->executeTarExtract($filename, $target, 'xz');
479-
} elseif (str_ends_with($filename, '.tar')) {
480-
default_shell()->executeTarExtract($filename, $target, 'none');
481-
} elseif (str_ends_with($filename, '.zip')) {
482-
// Zip requires special handling for strip-components
483-
$this->unzipWithStrip($filename, $target);
484-
} elseif (str_ends_with($filename, '.exe')) {
485-
// exe just copy to target
486-
$dest_file = FileSystem::convertPath("{$target}/" . basename($filename));
487-
FileSystem::copy($filename, $dest_file);
488-
} else {
489-
throw new FileSystemException("Unknown archive format: {$filename}");
490-
}
463+
$extname = FileSystem::extname($filename);
464+
465+
if ($extname !== 'exe' && !is_dir($target)) {
466+
FileSystem::createDir($target);
467+
}
468+
match (SystemTarget::getTargetOS()) {
469+
'Windows' => match ($extname) {
470+
'tar' => default_shell()->executeTarExtract($filename, $target, 'none'),
471+
'xz', 'txz', 'gz', 'tgz', 'bz2' => default_shell()->execute7zExtract($filename, $target),
472+
'zip' => $this->unzipWithStrip($filename, $target),
473+
'exe' => $this->copyFile($filename, $target),
474+
default => throw new FileSystemException("Unknown archive format: {$filename}"),
475+
},
476+
'Linux', 'Darwin' => match ($extname) {
477+
'tar' => default_shell()->executeTarExtract($filename, $target, 'none'),
478+
'gz', 'tgz' => default_shell()->executeTarExtract($filename, $target, 'gz'),
479+
'bz2' => default_shell()->executeTarExtract($filename, $target, 'bz2'),
480+
'xz', 'txz' => default_shell()->executeTarExtract($filename, $target, 'xz'),
481+
'zip' => $this->unzipWithStrip($filename, $target),
482+
'exe' => $this->copyFile($filename, $target),
483+
default => throw new FileSystemException("Unknown archive format: {$filename}"),
484+
},
485+
default => throw new SPCInternalException('Unsupported OS for archive extraction')
486+
};
491487
}
492488

493489
/**
494490
* Unzip file with stripping top-level directory.
495491
*/
496-
protected function unzipWithStrip(string $zip_file, string $extract_path): void
492+
protected function unzipWithStrip(string $zip_file, string $extract_path): bool
497493
{
498494
$temp_dir = FileSystem::convertPath(sys_get_temp_dir() . '/spc_unzip_' . bin2hex(random_bytes(16)));
499495
$zip_file = FileSystem::convertPath($zip_file);
@@ -572,6 +568,8 @@ protected function unzipWithStrip(string $zip_file, string $extract_path): void
572568

573569
// Clean up temp directory
574570
FileSystem::removeDir($temp_dir);
571+
572+
return true;
575573
}
576574

577575
/**
@@ -585,6 +583,7 @@ protected function replacePathVariables(string $path): string
585583
'{source_path}' => SOURCE_PATH,
586584
'{download_path}' => DOWNLOAD_PATH,
587585
'{working_dir}' => WORKING_DIR,
586+
'{php_sdk_path}' => getenv('PHP_SDK_PATH') ?: '',
588587
];
589588
return str_replace(array_keys($replacement), array_values($replacement), $path);
590589
}
@@ -627,9 +626,9 @@ private static function moveFileOrDir(string $source, string $dest): void
627626
}
628627
}
629628

630-
private function copyFile(string $source_file, string $target_path): void
629+
private function copyFile(string $source_file, string $target_path): bool
631630
{
632631
FileSystem::createDir(dirname($target_path));
633-
FileSystem::copy(FileSystem::convertPath($source_file), $target_path);
632+
return FileSystem::copy(FileSystem::convertPath($source_file), $target_path);
634633
}
635634
}

src/StaticPHP/Artifact/Downloader/DownloadResult.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ private function __construct(
3030
) {
3131
switch ($this->cache_type) {
3232
case 'archive':
33-
$this->filename !== null ?: throw new DownloaderException('Archive download result must have a filename.');
33+
case 'file':
34+
$this->filename !== null ?: throw new DownloaderException('Archive/file download result must have a filename.');
3435
$fn = FileSystem::isRelativePath($this->filename) ? (DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $this->filename) : $this->filename;
3536
file_exists($fn) ?: throw new DownloaderException("Downloaded archive file does not exist: {$fn}");
3637
break;
@@ -60,7 +61,20 @@ public static function archive(
6061
?string $version = null,
6162
array $metadata = []
6263
): DownloadResult {
63-
return new self('archive', config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata);
64+
// judge if it is archive or just a pure file
65+
$cache_type = self::isArchiveFile($filename) ? 'archive' : 'file';
66+
return new self($cache_type, config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata);
67+
}
68+
69+
public static function file(
70+
string $filename,
71+
array $config,
72+
bool $verified = false,
73+
?string $version = null,
74+
array $metadata = []
75+
): DownloadResult {
76+
$cache_type = self::isArchiveFile($filename) ? 'archive' : 'file';
77+
return new self($cache_type, config: $config, filename: $filename, verified: $verified, version: $version, metadata: $metadata);
6478
}
6579

6680
/**
@@ -143,4 +157,16 @@ public function withMeta(string $key, mixed $value): self
143157
array_merge($this->metadata, [$key => $value])
144158
);
145159
}
160+
161+
/**
162+
* Check
163+
*/
164+
private static function isArchiveFile(string $filename): bool
165+
{
166+
$archive_extensions = [
167+
'zip', 'tar', 'tar.gz', 'tgz', 'tar.bz2', 'tbz2', 'tar.xz', 'txz', 'rar', '7z',
168+
];
169+
$lower_filename = strtolower($filename);
170+
return array_any($archive_extensions, fn ($ext) => str_ends_with($lower_filename, '.' . $ext));
171+
}
146172
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace StaticPHP\Command\Dev;
6+
7+
use StaticPHP\Command\BaseCommand;
8+
use Symfony\Component\Console\Attribute\AsCommand;
9+
use Symfony\Component\Console\Input\InputArgument;
10+
use Symfony\Component\Console\Input\InputInterface;
11+
use Symfony\Component\Console\Output\OutputInterface;
12+
13+
#[AsCommand('dev:env', 'Returns the internally defined environment variables')]
14+
class EnvCommand extends BaseCommand
15+
{
16+
public function configure(): void
17+
{
18+
$this->addArgument('env', InputArgument::REQUIRED, 'The environment variable to show, if not set, all will be shown');
19+
}
20+
21+
public function initialize(InputInterface $input, OutputInterface $output): void
22+
{
23+
$this->no_motd = true;
24+
parent::initialize($input, $output);
25+
}
26+
27+
public function handle(): int
28+
{
29+
$env = $this->getArgument('env');
30+
if (($val = getenv($env)) === false) {
31+
$this->output->writeln("<error>Environment variable '{$env}' is not set.</error>");
32+
return static::FAILURE;
33+
}
34+
$this->output->writeln("<info>{$val}</info>");
35+
return static::SUCCESS;
36+
}
37+
}

src/StaticPHP/Command/DoctorCommand.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public function configure(): void
1818

1919
public function handle(): int
2020
{
21+
f_putenv('SPC_SKIP_TOOLCHAIN_CHECK=yes');
2122
$fix_policy = match ($this->input->getOption('auto-fix')) {
2223
'never' => FIX_POLICY_DIE,
2324
true, null => FIX_POLICY_AUTOFIX,

src/StaticPHP/ConsoleApplication.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use StaticPHP\Command\BuildLibsCommand;
88
use StaticPHP\Command\BuildTargetCommand;
9+
use StaticPHP\Command\Dev\EnvCommand;
910
use StaticPHP\Command\Dev\IsInstalledCommand;
1011
use StaticPHP\Command\Dev\ShellCommand;
1112
use StaticPHP\Command\DoctorCommand;
@@ -57,6 +58,7 @@ public function __construct()
5758
// dev commands
5859
new ShellCommand(),
5960
new IsInstalledCommand(),
61+
new EnvCommand(),
6062
]);
6163

6264
// add additional commands from registries

src/StaticPHP/Doctor/Doctor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ private function emitFix(string $fix_item, array $fix_item_params = []): bool
130130
$this->output?->writeln('<error>Fix failed: ' . $e->getMessage() . '</error>');
131131
return false;
132132
} catch (\Throwable $e) {
133+
logger()->debug('Error: ' . $e->getMessage() . " at {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString());
133134
$this->output?->writeln('<error>Fix failed with an unexpected error: ' . $e->getMessage() . '</error>');
134135
return false;
135136
} finally {

0 commit comments

Comments
 (0)