Skip to content

Commit 0968047

Browse files
committed
Attempt to provide script for downloading compiled libraries
1 parent eff70df commit 0968047

3 files changed

Lines changed: 244 additions & 7 deletions

File tree

composer.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
"license": "MIT",
66
"require": {
77
"php": "^8.3",
8-
"ext-ffi": "*",
9-
"codewithkyrian/platform-package-installer": "^2.1"
8+
"ext-ffi": "*"
109
},
1110
"require-dev": {
1211
"phpunit/phpunit": "^12.0",
@@ -25,20 +24,20 @@
2524
}
2625
},
2726
"scripts": {
27+
"post-install-cmd": "HtmlShot\\Composer\\Installer::downloadArtifact",
28+
"post-update-cmd": "HtmlShot\\Composer\\Installer::downloadArtifact",
2829
"test": "vendor/bin/phpunit",
2930
"lint": "vendor/bin/phpstan analyse",
3031
"format": "vendor/bin/pint"
3132
},
32-
"config": {
33-
"allow-plugins": {
34-
"codewithkyrian/platform-package-installer": true
35-
}
36-
},
3733
"extra": {
3834
"artifacts": {
3935
"linux-x86_64": "https://github.com/avvertix/html-shot/releases/download/v0.0.1/libtakumi_php.so",
4036
"darwin-arm64": "https://github.com/avvertix/html-shot/releases/download/v0.0.1/libtakumi_php.dylib",
4137
"windows-64": "https://github.com/avvertix/html-shot/releases/download/v0.0.1/takumi_php.dll"
4238
}
39+
},
40+
"archive": {
41+
"exclude": ["examples", "rust"]
4342
}
4443
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HtmlShot\Composer;
6+
7+
use Composer\Script\Event;
8+
9+
class Installer
10+
{
11+
public static function downloadArtifact(Event $event): void
12+
{
13+
$composer = $event->getComposer();
14+
$io = $event->getIO();
15+
16+
$extra = $composer->getPackage()->getExtra();
17+
$artifacts = $extra['artifacts'] ?? [];
18+
19+
if ($artifacts === []) {
20+
$io->write('<warning>No artifacts configured in extra.artifacts — skipping binary download.</warning>');
21+
22+
return;
23+
}
24+
25+
$normalized = array_combine(
26+
array_map('strtolower', array_keys($artifacts)),
27+
array_values($artifacts)
28+
);
29+
30+
$url = Platform::findBestMatch($normalized);
31+
32+
if ($url === false) {
33+
$platform = Platform::current();
34+
$io->writeError(
35+
"<error>No artifact URL found for current platform ({$platform['os']}-{$platform['arch']}).</error>"
36+
);
37+
38+
return;
39+
}
40+
41+
$packageRoot = dirname(__DIR__, 3);
42+
$libDir = $packageRoot.'/lib';
43+
44+
if (! is_dir($libDir) && ! mkdir($libDir, 0755, true)) {
45+
$io->writeError("<error>Failed to create lib directory: {$libDir}</error>");
46+
47+
return;
48+
}
49+
50+
$urlPath = parse_url($url, PHP_URL_PATH);
51+
$filename = basename((string) $urlPath);
52+
53+
if ($filename === '') {
54+
$io->writeError("<error>Cannot determine filename from artifact URL: {$url}</error>");
55+
56+
return;
57+
}
58+
59+
$destination = $libDir.'/'.$filename;
60+
61+
if (file_exists($destination)) {
62+
$io->write(" - Artifact <info>{$filename}</info> already present, skipping download.");
63+
64+
return;
65+
}
66+
67+
$io->write(" - Downloading <info>{$filename}</info> from <comment>{$url}</comment>");
68+
69+
$context = stream_context_create([
70+
'http' => [
71+
'method' => 'GET',
72+
'follow_location' => 1,
73+
'max_redirects' => 10,
74+
'header' => ['User-Agent: Composer HtmlShot-Installer/1.0'],
75+
'timeout' => 120,
76+
],
77+
'ssl' => [
78+
'verify_peer' => true,
79+
'verify_peer_name' => true,
80+
],
81+
]);
82+
83+
$content = @file_get_contents($url, false, $context);
84+
85+
if ($content === false) {
86+
$io->writeError("<error>Download failed for: {$url}</error>");
87+
88+
return;
89+
}
90+
91+
if (file_put_contents($destination, $content) === false) {
92+
$io->writeError("<error>Failed to write artifact to: {$destination}</error>");
93+
94+
return;
95+
}
96+
97+
$io->write(" - Artifact saved to <info>lib/{$filename}</info>");
98+
}
99+
}

src/HtmlShot/Composer/Platform.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HtmlShot\Composer;
6+
7+
/**
8+
* Platform detection and matching for selecting the correct binary artifact.
9+
*
10+
* Adapted from codewithkyrian/platform-package-installer (MIT).
11+
*/
12+
class Platform
13+
{
14+
public static function current(): array
15+
{
16+
$osName = strtolower(php_uname('s'));
17+
$arch = php_uname('m');
18+
19+
return [
20+
'os' => $osName,
21+
'arch' => self::normalizeArchitecture($arch),
22+
'full' => php_uname(),
23+
];
24+
}
25+
26+
public static function normalizeArchitecture(string $arch): string
27+
{
28+
$arch = strtolower($arch);
29+
30+
$archMap = [
31+
'x86_64' => 'x86_64',
32+
'amd64' => 'x86_64',
33+
'i386' => 'x86',
34+
'i686' => 'x86',
35+
'x64' => 'x86_64',
36+
'x86' => 'x86',
37+
'32' => 'x86',
38+
'64' => 'x86_64',
39+
'arm64' => 'arm64',
40+
'aarch64' => 'arm64',
41+
'armv7' => 'arm',
42+
'armv8' => 'arm64',
43+
'arm64v8' => 'arm64',
44+
'ppc64' => 'ppc64',
45+
'ppc64le' => 'ppc64le',
46+
's390x' => 's390x',
47+
];
48+
49+
return $archMap[$arch] ?? $arch;
50+
}
51+
52+
public static function matches(string $definedPlatform, ?array $currentPlatform = null): bool
53+
{
54+
$currentPlatform ??= self::current();
55+
$definedPlatform = strtolower($definedPlatform);
56+
57+
if ($definedPlatform === 'all') {
58+
return true;
59+
}
60+
61+
$parts = explode('-', $definedPlatform);
62+
$os = $parts[0];
63+
$arch = count($parts) > 1 ? $parts[1] : null;
64+
65+
$archMatch = $arch === null
66+
|| self::normalizeArchitecture($arch) === $currentPlatform['arch'];
67+
68+
return self::matchesOS($os, $currentPlatform['os']) && $archMatch;
69+
}
70+
71+
/**
72+
* Return the value from $platformEntries whose key best matches the current platform,
73+
* preferring more-specific (os-arch) keys over os-only keys, or false if none match.
74+
*
75+
* @template T
76+
*
77+
* @param array<string, T> $platformEntries
78+
* @param array{os: string, arch: string}|null $currentPlatform
79+
* @return T|false
80+
*/
81+
public static function findBestMatch(array $platformEntries, ?array $currentPlatform = null): mixed
82+
{
83+
$currentPlatform ??= self::current();
84+
85+
$matches = array_filter(
86+
array_keys($platformEntries),
87+
fn ($p) => self::matches($p, $currentPlatform)
88+
);
89+
90+
usort($matches, function (string $a, string $b): int {
91+
if ($a === 'all') {
92+
return 1;
93+
}
94+
if ($b === 'all') {
95+
return -1;
96+
}
97+
98+
$aHasArch = str_contains($a, '-');
99+
$bHasArch = str_contains($b, '-');
100+
101+
if ($aHasArch && ! $bHasArch) {
102+
return -1;
103+
}
104+
if (! $aHasArch && $bHasArch) {
105+
return 1;
106+
}
107+
108+
return 0;
109+
});
110+
111+
foreach ($matches as $platform) {
112+
return $platformEntries[$platform];
113+
}
114+
115+
return false;
116+
}
117+
118+
private static function matchesOS(string $definedOs, string $currentOs): bool
119+
{
120+
$osAliases = [
121+
'windows' => ['windows', 'win32', 'win64', 'windows nt'],
122+
'darwin' => ['macos', 'mac', 'darwin'],
123+
'linux' => ['linux', 'gnu/linux'],
124+
'raspberrypi' => ['raspbian', 'raspberry pi'],
125+
];
126+
127+
if ($definedOs === $currentOs) {
128+
return true;
129+
}
130+
131+
foreach ($osAliases as $alias => $variations) {
132+
if ($definedOs === $alias && in_array($currentOs, $variations, true)) {
133+
return true;
134+
}
135+
}
136+
137+
return false;
138+
}
139+
}

0 commit comments

Comments
 (0)