Skip to content

Commit d485308

Browse files
authored
Merge pull request #7 from volt-test/Feat/download-and-install-the-binary-based-on-os-and-arch
Feat/download and install the binary based on os and arch
2 parents 2c1a657 + 9118096 commit d485308

File tree

9 files changed

+168
-65
lines changed

9 files changed

+168
-65
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ jobs:
4444
run: |
4545
choco install php --version=8.2 --params '"/ExtensionList:mbstring,curl,openssl,xml"'
4646
refreshenv
47+
# Download CA bundle
48+
Invoke-WebRequest -Uri https://curl.se/ca/cacert.pem -OutFile C:\tools\cacert.pem
49+
# Configure PHP to use the CA bundle
50+
$phpIni = php --ini | Select-String "Loaded Configuration File" | ForEach-Object { $_.Line.Split()[-1] }
51+
Add-Content $phpIni "`nopenssl.cafile = C:\tools\cacert.pem"
4752
php -v
4853
shell: pwsh
4954

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ php test.php
132132
- PHP 8.0 or higher
133133
- ext-json
134134
- ext-pcntl
135+
- ext-curl
135136

136137
## License
137138

-5.64 MB
Binary file not shown.
-5.39 MB
Binary file not shown.
-1.76 MB
Binary file not shown.
-1.78 MB
Binary file not shown.

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
],
2525
"require": {
2626
"php": "^8.0",
27-
"ext-json": "*"
27+
"ext-json": "*",
28+
"ext-curl": "*"
2829
},
2930
"suggest": {
3031
"ext-pcntl": "For signal handling support on Unix-like systems"

src/Platform.php

Lines changed: 155 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,66 +5,69 @@
55
class Platform
66
{
77
private const BINARY_NAME = 'volt-test';
8+
9+
private const CURRENT_VERSION = 'v0.0.1-beta';
10+
private const BASE_DOWNLOAD_URL = 'https://github.com/volt-test/binaries/releases/download';
811
private const SUPPORTED_PLATFORMS = [
9-
'linux-amd64' => 'linux-amd64/volt-test',
10-
'darwin-amd64' => 'darwin-amd64/volt-test',
11-
'darwin-arm64' => 'darwin-arm64/volt-test',
12-
'windows-amd64' => 'windows-amd64/volt-test.exe',
13-
'windows-AMD64' => 'windows-amd64/volt-test.exe',
12+
'linux-amd64' => 'volt-test-linux-amd64',
13+
'darwin-amd64' => 'volt-test-darwin-amd64',
14+
'darwin-arm64' => 'volt-test-darwin-arm64',
15+
'windows-amd64' => 'volt-test-windows-amd64.exe',
16+
'windows-AMD64' => 'volt-test-windows-amd64.exe',
1417
];
1518

16-
public static function installBinary($testing = false): void
19+
private static function getVendorDir(): string
1720
{
18-
$platform = self::detectPlatform($testing);
19-
20-
if (! array_key_exists($platform, self::SUPPORTED_PLATFORMS)) {
21-
throw new \RuntimeException("Platform $platform is not supported");
22-
}
23-
24-
$sourceFile = __DIR__ . '/../bin/platforms/' . self::SUPPORTED_PLATFORMS[$platform];
25-
$targetDir = self::getBinaryDir();
26-
$targetFile = $targetDir . '/' . basename(self::SUPPORTED_PLATFORMS[$platform]);
27-
28-
if (! file_exists($sourceFile)) {
29-
throw new \RuntimeException("Binary not found for platform: $platform");
21+
// First try using Composer's environment variable
22+
if (getenv('COMPOSER_VENDOR_DIR')) {
23+
return rtrim(getenv('COMPOSER_VENDOR_DIR'), '/\\');
3024
}
3125

32-
if (! is_dir($targetDir)) {
33-
if (! mkdir($targetDir, 0755, true)) {
34-
throw new \RuntimeException("Failed to create directory: $targetDir");
26+
// Then try using Composer's home directory
27+
if (getenv('COMPOSER_HOME')) {
28+
$vendorDir = rtrim(getenv('COMPOSER_HOME'), '/\\') . '/vendor';
29+
if (is_dir($vendorDir)) {
30+
return $vendorDir;
3531
}
3632
}
3733

38-
if (! copy($sourceFile, $targetFile)) {
39-
throw new \RuntimeException("Failed to copy binary to: $targetFile");
34+
// Try to find vendor directory relative to current file
35+
$paths = [
36+
__DIR__ . '/../../../', // From src/VoltTest to vendor
37+
__DIR__ . '/vendor/', // Direct vendor subdirectory
38+
dirname(__DIR__, 2) . '/vendor/', // Two levels up
39+
dirname(__DIR__, 3) . '/vendor/', // Three levels up
40+
dirname(__DIR__) . '/vendor/',
41+
];
42+
43+
foreach ($paths as $path) {
44+
if (file_exists($path . 'autoload.php')) {
45+
return rtrim($path, '/\\');
46+
}
4047
}
4148

42-
chmod($targetFile, 0755);
43-
44-
// Create symlink in vendor/bin
45-
$vendorBinDir = self::getVendorBinDir();
46-
if (! is_dir($vendorBinDir)) {
47-
if (! mkdir($vendorBinDir, 0755, true)) {
48-
throw new \RuntimeException("Failed to create vendor bin directory: $vendorBinDir");
49+
// If running as a Composer script, use the working directory
50+
if (getenv('COMPOSER')) {
51+
$path = getcwd() . '/vendor';
52+
if (is_dir($path)) {
53+
return $path;
4954
}
5055
}
5156

52-
$symlinkPath = $vendorBinDir . '/' . self::BINARY_NAME;
53-
if (file_exists($symlinkPath)) {
54-
unlink($symlinkPath);
55-
}
57+
throw new \RuntimeException(
58+
'Could not locate Composer vendor directory. ' .
59+
'Please ensure you are installing through Composer.'
60+
);
61+
}
5662

57-
if (PHP_OS_FAMILY === 'Windows') {
58-
// Windows doesn't support symlinks by default, so we'll copy the file
59-
if (! copy($targetFile, $symlinkPath)) {
60-
throw new \RuntimeException("Failed to copy binary to vendor/bin: $symlinkPath");
61-
}
62-
} else {
63-
if (! symlink($targetFile, $symlinkPath)) {
64-
throw new \RuntimeException("Failed to create symlink in vendor/bin: $symlinkPath");
65-
}
66-
}
63+
private static function getBinaryDir(): string
64+
{
65+
return self::getVendorDir() . '/bin';
66+
}
6767

68+
private static function getCurrentVersion(): string
69+
{
70+
return self::CURRENT_VERSION;
6871
}
6972

7073
private static function detectPlatform($testing = false): string
@@ -82,40 +85,131 @@ private static function detectPlatform($testing = false): string
8285
} elseif (strpos($os, 'linux') === 0) {
8386
$os = 'linux';
8487
}
88+
8589
if ($arch === 'x86_64') {
8690
$arch = 'amd64';
91+
} elseif ($arch === 'arm64' || $arch === 'aarch64') {
92+
$arch = 'arm64';
8793
}
8894

8995
return "$os-$arch";
9096
}
9197

92-
private static function getBinaryDir(): string
98+
public static function installBinary($testing = false): void
9399
{
94-
return self::getVendorDir() . '/volt-test/bin';
95-
}
100+
$platform = self::detectPlatform($testing);
96101

97-
private static function getVendorBinDir(): string
98-
{
99-
return self::getVendorDir() . '/bin';
100-
}
102+
if (!array_key_exists($platform, self::SUPPORTED_PLATFORMS)) {
103+
throw new \RuntimeException("Platform $platform is not supported");
104+
}
101105

102-
private static function getVendorDir(): string
103-
{
104-
// Traverse up from current directory until we find vendor directory
105-
$dir = __DIR__;
106-
while ($dir !== '/' && ! is_dir($dir . '/vendor')) {
107-
$dir = dirname($dir);
106+
$version = self::getCurrentVersion();
107+
$binaryName = self::SUPPORTED_PLATFORMS[$platform];
108+
$downloadUrl = sprintf('%s/%s/%s', self::BASE_DOWNLOAD_URL, $version, $binaryName);
109+
110+
$binDir = self::getBinaryDir();
111+
$targetFile = $binDir . '/' . self::BINARY_NAME;
112+
if (PHP_OS_FAMILY === 'Windows') {
113+
$targetFile .= '.exe';
108114
}
109115

110-
if (! is_dir($dir . '/vendor')) {
111-
throw new \RuntimeException('Could not locate vendor directory');
116+
if (!is_dir($binDir)) {
117+
if (!mkdir($binDir, 0755, true)) {
118+
throw new \RuntimeException("Failed to create directory: $binDir");
119+
}
112120
}
113121

114-
return $dir . '/vendor';
122+
echo "Downloading VoltTest binary $version for platform: $platform\n";
123+
124+
$tempFile = tempnam(sys_get_temp_dir(), 'volt-test-download-');
125+
if ($tempFile === false) {
126+
throw new \RuntimeException("Failed to create temporary file");
127+
}
128+
129+
try {
130+
$ch = curl_init($downloadUrl);
131+
if ($ch === false) {
132+
throw new \RuntimeException("Failed to initialize cURL");
133+
}
134+
135+
$fp = fopen($tempFile, 'w');
136+
if ($fp === false) {
137+
curl_close($ch);
138+
throw new \RuntimeException("Failed to open temporary file for writing");
139+
}
140+
141+
curl_setopt_array($ch, [
142+
CURLOPT_FILE => $fp,
143+
CURLOPT_FOLLOWLOCATION => true,
144+
CURLOPT_SSL_VERIFYPEER => true,
145+
CURLOPT_FAILONERROR => true,
146+
CURLOPT_TIMEOUT => 300,
147+
CURLOPT_CONNECTTIMEOUT => 30,
148+
CURLOPT_HTTPHEADER => ['User-Agent: volt-test-php-sdk']
149+
]);
150+
151+
if (curl_exec($ch) === false) {
152+
throw new \RuntimeException("Download failed: " . curl_error($ch));
153+
}
154+
155+
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
156+
if ($httpCode !== 200) {
157+
throw new \RuntimeException("HTTP request failed with status $httpCode");
158+
}
159+
160+
curl_close($ch);
161+
fclose($fp);
162+
163+
if (!file_exists($tempFile) || filesize($tempFile) === 0) {
164+
throw new \RuntimeException("Downloaded file is empty or missing");
165+
}
166+
167+
if (!rename($tempFile, $targetFile)) {
168+
throw new \RuntimeException("Failed to move downloaded binary to: $targetFile");
169+
}
170+
171+
if (!chmod($targetFile, 0755)) {
172+
throw new \RuntimeException("Failed to set executable permissions on binary");
173+
}
174+
175+
file_put_contents($binDir . '/.volt-test-version', $version);
176+
177+
echo "Successfully installed VoltTest binary $version to: $targetFile\n";
178+
} catch (\Exception $e) {
179+
if (file_exists($tempFile)) {
180+
unlink($tempFile);
181+
}
182+
throw $e;
183+
}
115184
}
116185

117186
public static function getBinaryPath(): string
118187
{
119-
return self::getBinaryDir() . '/' . basename(self::SUPPORTED_PLATFORMS[self::detectPlatform()]);
188+
$binDir = self::getBinaryDir();
189+
$binaryName = self::BINARY_NAME;
190+
if (PHP_OS_FAMILY === 'Windows') {
191+
$binaryName .= '.exe';
192+
}
193+
194+
$binaryPath = $binDir . '/' . $binaryName;
195+
$versionFile = $binDir . '/.volt-test-version';
196+
197+
$needsInstall = true;
198+
199+
if (file_exists($binaryPath) && file_exists($versionFile)) {
200+
try {
201+
$currentVersion = trim(file_get_contents($versionFile));
202+
$latestVersion = self::getCurrentVersion();
203+
$needsInstall = $currentVersion !== $latestVersion;
204+
} catch (\Exception $e) {
205+
$needsInstall = true;
206+
}
207+
}
208+
209+
if ($needsInstall) {
210+
self::installBinary();
211+
}
212+
213+
return $binaryPath;
120214
}
121215
}

tests/Units/PlatformTest.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public function testGetBinaryPath()
7171

7272
// Assert that the binary path contains the expected directory structure
7373
$this->assertStringContainsString(
74-
'volt-test/bin',
74+
'/bin',
7575
$binaryPath,
7676
"Binary path does not contain the expected directory structure"
7777
);
@@ -94,7 +94,7 @@ public function testInstallBinary()
9494
$vendorDir = $getVendorDirMethod->invoke($platform);
9595

9696
// Assert that the binary directory is created
97-
$binaryDir = $vendorDir . '/volt-test/bin';
97+
$binaryDir = $vendorDir . '/bin';
9898
$this->assertDirectoryExists($binaryDir, "Binary directory was not created");
9999

100100
// Assert that the binary file exists
@@ -107,7 +107,9 @@ public function testInstallBinary()
107107
// Check vendor/bin symlink or copy
108108
$vendorBinDir = $vendorDir . '/bin';
109109
$symlinkPath = $vendorBinDir . '/volt-test';
110-
110+
if (PHP_OS_FAMILY === 'Windows') {
111+
$symlinkPath .= '.exe';
112+
}
111113
$this->assertTrue(
112114
file_exists($symlinkPath) || is_link($symlinkPath),
113115
"Symlink or copy in vendor/bin directory was not created"

0 commit comments

Comments
 (0)