Skip to content
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
15 changes: 9 additions & 6 deletions .github/workflows/build-assets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ jobs:
needs:
- build-phar
runs-on: ${{ matrix.operating-system }}
env:
SPC_VERSION: 2.8.5
strategy:
fail-fast: false
matrix:
Expand All @@ -76,7 +78,7 @@ jobs:
- ubuntu-24.04-arm
- macos-15-intel
- macos-26
- windows-2025
# - windows-2025 - disabled for now, seems broken
permissions:
# id-token:write is required for build provenance attestation.
id-token: write
Expand All @@ -92,33 +94,34 @@ jobs:
# Source URL: https://static-php.dev/en/guide/manual-build.html#build-locally-using-spc-binary-recommended
case "${{ matrix.operating-system }}" in
ubuntu-24.04)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
curl -fsSL -o spc.tgz https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-linux-x86_64.tar.gz
;;

ubuntu-24.04-arm)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
curl -fsSL -o spc.tgz https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-linux-aarch64.tar.gz
;;

macos-15-intel)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
curl -fsSL -o spc.tgz https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-macos-x86_64.tar.gz
;;

macos-26)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
curl -fsSL -o spc.tgz https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-macos-aarch64.tar.gz
;;

*)
echo "unsupported operating system: ${{ matrix.operating-system }}"
exit 1
;;
esac
tar zxvf spc.tgz
chmod +x spc
echo "SPC_BINARY=./spc" >> $GITHUB_ENV
echo "PIE_BINARY_OUTPUT=pie-${{ runner.os }}-${{ runner.arch }}" >> $GITHUB_ENV
- name: Download SPC (Windows)
if: runner.os == 'Windows'
run: |
curl.exe -fsSL -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
curl.exe -fsSL -o spc.exe https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-windows-x64.exe
chmod +x spc.exe
echo "SPC_BINARY=.\spc.exe" >> $env:GITHUB_ENV
echo "PIE_BINARY_OUTPUT=pie-${{ runner.os }}-${{ runner.arch }}.exe" >> $env:GITHUB_ENV
Expand Down
24 changes: 1 addition & 23 deletions src/Platform/PackageManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@

use function array_unshift;
use function implode;
use function str_contains;
use function strtolower;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum PackageManager: string
Expand Down Expand Up @@ -69,7 +67,7 @@ public function install(array $packages): void

return;
} catch (ProcessFailedException $e) {
if (Platform::isInteractive() && self::isProbablyPermissionDenied($e)) {
if (Platform::isInteractive() && Process::processProbablyPermissionDenied($e)) {
array_unshift($cmd, Sudo::find());

Process::run($cmd);
Expand All @@ -80,24 +78,4 @@ public function install(array $packages): void
throw $e;
}
}

private static function isProbablyPermissionDenied(ProcessFailedException $e): bool
{
$mergedProcessOutput = strtolower($e->getProcess()->getErrorOutput() . $e->getProcess()->getOutput());

$needles = [
'permission denied',
'you must be root',
'operation not permitted',
'are you root',
];

foreach ($needles as $needle) {
if (str_contains($mergedProcessOutput, $needle)) {
return true;
}
}

return false;
}
}
23 changes: 23 additions & 0 deletions src/Util/Process.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process as SymfonyProcess;

use function str_contains;
use function strtolower;
use function trim;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
Expand Down Expand Up @@ -45,4 +47,25 @@ public static function run(
->mustRun($outputCallback)
->getOutput());
}

public static function processProbablyPermissionDenied(ProcessFailedException $e): bool
{
$mergedProcessOutput = strtolower($e->getProcess()->getErrorOutput() . $e->getProcess()->getOutput());

$needles = [
'permission denied',
'you must be root',
'operation not permitted',
'are you root',
'has to be run with superuser privileges',
];

foreach ($needles as $needle) {
if (str_contains($mergedProcessOutput, $needle)) {
return true;
}
}

return false;
}
}
45 changes: 45 additions & 0 deletions test/unit/Util/ProcessTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Php\PieUnitTest\Util;

use Php\Pie\Util\Process;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process as SymfonyProcess;

#[CoversClass(Process::class)]
final class ProcessTest extends TestCase
{
/** @return array<string, array{0: string, 1: string, 2: bool}> */
public static function permissionDeniedProvider(): array
{
return [
'apt 1 denied stderr' => ['', 'Error: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)', true],
'apt 1 denied stdout' => ['Error: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)', '', true],
'apt 2 denied stderr' => ['', 'Error: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), are you root?', true],
'apt 2 denied stdout' => ['Error: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), are you root?', '', true],
'dnf denied stderr' => ['', 'Error: This command has to be run with superuser privileges (under the root user on most systems).', true],
'dnf denied stdout' => ['Error: This command has to be run with superuser privileges (under the root user on most systems).', '', true],
'apk denied stderr' => ['', 'ERROR: Unable to open log: Permission denied', true],
'apk denied stdout' => ['ERROR: Unable to open log: Permission denied', '', true],
'no permission denied' => ['some other error', 'exit code 1', false],
];
}

#[DataProvider('permissionDeniedProvider')]
public function testProcessProbablyPermissionDenied(string $stdout, string $stderr, bool $expected): void
{
$symfonyProcess = $this->createMock(SymfonyProcess::class);
$symfonyProcess->method('getOutput')->willReturn($stdout);
$symfonyProcess->method('getErrorOutput')->willReturn($stderr);

$exception = $this->createMock(ProcessFailedException::class);
$exception->method('getProcess')->willReturn($symfonyProcess);

self::assertSame($expected, Process::processProbablyPermissionDenied($exception));
}
}
Loading