Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added CPU architecture detection and option #101

Merged
merged 9 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Command/BrowserCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ final protected function execute(InputInterface $input, OutputInterface $output)
$filePath = $driverDownloader->download($driver, $installPath);

$io->success(
sprintf('%s %s installed to %s', $driver->name->value, $driver->version->toBuildString(), $filePath),
sprintf('%s %s%s installed to %s', $driver->name->value, $driver->version->toBuildString(), $driver->cpuArchitecture->toString(), $filePath),
);

return self::SUCCESS;
Expand Down
7 changes: 5 additions & 2 deletions src/Command/DriverCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ final protected function configure(): void
new Input\InstallPathArgument(),
new Input\VersionOption(),
new Input\OperatingSystemOption(),
new Input\CpuArchitectureOption(),
],
),
);
Expand All @@ -54,6 +55,7 @@ final protected function execute(InputInterface $input, OutputInterface $output)
$installPath = Input\InstallPathArgument::value($input);
$versionString = Input\VersionOption::value($input);
$operatingSystem = Input\OperatingSystemOption::value($input);
$cpuArchitecture = Input\CpuArchitectureOption::value($input);

// TODO: move this into VersionOption class
if ($versionString === Input\VersionOption::LATEST) {
Expand All @@ -68,7 +70,7 @@ final protected function execute(InputInterface $input, OutputInterface $output)
$version = Version::fromString($versionString);
}

$driver = new Driver($driverName, $version, $operatingSystem);
$driver = new Driver($driverName, $version, $operatingSystem, $cpuArchitecture);

if ($io->isVerbose()) {
$io->writeln(
Expand All @@ -81,9 +83,10 @@ final protected function execute(InputInterface $input, OutputInterface $output)

$io->success(
sprintf(
'%s %s installed to %s',
'%s %s%s installed to %s',
$driver->name->value,
$driver->version->toBuildString(),
$driver->cpuArchitecture->toString(),
$filePath,
),
);
Expand Down
80 changes: 80 additions & 0 deletions src/Command/Input/CpuArchitectureOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace DBrekelmans\BrowserDriverInstaller\Command\Input;

use DBrekelmans\BrowserDriverInstaller\Cpu\CpuArchitecture;
use DBrekelmans\BrowserDriverInstaller\Exception\UnexpectedType;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use UnexpectedValueException;

use function array_map;
use function implode;
use function is_string;
use function sprintf;

/** @implements Option<CpuArchitecture> */
final class CpuArchitectureOption extends InputOption implements Option
{
public function __construct()
{
parent::__construct(
self::name(),
$this->shortcut(),
$this->mode()->value,
$this->description(),
$this->default(),
);
}

public static function name(): string
{
return 'arch';
}

public function shortcut(): string|null
{
return null;
}

public function mode(): OptionMode
{
return OptionMode::REQUIRED;
}

public function description(): string
{
return sprintf(
'CPU architecture for which to install the driver (%s)',
implode('|', array_map(static fn ($case) => $case->value, CpuArchitecture::cases())),
);
}

public function default(): string|null
{
return CpuArchitecture::AMD64->value;
}

public static function value(InputInterface $input): CpuArchitecture
{
$value = $input->getOption(self::name());

if (! is_string($value)) {
throw UnexpectedType::expected('string', $value);
}

if (CpuArchitecture::tryFrom($value) === null) {
throw new UnexpectedValueException(
sprintf(
'Unexpected value %s. Expected one of: %s',
$value,
implode(', ', array_map(static fn ($case) => $case->value, CpuArchitecture::cases())),
),
);
}

return CpuArchitecture::from($value);
}
}
29 changes: 29 additions & 0 deletions src/Cpu/CpuArchitecture.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace DBrekelmans\BrowserDriverInstaller\Cpu;

use function php_uname;

enum CpuArchitecture: string
{
case AMD64 = 'amd64';
case ARM64 = 'arm64';

public function toString(): string
{
return match ($this) {
self::AMD64 => ' amd64',
self::ARM64 => ' arm64',
};
}

public static function detectFromPhp(): CpuArchitecture
{
return match (php_uname('m')) {
'arm64', 'aarch64' => CpuArchitecture::ARM64,
default => CpuArchitecture::AMD64,
};
}
}
13 changes: 13 additions & 0 deletions src/Driver/ChromeDriver/DownloadUrlResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace DBrekelmans\BrowserDriverInstaller\Driver\ChromeDriver;

use DBrekelmans\BrowserDriverInstaller\Cpu\CpuArchitecture;
use DBrekelmans\BrowserDriverInstaller\Driver\DownloadUrlResolver as DownloadUrlResolverInterface;
use DBrekelmans\BrowserDriverInstaller\Driver\Driver;
use DBrekelmans\BrowserDriverInstaller\OperatingSystem\OperatingSystem;
Expand Down Expand Up @@ -40,6 +41,10 @@ public function byDriver(Driver $driver): string
throw new UnexpectedValueException(sprintf('Could not find the chromedriver downloads for version %s', $driver->version->toString()));
}

if ($driver->cpuArchitecture === CpuArchitecture::ARM64 && $driver->operatingSystem !== OperatingSystem::MACOS) {
throw new UnexpectedValueException('Chromedriver ARM64 is only available on macOS.');
}

$platformName = $this->getPlatformName($driver);
$downloads = $versions['builds'][$driver->version->toString()]['downloads']['chromedriver'];
foreach ($downloads as $download) {
Expand All @@ -59,6 +64,10 @@ public function byDriver(Driver $driver): string

private function getBinaryName(Driver $driver): string
{
if ($driver->operatingSystem === OperatingSystem::MACOS && $driver->cpuArchitecture === CpuArchitecture::ARM64) {
return 'chromedriver_mac_arm64';
}

return match ($driver->operatingSystem) {
OperatingSystem::LINUX => 'chromedriver_linux64',
OperatingSystem::MACOS => 'chromedriver_mac64',
Expand All @@ -68,6 +77,10 @@ private function getBinaryName(Driver $driver): string

private function getPlatformName(Driver $driver): string
{
if ($driver->cpuArchitecture === CpuArchitecture::ARM64) {
return 'mac-arm64';
}

return match ($driver->operatingSystem) {
OperatingSystem::LINUX => 'linux64',
OperatingSystem::MACOS => 'mac-x64',
Expand Down
15 changes: 12 additions & 3 deletions src/Driver/ChromeDriver/Downloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace DBrekelmans\BrowserDriverInstaller\Driver\ChromeDriver;

use DBrekelmans\BrowserDriverInstaller\Archive\Extractor;
use DBrekelmans\BrowserDriverInstaller\Cpu\CpuArchitecture;
use DBrekelmans\BrowserDriverInstaller\Driver\Downloader as DownloaderInterface;
use DBrekelmans\BrowserDriverInstaller\Driver\DownloadUrlResolver;
use DBrekelmans\BrowserDriverInstaller\Driver\Driver;
Expand Down Expand Up @@ -44,6 +45,10 @@ public function __construct(

public function supports(Driver $driver): bool
{
if ($driver->cpuArchitecture === CpuArchitecture::ARM64 && $driver->operatingSystem !== OperatingSystem::MACOS) {
return false;
}

return $driver->name === DriverName::CHROME;
}

Expand Down Expand Up @@ -179,7 +184,7 @@ private function getFileName(OperatingSystem $operatingSystem): string
*/
public function cleanArchiveStructure(Driver $driver, string $unzipLocation, array $extractedFiles): array
{
$archiveDirectory = $this->getArchiveDirectory($driver->operatingSystem);
$archiveDirectory = $this->getArchiveDirectory($driver);
$filename = $this->getFileName($driver->operatingSystem);
$this->filesystem->rename(
$unzipLocation . DIRECTORY_SEPARATOR . $archiveDirectory . $filename,
Expand All @@ -190,9 +195,13 @@ public function cleanArchiveStructure(Driver $driver, string $unzipLocation, arr
return str_replace($archiveDirectory, '', $extractedFiles);
}

private function getArchiveDirectory(OperatingSystem $operatingSystem): string
private function getArchiveDirectory(Driver $driver): string
{
return match ($operatingSystem) {
if ($driver->operatingSystem === OperatingSystem::MACOS && $driver->cpuArchitecture === CpuArchitecture::ARM64) {
return 'chromedriver-mac-arm64/';
}

return match ($driver->operatingSystem) {
OperatingSystem::LINUX => 'chromedriver-linux64/',
OperatingSystem::WINDOWS => 'chromedriver-win32/', // This weirdly contains a forward slash on windows
OperatingSystem::MACOS => 'chromedriver-mac-x64/',
Expand Down
2 changes: 1 addition & 1 deletion src/Driver/DownloaderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function createFromDriver(Driver $driver): Downloader
}
}

throw NotImplemented::feature(sprintf('Downloader for %s', $driver->name->value));
throw NotImplemented::feature(sprintf('Downloader for %s %s', $driver->name->value, $driver->cpuArchitecture->value));
}

public function register(Downloader $downloader, string|null $identifier = null): void
Expand Down
2 changes: 2 additions & 0 deletions src/Driver/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace DBrekelmans\BrowserDriverInstaller\Driver;

use DBrekelmans\BrowserDriverInstaller\Cpu\CpuArchitecture;
use DBrekelmans\BrowserDriverInstaller\OperatingSystem\OperatingSystem;
use DBrekelmans\BrowserDriverInstaller\Version;

Expand All @@ -13,6 +14,7 @@ public function __construct(
public readonly DriverName $name,
public readonly Version $version,
public readonly OperatingSystem $operatingSystem,
public readonly CpuArchitecture $cpuArchitecture,
) {
}
}
6 changes: 5 additions & 1 deletion src/Driver/DriverFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use DBrekelmans\BrowserDriverInstaller\Browser\Browser;
use DBrekelmans\BrowserDriverInstaller\Browser\BrowserName;
use DBrekelmans\BrowserDriverInstaller\Cpu\CpuArchitecture;

final class DriverFactory
{
Expand All @@ -20,7 +21,10 @@ public function createFromBrowser(Browser $browser): Driver

$name = $this->getDriverNameForBrowser($browser);

return new Driver($name, $version, $browser->operatingSystem);
// Detect CPU arch
$cpuArchitecture = CpuArchitecture::detectFromPhp();

return new Driver($name, $version, $browser->operatingSystem, $cpuArchitecture);
}

private function getDriverNameForBrowser(Browser $browser): DriverName
Expand Down
31 changes: 28 additions & 3 deletions src/Driver/GeckoDriver/Downloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace DBrekelmans\BrowserDriverInstaller\Driver\GeckoDriver;

use DBrekelmans\BrowserDriverInstaller\Archive\Extractor;
use DBrekelmans\BrowserDriverInstaller\Cpu\CpuArchitecture;
use DBrekelmans\BrowserDriverInstaller\Driver\Downloader as DownloaderInterface;
use DBrekelmans\BrowserDriverInstaller\Driver\Driver;
use DBrekelmans\BrowserDriverInstaller\Driver\DriverName;
Expand All @@ -30,9 +31,9 @@

final class Downloader implements DownloaderInterface
{
private const DOWNLOAD_PATH_OS_PART_WINDOWS = 'win64';
private const DOWNLOAD_PATH_OS_PART_WINDOWS = 'win';
private const DOWNLOAD_PATH_OS_PART_MACOS = 'macos';
private const DOWNLOAD_PATH_OS_PART_LINUX = 'linux64';
private const DOWNLOAD_PATH_OS_PART_LINUX = 'linux';
private const DOWNLOAD_BASE_PATH = 'https://github.com/mozilla/geckodriver/releases/download/';

public function __construct(
Expand Down Expand Up @@ -112,10 +113,11 @@ private function downloadArchive(Driver $driver): string
private function getDownloadPath(Driver $driver): string
{
return self::DOWNLOAD_BASE_PATH . sprintf(
'v%s/geckodriver-v%s-%s%s',
'v%s/geckodriver-v%s-%s%s%s',
$driver->version->toString(),
$driver->version->toString(),
$this->getOsForDownloadPath($driver),
$this->getCpuArchForDownloadPath($driver),
$this->getArchiveExtension($driver),
);
}
Expand All @@ -129,6 +131,29 @@ private function getOsForDownloadPath(Driver $driver): string
};
}

private function getCpuArchForDownloadPath(Driver $driver): string
{
if ($driver->operatingSystem === OperatingSystem::LINUX) {
return match ($driver->cpuArchitecture) {
CpuArchitecture::AMD64 => '64',
CpuArchitecture::ARM64 => '-aarch64',
};
}

if ($driver->operatingSystem === OperatingSystem::MACOS) {
return match ($driver->cpuArchitecture) {
CpuArchitecture::AMD64 => '',
CpuArchitecture::ARM64 => '-aarch64',
};
}

// Windows
return match ($driver->cpuArchitecture) {
CpuArchitecture::AMD64 => '64',
CpuArchitecture::ARM64 => '-aarch64',
};
}

private function extractArchive(string $archive): string
{
$extractedFiles = $this->archiveExtractor->extract($archive, dirname($archive));
Expand Down
Loading
Loading