Skip to content

Commit f29a47e

Browse files
authored
Merge pull request #770 from asgrim/merge-up-1-5-2
Merge up fixes from 1.5.2 into 1.6.x
2 parents 971aaf2 + e7bcf39 commit f29a47e

10 files changed

Lines changed: 279 additions & 9 deletions

File tree

‎src/Building/PcreCompilerFlags.php‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Php\Pie\Building;
6+
7+
use Php\Pie\Platform\TargetPlatform;
8+
use Php\Pie\Util\Process;
9+
use Symfony\Component\Process\Exception\ProcessFailedException;
10+
11+
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
12+
final class PcreCompilerFlags
13+
{
14+
private function __construct()
15+
{
16+
}
17+
18+
/** @return non-empty-string|null */
19+
public static function forTargetPlatform(TargetPlatform $targetPlatform): string|null
20+
{
21+
if (! $targetPlatform->phpBinaryPath->usesExternalPcre()) {
22+
return null;
23+
}
24+
25+
try {
26+
$flags = Process::run(['pkg-config', '--cflags', 'libpcre2-8']);
27+
} catch (ProcessFailedException) {
28+
return null;
29+
}
30+
31+
return $flags !== '' ? $flags : null;
32+
}
33+
}

‎src/Building/UnixBuild.php‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Php\Pie\Building;
66

77
use Composer\IO\IOInterface;
8+
use Composer\Util\Platform;
89
use LogicException;
910
use Php\Pie\ComposerIntegration\BundledPhpExtensionsRepository;
1011
use Php\Pie\Downloading\DownloadedPackage;
@@ -24,6 +25,7 @@
2425
use function Safe\rename;
2526
use function sprintf;
2627
use function str_starts_with;
28+
use function trim;
2729

2830
use const DIRECTORY_SEPARATOR;
2931

@@ -104,7 +106,7 @@ private function buildFromSource(
104106
$configureOptions[] = '--with-php-config=' . $phpConfigPath;
105107
}
106108

107-
$this->configure($downloadedPackage, $configureOptions, $io, $outputCallback);
109+
$this->configure($downloadedPackage, $targetPlatform, $configureOptions, $io, $outputCallback);
108110

109111
$optionsOutput = count($configureOptions) ? ' with options: ' . implode(' ', $configureOptions) : '.';
110112
$io->write('<info>Configure complete</info>' . $optionsOutput);
@@ -208,11 +210,23 @@ private function phpize(
208210
*/
209211
private function configure(
210212
DownloadedPackage $downloadedPackage,
213+
TargetPlatform $targetPlatform,
211214
array $configureOptions,
212215
IOInterface $io,
213216
callable|null $outputCallback,
214217
): void {
215218
$configureCommand = ['./configure', ...$configureOptions];
219+
$configureEnv = null;
220+
221+
$pcreCompilerFlags = PcreCompilerFlags::forTargetPlatform($targetPlatform);
222+
if ($pcreCompilerFlags !== null) {
223+
$configureEnv = ['CPPFLAGS' => trim(Platform::getEnv('CPPFLAGS') . ' ' . $pcreCompilerFlags)];
224+
225+
$io->write(
226+
'<comment>PHP uses an external PCRE library, adding to CPPFLAGS: ' . $pcreCompilerFlags . '</comment>',
227+
verbosity: IOInterface::VERBOSE,
228+
);
229+
}
216230

217231
$io->write(
218232
'<comment>Running configure step with: ' . implode(' ', $configureCommand) . '</comment>',
@@ -223,6 +237,7 @@ private function configure(
223237
$configureCommand,
224238
$downloadedPackage->extractedSourcePath,
225239
outputCallback: $outputCallback,
240+
env: $configureEnv,
226241
);
227242
}
228243

‎src/File/BinaryFile.php‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
namespace Php\Pie\File;
66

77
use Php\Pie\Util;
8+
use Safe\Exceptions\HashException;
89

10+
use function error_clear_last;
911
use function file_exists;
1012
use function hash;
1113
use function hash_equals;
12-
use function Safe\hash_file;
14+
use function hash_file;
1315

1416
/**
1517
* @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
@@ -33,10 +35,23 @@ public function __construct(
3335
/** @param non-empty-string $filePath */
3436
public static function fromFileWithSha256Checksum(string $filePath): self
3537
{
36-
return new self(
37-
$filePath,
38-
hash_file(self::HASH_TYPE_SHA256, $filePath),
39-
);
38+
error_clear_last();
39+
40+
/**
41+
* `\Safe\hash_file` always passes 4 params, which breaks in PHP <8.1.20 / <8.2.7
42+
* We can use `\Safe\has_file` if it either gets fixed upstream, or we drop PHP 8.2
43+
*
44+
* @link https://github.com/php/php-src/issues/11180
45+
*
46+
* @phpstan-ignore theCodingMachineSafe.function
47+
*/
48+
$checksum = hash_file(self::HASH_TYPE_SHA256, $filePath);
49+
50+
if ($checksum === false) {
51+
throw HashException::createFromPhpError();
52+
}
53+
54+
return new self($filePath, $checksum);
4055
}
4156

4257
public function verify(): void

‎src/Installing/Ini/OndrejPhpenmod.php‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
use Symfony\Component\Process\Exception\ProcessFailedException;
1717

1818
use function array_unshift;
19+
use function dirname;
1920
use function file_exists;
2021
use function is_dir;
2122
use function is_writable;
2223
use function rtrim;
2324
use function Safe\preg_match;
2425
use function sprintf;
26+
use function str_starts_with;
2527

2628
use const DIRECTORY_SEPARATOR;
2729

@@ -40,7 +42,22 @@ public function __construct(
4042

4143
public function canBeUsed(TargetPlatform $targetPlatform): bool
4244
{
43-
return $this->phpenmodPath() !== null;
45+
return $this->phpenmodPath() !== null
46+
&& $this->targetPhpScansDirectoryManagedByPhpenmod($targetPlatform);
47+
}
48+
49+
private function targetPhpScansDirectoryManagedByPhpenmod(TargetPlatform $targetPlatform): bool
50+
{
51+
$additionalPhpIniPath = $targetPlatform->phpBinaryPath->additionalIniDirectory();
52+
53+
if ($additionalPhpIniPath === null) {
54+
return false;
55+
}
56+
57+
return str_starts_with(
58+
$additionalPhpIniPath,
59+
dirname(sprintf($this->modsAvailablePath, $targetPlatform->phpBinaryPath->majorMinorVersion())) . DIRECTORY_SEPARATOR,
60+
);
4461
}
4562

4663
public function setup(

‎src/Platform/TargetPhp/PhpBinaryPath.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ public function buildProvider(): string|null
294294
return null;
295295
}
296296

297+
public function usesExternalPcre(): bool
298+
{
299+
return preg_match('/^Configure Command\s*=>.*--with-external-pcre\b/m', $this->phpinfo()) === 1;
300+
}
301+
297302
/**
298303
* Returns a map where the key is the name of the extension and the value is the version ('0' if not defined)
299304
*
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
3+
if [ "$*" = "--cflags libpcre2-8" ]; then
4+
echo "$PIE_TEST_PCRE_CFLAGS"
5+
exit 0
6+
fi
7+
8+
echo "unknown fake pkg-config command: $*" >&2
9+
exit 1

‎test/end-to-end/Dockerfile‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ FROM boxproject/box:4.7.0 AS box_base
22
FROM alpine:3.24 AS alpine_base
33
FROM fedora:44 AS fedora_base
44
FROM ubuntu:26.04 AS ubuntu_base
5+
FROM ubuntu:22.04 AS ubuntu_2204_base
56
FROM homebrew/brew:4.6.20 AS homebrew_base
67
FROM redhat/ubi9-minimal:9.8 AS ubi9_base
78

@@ -41,16 +42,22 @@ COPY --from=build_pie_phar /app/pie.phar /usr/local/bin/pie
4142
RUN pie install --auto-install-build-tools -v asgrim/example-pie-extension:2.0.5
4243
RUN pie show
4344

44-
FROM homebrew_base AS test_pie_installs_build_tools_with_brew
45+
FROM homebrew_base AS homebrew_base_with_pie
4546
RUN brew update && brew install php
4647
COPY --from=build_pie_phar /app/pie.phar /usr/local/bin/pie
4748
USER root
4849
RUN rm -f /etc/apt/sources.list.d/github-cli.list && apt-get update && apt-get install -y unzip
4950
RUN apt-get remove --allow-remove-essential -y apt
5051
USER linuxbrew
52+
53+
FROM homebrew_base_with_pie AS test_pie_installs_build_tools_with_brew
5154
RUN pie install --auto-install-build-tools -v asgrim/example-pie-extension:2.0.5
5255
RUN pie show
5356

57+
FROM homebrew_base_with_pie AS test_pie_installs_extension_using_pcre_with_brew
58+
RUN pie install --auto-install-build-tools -v apcu/apcu
59+
RUN php -m | grep -i apcu
60+
5461
FROM ubuntu_base AS test_pie_installs_system_deps_on_ubuntu
5562
RUN apt-get update && apt install -y unzip curl wget gcc make autoconf libtool bison re2c pkg-config libzip-dev libssl-dev libonig-dev
5663
RUN mkdir -p /opt/php \
@@ -78,6 +85,13 @@ RUN dnf install -y php php-pecl-zip unzip gcc make autoconf bison re2c libtool p
7885
COPY --from=build_pie_phar /app/pie.phar /usr/local/bin/pie
7986
RUN pie install -v --auto-install-system-dependencies --force php/sodium
8087

88+
FROM ubuntu_2204_base AS test_pie_installs_on_php_with_three_argument_hash_file
89+
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y php8.1-cli php8.1-dev unzip ca-certificates gcc make autoconf libtool
90+
RUN php -r 'exit(version_compare(PHP_VERSION, "8.1.20", "<") ? 0 : 1);'
91+
COPY --from=build_pie_phar /app/pie.phar /usr/local/bin/pie
92+
RUN pie install -v apcu/apcu
93+
RUN pie show
94+
8195
FROM quay.io/rockylinux/rockylinux:8 AS test_lib64_detection_on_rocky8
8296
RUN dnf module enable -y php:8.2 && dnf install -y php-cli php-devel unzip gcc gcc-c++ make autoconf bison flex libtool git cmake openssl-devel cyrus-sasl-devel
8397
RUN git clone --depth 1 --branch 1.1.4 https://github.com/awesomized/libmemcached /tmp/libmemcached \
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Php\PieIntegrationTest\Building;
6+
7+
use Composer\Util\Platform;
8+
use Php\Pie\Building\PcreCompilerFlags;
9+
use Php\Pie\Platform\Architecture;
10+
use Php\Pie\Platform\OperatingSystem;
11+
use Php\Pie\Platform\OperatingSystemFamily;
12+
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
13+
use Php\Pie\Platform\TargetPlatform;
14+
use Php\Pie\Platform\ThreadSafetyMode;
15+
use PHPUnit\Framework\Attributes\CoversClass;
16+
use PHPUnit\Framework\TestCase;
17+
18+
use function getenv;
19+
use function Safe\realpath;
20+
21+
use const PATH_SEPARATOR;
22+
23+
#[CoversClass(PcreCompilerFlags::class)]
24+
final class PcreCompilerFlagsTest extends TestCase
25+
{
26+
private const FAKE_PKG_CONFIG_PATH = __DIR__ . '/../../assets/fake-pkg-config-pcre';
27+
private const PATH_WITHOUT_PKG_CONFIG = __DIR__;
28+
29+
/** @var array<array-key, mixed> */
30+
private array $oldEnv;
31+
32+
public function setUp(): void
33+
{
34+
if (Platform::isWindows()) {
35+
self::markTestSkipped('Bash script does not run on Windows.');
36+
}
37+
38+
$this->oldEnv = $_ENV;
39+
}
40+
41+
protected function tearDown(): void
42+
{
43+
$_ENV = $this->oldEnv;
44+
}
45+
46+
public function testFlagsFromPkgConfigAreReturnedWhenPhpUsesExternalPcre(): void
47+
{
48+
$_ENV['PATH'] = realpath(self::FAKE_PKG_CONFIG_PATH) . PATH_SEPARATOR . getenv('PATH');
49+
$_ENV['PIE_TEST_PCRE_CFLAGS'] = '-I/opt/homebrew/Cellar/pcre2/10.49/include';
50+
51+
self::assertSame(
52+
'-I/opt/homebrew/Cellar/pcre2/10.49/include',
53+
PcreCompilerFlags::forTargetPlatform($this->targetPlatformWherePhpUsesExternalPcre(true)),
54+
);
55+
}
56+
57+
public function testNoFlagsWhenPhpUsesBundledPcre(): void
58+
{
59+
$_ENV['PATH'] = realpath(self::FAKE_PKG_CONFIG_PATH) . PATH_SEPARATOR . getenv('PATH');
60+
$_ENV['PIE_TEST_PCRE_CFLAGS'] = '-I/opt/homebrew/Cellar/pcre2/10.49/include';
61+
62+
self::assertNull(PcreCompilerFlags::forTargetPlatform($this->targetPlatformWherePhpUsesExternalPcre(false)));
63+
}
64+
65+
public function testNoFlagsWhenPkgConfigReportsNone(): void
66+
{
67+
$_ENV['PATH'] = realpath(self::FAKE_PKG_CONFIG_PATH) . PATH_SEPARATOR . getenv('PATH');
68+
$_ENV['PIE_TEST_PCRE_CFLAGS'] = '';
69+
70+
self::assertNull(PcreCompilerFlags::forTargetPlatform($this->targetPlatformWherePhpUsesExternalPcre(true)));
71+
}
72+
73+
public function testNoFlagsWhenPkgConfigIsNotAvailable(): void
74+
{
75+
$_ENV['PATH'] = realpath(self::PATH_WITHOUT_PKG_CONFIG);
76+
77+
self::assertNull(PcreCompilerFlags::forTargetPlatform($this->targetPlatformWherePhpUsesExternalPcre(true)));
78+
}
79+
80+
private function targetPlatformWherePhpUsesExternalPcre(bool $usesExternalPcre): TargetPlatform
81+
{
82+
$phpBinaryPath = $this->createMock(PhpBinaryPath::class);
83+
$phpBinaryPath->method('usesExternalPcre')->willReturn($usesExternalPcre);
84+
85+
return new TargetPlatform(
86+
OperatingSystem::NonWindows,
87+
OperatingSystemFamily::Linux,
88+
$phpBinaryPath,
89+
Architecture::x86_64,
90+
ThreadSafetyMode::NonThreadSafe,
91+
1,
92+
null,
93+
null,
94+
);
95+
}
96+
}

‎test/unit/Installing/Ini/OndrejPhpenmodTest.php‎

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,53 @@ public function testCanBeUsedReturnsFalseWhenPhpenmodNotInPath(): void
115115
#[RequiresOperatingSystemFamily('Linux')]
116116
public function testCanBeUsedReturnsTrueWhenPhpenmodInPath(): void
117117
{
118+
$this->mockPhpBinary
119+
->method('additionalIniDirectory')
120+
->willReturn('/etc/php/8.4/cli/conf.d');
121+
$this->mockPhpBinary
122+
->method('majorMinorVersion')
123+
->willReturn('8.4');
124+
118125
self::assertTrue(
119126
(new OndrejPhpenmod(
120127
$this->checkAndAddExtensionToIniIfNeeded,
121128
self::GOOD_PHPENMOD,
122-
self::NON_EXISTENT_MODS_AVAILABLE_PATH,
129+
))->canBeUsed($this->targetPlatform),
130+
);
131+
}
132+
133+
#[RequiresOperatingSystemFamily('Linux')]
134+
public function testCanBeUsedReturnsFalseWhenTargetPhpScansTheOndrejConfDirectoryOfAnotherVersion(): void
135+
{
136+
$this->mockPhpBinary
137+
->method('additionalIniDirectory')
138+
->willReturn('/etc/php/8.3/cli/conf.d');
139+
$this->mockPhpBinary
140+
->method('majorMinorVersion')
141+
->willReturn('8.4');
142+
143+
self::assertFalse(
144+
(new OndrejPhpenmod(
145+
$this->checkAndAddExtensionToIniIfNeeded,
146+
self::GOOD_PHPENMOD,
147+
))->canBeUsed($this->targetPlatform),
148+
);
149+
}
150+
151+
#[RequiresOperatingSystemFamily('Linux')]
152+
public function testCanBeUsedReturnsFalseWhenTargetPhpDoesNotScanTheOndrejConfDirectory(): void
153+
{
154+
$this->mockPhpBinary
155+
->method('additionalIniDirectory')
156+
->willReturn('/home/someone/php-src-build/lib/php.ini.d');
157+
$this->mockPhpBinary
158+
->method('majorMinorVersion')
159+
->willReturn('8.4');
160+
161+
self::assertFalse(
162+
(new OndrejPhpenmod(
163+
$this->checkAndAddExtensionToIniIfNeeded,
164+
self::GOOD_PHPENMOD,
123165
))->canBeUsed($this->targetPlatform),
124166
);
125167
}

0 commit comments

Comments
 (0)