Skip to content

Commit cf3bdc4

Browse files
authored
Merge pull request #580 from asgrim/541-replace-placeholders
541: replace some common placeholders before build
2 parents 896aace + c37d0ac commit cf3bdc4

File tree

7 files changed

+375
-1
lines changed

7 files changed

+375
-1
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"symfony/console": "^6.4.34",
3838
"symfony/event-dispatcher": "^6.4.32",
3939
"symfony/process": "^6.4.33",
40+
"thecodingmachine/safe": "^3.4",
4041
"thephpf/attestation": "^0.0.5",
4142
"webmozart/assert": "^1.12.1"
4243
},

composer.lock

Lines changed: 144 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/extension-maintainers.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,3 +579,18 @@ jobs:
579579
```
580580
581581
Source: [https://github.com/php/php-windows-builder?tab=readme-ov-file#examples](https://github.com/php/php-windows-builder?tab=readme-ov-file#examples)
582+
583+
## Other features
584+
585+
### Placeholder Replacement
586+
587+
To help backwards compatibility with PECL extensions, PIE supports some automatic placeholder replacements within
588+
all `.c` and .`h` files found within the downloaded source directory. These placeholders are replaced after the
589+
download step, and before the build step. PIE will automatically replace the following placeholders:
590+
591+
| Placeholder | Description | Example |
592+
|-------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
593+
| `@name@`, `@package_name@`, `@package-name@` | The short, internal name of the PHP extension (e.g., `xdebug`). _Note: this is not the Packagist package name (e.g. `xdebug/xdebug`)_. | `xdebug` |
594+
| `@version@`, `@package_version@`, `@package-version@` | The "pretty" version of the package defined in `composer.json`. | `3.3.2` |
595+
| `@release_date@`, `@release-date@` | The formatted release date according to Composer package metadata. | `2024-01-15T10:00:00+00:00` |
596+
| `@php_bin@`, `@php-bin@` | The full path to the PHP binary executable used during the build. | `/usr/bin/php8.4` |
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Php\Pie\Building;
6+
7+
use Composer\IO\IOInterface;
8+
use DateTimeInterface;
9+
use Php\Pie\Downloading\DownloadedPackage;
10+
use Php\Pie\Platform\TargetPlatform;
11+
use RecursiveDirectoryIterator;
12+
use RecursiveIteratorIterator;
13+
use Safe\Exceptions\FilesystemException;
14+
use SplFileInfo;
15+
16+
use function assert;
17+
use function in_array;
18+
use function Safe\file_get_contents;
19+
use function Safe\file_put_contents;
20+
use function str_ireplace;
21+
22+
/**
23+
* This tries to support some of the PECL style replacements, but is less flexible. In PECL, you would define in your
24+
* package.xml something like;
25+
*
26+
* <tasks:replace from="@package_version@" to="version" type="package-info" />
27+
*
28+
* This would perform a replacement on the given file. I [searched through GitHub](https://github.com/search?q=%22tasks%3Areplace%22+language%3AXML&type=code&p=1&l=XML)
29+
* to find some common replacements to make, and defined them in a mostly hard-coded way, or at least the ones that
30+
* make sense or low-hanging fruit (all underscores can also use hyphens):
31+
*
32+
* - @name@ or @package_name@ - replaces with the extension name (NOT the Composer package name)
33+
* - @version@ or @package_version@ - replaces with the Composer "pretty" version
34+
* - @release_date@ - release date according to Composer package, formatted 'Y-m-d\TH:i:sP'
35+
* - @php_bin@ - path to the PHP binary
36+
*
37+
* Some omitted replacements are `@php_dir@` (seems to actually point to PEAR directory, which is redundant anyway),
38+
* `@bin_dir@` (could get this with PHP_BINDIR constant from the $targetPlatform, but not sure if is worth the time),
39+
* and a few other variations (@phd_ide_version@ for example, which could be replaced with @package_version@).
40+
*
41+
* If this feature needs more flexibility in future, we can look into it, but this implementation seems to be a fairly
42+
* easy win for some basic replacements anyway...
43+
*/
44+
class PlaceholderReplacer
45+
{
46+
private const FILE_EXTENSIONS = ['c', 'h'];
47+
48+
public function replacePlaceholdersWithPlaceholderReplacements(IOInterface $io, TargetPlatform $targetPlatform, DownloadedPackage $downloadedPackage): void
49+
{
50+
$replacements = [
51+
'ext-name' => $downloadedPackage->package->extensionName()->name(),
52+
'version' => $downloadedPackage->package->composerPackage()->getPrettyVersion(),
53+
'release-date' => (string) $downloadedPackage->package->composerPackage()->getReleaseDate()?->format(DateTimeInterface::ATOM),
54+
'php-bin' => $targetPlatform->phpBinaryPath->phpBinaryPath,
55+
];
56+
57+
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($downloadedPackage->extractedSourcePath)) as $file) {
58+
assert($file instanceof SplFileInfo);
59+
60+
if (! $file->isFile() || ! in_array($file->getExtension(), self::FILE_EXTENSIONS)) {
61+
continue;
62+
}
63+
64+
$io->write('Replacing placeholders in: ' . $file->getPathname(), verbosity: IOInterface::VERY_VERBOSE);
65+
try {
66+
$this->replaceReplacementsInFile($replacements, $file->getPathname());
67+
} catch (FilesystemException $e) {
68+
$io->write('Placeholder replacement failed in : ' . $file->getPathname(), verbosity: IOInterface::VERBOSE);
69+
$io->write((string) $e, verbosity: IOInterface::VERBOSE);
70+
}
71+
}
72+
}
73+
74+
/**
75+
* @param array{ext-name: string, version: string, release-date: string, php-bin: string} $replacements
76+
*
77+
* @throws FilesystemException
78+
*/
79+
private function replaceReplacementsInFile(array $replacements, string $filename): void
80+
{
81+
file_put_contents(
82+
$filename,
83+
str_ireplace(
84+
[
85+
'@name@',
86+
'@package_name@',
87+
'@package-name@',
88+
'@package_version@',
89+
'@package-version@',
90+
'@release_date@',
91+
'@release-date@',
92+
'@php_bin@',
93+
'@php-bin@',
94+
],
95+
[
96+
$replacements['ext-name'],
97+
$replacements['ext-name'],
98+
$replacements['ext-name'],
99+
$replacements['version'],
100+
$replacements['version'],
101+
$replacements['release-date'],
102+
$replacements['release-date'],
103+
$replacements['php-bin'],
104+
$replacements['php-bin'],
105+
],
106+
file_get_contents($filename),
107+
),
108+
);
109+
}
110+
}

src/ComposerIntegration/InstallAndBuildProcess.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Composer\Package\CompletePackageInterface;
88
use Composer\PartialComposer;
99
use Php\Pie\Building\Build;
10+
use Php\Pie\Building\PlaceholderReplacer;
1011
use Php\Pie\DependencyResolver\Package;
1112
use Php\Pie\Downloading\DownloadedPackage;
1213
use Php\Pie\Installing\Install;
@@ -20,6 +21,7 @@ public function __construct(
2021
private readonly Build $pieBuild,
2122
private readonly Install $pieInstall,
2223
private readonly InstalledJsonMetadata $installedJsonMetadata,
24+
private readonly PlaceholderReplacer $placeholderReplacer,
2325
) {
2426
}
2527

@@ -42,6 +44,12 @@ public function __invoke(
4244
$downloadedPackage->extractedSourcePath,
4345
));
4446

47+
$this->placeholderReplacer->replacePlaceholdersWithPlaceholderReplacements(
48+
$io,
49+
$composerRequest->targetPlatform,
50+
$downloadedPackage,
51+
);
52+
4553
$this->installedJsonMetadata->addDownloadMetadata(
4654
$composer,
4755
$composerRequest,

0 commit comments

Comments
 (0)