mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-15 12:55:36 +08:00
Compare commits
19 Commits
v3c/llvm-t
...
bf6216e59f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf6216e59f | ||
|
|
411ad7cc0f | ||
|
|
0761267eb3 | ||
|
|
072a3b5505 | ||
|
|
0e5738b710 | ||
|
|
3ff9426e50 | ||
|
|
6057394641 | ||
|
|
2f4fb9d28f | ||
|
|
96ab2de4b1 | ||
|
|
c641c3b8db | ||
|
|
48d6e9ebc2 | ||
|
|
6cab47db67 | ||
|
|
ec3fd0f4b0 | ||
|
|
e72f9aa623 | ||
|
|
c666cd6cd0 | ||
|
|
b8dd508148 | ||
|
|
37d8b87c3b | ||
|
|
5172580294 | ||
|
|
99e05aa22b |
9
.github/workflows/release-build.yml
vendored
9
.github/workflows/release-build.yml
vendored
@@ -38,6 +38,9 @@ jobs:
|
|||||||
- name: "windows-x64"
|
- name: "windows-x64"
|
||||||
os: "ubuntu-latest"
|
os: "ubuntu-latest"
|
||||||
filename: "spc-windows-x64.exe"
|
filename: "spc-windows-x64.exe"
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
attestations: write
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: "actions/checkout@v5"
|
uses: "actions/checkout@v5"
|
||||||
@@ -105,6 +108,12 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: "Generate build provenance attestation"
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: actions/attest-build-provenance@v4
|
||||||
|
with:
|
||||||
|
subject-path: "${{ github.workspace }}/${{ matrix.operating-system.name == 'windows-x64' && 'spc.exe' || 'spc' }}"
|
||||||
|
|
||||||
- name: "Copy file"
|
- name: "Copy file"
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ matrix.operating-system.name }}" != "windows-x64" ]; then
|
if [ "${{ matrix.operating-system.name }}" != "windows-x64" ]; then
|
||||||
|
|||||||
20
config/pkg/ext/ext-fastchart.yml
Normal file
20
config/pkg/ext/ext-fastchart.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
ext-fastchart:
|
||||||
|
type: php-extension
|
||||||
|
artifact:
|
||||||
|
source:
|
||||||
|
type: ghtar
|
||||||
|
repo: iliaal/fastchart
|
||||||
|
extract: php-src/ext/fastchart
|
||||||
|
prefer-stable: true
|
||||||
|
metadata:
|
||||||
|
license-files: [LICENSE]
|
||||||
|
depends:
|
||||||
|
- freetype
|
||||||
|
suggests:
|
||||||
|
- libpng
|
||||||
|
- libjpeg
|
||||||
|
- libwebp
|
||||||
|
php-extension:
|
||||||
|
os:
|
||||||
|
- Linux
|
||||||
|
- Darwin
|
||||||
14
config/pkg/ext/ext-fastjson.yml
Normal file
14
config/pkg/ext/ext-fastjson.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
ext-fastjson:
|
||||||
|
type: php-extension
|
||||||
|
artifact:
|
||||||
|
source:
|
||||||
|
type: ghtar
|
||||||
|
repo: iliaal/fastjson
|
||||||
|
extract: php-src/ext/fastjson
|
||||||
|
prefer-stable: true
|
||||||
|
metadata:
|
||||||
|
license-files: [LICENSE]
|
||||||
|
php-extension:
|
||||||
|
os:
|
||||||
|
- Linux
|
||||||
|
- Darwin
|
||||||
@@ -20,8 +20,8 @@ class unixodbc extends LibraryPackage
|
|||||||
{
|
{
|
||||||
$sysconf_selector = match ($os = SystemTarget::getTargetOS()) {
|
$sysconf_selector = match ($os = SystemTarget::getTargetOS()) {
|
||||||
'Darwin' => match (SystemTarget::getTargetArch()) {
|
'Darwin' => match (SystemTarget::getTargetArch()) {
|
||||||
'x86_64' => '/usr/local/etc',
|
'x86_64' => is_dir('/usr/local/etc') ? '/usr/local/etc' : '/opt/local/etc',
|
||||||
'aarch64' => '/opt/homebrew/etc',
|
'aarch64' => is_dir('/opt/homebrew/etc') ? '/opt/homebrew/etc' : '/opt/local/etc',
|
||||||
default => throw new WrongUsageException('Unsupported architecture: ' . GNU_ARCH),
|
default => throw new WrongUsageException('Unsupported architecture: ' . GNU_ARCH),
|
||||||
},
|
},
|
||||||
'Linux' => '/etc',
|
'Linux' => '/etc',
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class GenExtTestMatrixCommand extends BaseCommand
|
|||||||
'glfw',
|
'glfw',
|
||||||
'imagick',
|
'imagick',
|
||||||
'intl',
|
'intl',
|
||||||
|
'mongodb',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -73,13 +73,20 @@ class LinuxMuslCheck
|
|||||||
$prefix = 'sudo ';
|
$prefix = 'sudo ';
|
||||||
logger()->warning('Current user is not root, using sudo for running command');
|
logger()->warning('Current user is not root, using sudo for running command');
|
||||||
}
|
}
|
||||||
|
$sysEnv = ['CC' => 'gcc', 'CXX' => 'g++', 'AR' => 'ar', 'LD' => 'ld', 'RANLIB' => 'ranlib'];
|
||||||
|
$envFlags = '';
|
||||||
|
foreach ($sysEnv as $k => $v) {
|
||||||
|
$envFlags .= "{$k}={$v} ";
|
||||||
|
}
|
||||||
|
$envFlags = rtrim($envFlags);
|
||||||
$shell = shell()->cd(SOURCE_PATH . '/musl-wrapper')
|
$shell = shell()->cd(SOURCE_PATH . '/musl-wrapper')
|
||||||
->exec('CC=gcc CXX=g++ AR=ar LD=ld ./configure --disable-gcc-wrapper')
|
->setEnv($sysEnv)
|
||||||
->exec('CC=gcc CXX=g++ AR=ar LD=ld make -j');
|
->exec('./configure --disable-gcc-wrapper')
|
||||||
|
->exec('make -j');
|
||||||
if ($prefix !== '') {
|
if ($prefix !== '') {
|
||||||
f_passthru('cd ' . SOURCE_PATH . "/musl-wrapper && CC=gcc CXX=g++ AR=ar LD=ld {$prefix}make install");
|
f_passthru('cd ' . SOURCE_PATH . "/musl-wrapper && {$envFlags} {$prefix}make install");
|
||||||
} else {
|
} else {
|
||||||
$shell->exec("CC=gcc CXX=g++ AR=ar LD=ld {$prefix}make install");
|
$shell->exec("{$prefix}make install");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,15 +33,20 @@ class MacOSToolCheck
|
|||||||
'glibtoolize',
|
'glibtoolize',
|
||||||
];
|
];
|
||||||
|
|
||||||
#[CheckItem('if homebrew has installed', limit_os: 'Darwin', level: 998)]
|
#[CheckItem('if homebrew or macports has installed', limit_os: 'Darwin', level: 998)]
|
||||||
public function checkBrew(): ?CheckResult
|
public function checkBrewOrPorts(): ?CheckResult
|
||||||
{
|
{
|
||||||
if (($path = MacOSUtil::findCommand('brew')) === null) {
|
$brewPath = MacOSUtil::findCommand('brew');
|
||||||
return CheckResult::fail('Homebrew is not installed', 'brew');
|
$portPath = MacOSUtil::findCommand('port');
|
||||||
}
|
|
||||||
if ($path !== '/opt/homebrew/bin/brew' && getenv('GNU_ARCH') === 'aarch64') {
|
if ($brewPath && $brewPath !== '/opt/homebrew/bin/brew' && getenv('GNU_ARCH') === 'aarch64') {
|
||||||
return CheckResult::fail('Current homebrew (/usr/local/bin/homebrew) is not installed for M1 Mac, please re-install homebrew in /opt/homebrew/ !');
|
return CheckResult::fail('Current homebrew (/usr/local/bin/homebrew) is not installed for M1 Mac, please re-install homebrew in /opt/homebrew/ !');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($brewPath === null && $portPath === null) {
|
||||||
|
return CheckResult::fail('Homebrew is not installed', 'brew');
|
||||||
|
}
|
||||||
|
|
||||||
return CheckResult::ok();
|
return CheckResult::ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,8 +65,8 @@ class MacOSToolCheck
|
|||||||
return CheckResult::ok();
|
return CheckResult::ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[CheckItem('if homebrew llvm are installed', limit_os: 'Darwin')]
|
#[CheckItem('if homebrew or macports llvm are installed', limit_os: 'Darwin')]
|
||||||
public function checkBrewLLVM(): ?CheckResult
|
public function checkBrewOrPortsLLVM(): ?CheckResult
|
||||||
{
|
{
|
||||||
if (getenv('SPC_USE_LLVM') === 'brew') {
|
if (getenv('SPC_USE_LLVM') === 'brew') {
|
||||||
$homebrew_prefix = getenv('HOMEBREW_PREFIX') ?: (SystemTarget::getTargetArch() === 'aarch64' ? '/opt/homebrew' : '/usr/local/homebrew');
|
$homebrew_prefix = getenv('HOMEBREW_PREFIX') ?: (SystemTarget::getTargetArch() === 'aarch64' ? '/opt/homebrew' : '/usr/local/homebrew');
|
||||||
@@ -71,6 +76,16 @@ class MacOSToolCheck
|
|||||||
}
|
}
|
||||||
return CheckResult::ok($path);
|
return CheckResult::ok($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getenv('SPC_USE_LLVM') === 'port') {
|
||||||
|
$macportsPrefix = '/opt/local';
|
||||||
|
|
||||||
|
if (($path = MacOSUtil::findCommand('clang', ["{$macportsPrefix}/bin"])) === null) {
|
||||||
|
return CheckResult::fail('MacPorts llvm is not installed', 'build-tools', ['missing' => ['llvm']]);
|
||||||
|
}
|
||||||
|
return CheckResult::ok($path);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +106,7 @@ class MacOSToolCheck
|
|||||||
if ($command_path !== []) {
|
if ($command_path !== []) {
|
||||||
return CheckResult::fail("Current {$bison} version is too old: " . $matches[0]);
|
return CheckResult::fail("Current {$bison} version is too old: " . $matches[0]);
|
||||||
}
|
}
|
||||||
return $this->checkBisonVersion(['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin']);
|
return $this->checkBisonVersion(['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin', '/opt/local/bin']);
|
||||||
}
|
}
|
||||||
return CheckResult::ok($matches[0]);
|
return CheckResult::ok($matches[0]);
|
||||||
}
|
}
|
||||||
@@ -108,6 +123,9 @@ class MacOSToolCheck
|
|||||||
#[FixItem('build-tools')]
|
#[FixItem('build-tools')]
|
||||||
public function fixBuildTools(array $missing): bool
|
public function fixBuildTools(array $missing): bool
|
||||||
{
|
{
|
||||||
|
$brewPath = MacOSUtil::findCommand('brew');
|
||||||
|
$portPath = MacOSUtil::findCommand('port');
|
||||||
|
|
||||||
$replacement = [
|
$replacement = [
|
||||||
'glibtoolize' => 'libtool',
|
'glibtoolize' => 'libtool',
|
||||||
];
|
];
|
||||||
@@ -115,7 +133,18 @@ class MacOSToolCheck
|
|||||||
if (isset($replacement[$cmd])) {
|
if (isset($replacement[$cmd])) {
|
||||||
$cmd = $replacement[$cmd];
|
$cmd = $replacement[$cmd];
|
||||||
}
|
}
|
||||||
shell()->exec('brew install --formula ' . escapeshellarg($cmd));
|
|
||||||
|
if ($brewPath !== null) {
|
||||||
|
shell()->exec('brew install --formula ' . escapeshellarg($cmd));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($portPath !== null) {
|
||||||
|
shell()->exec('port install ' . escapeshellarg($cmd));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,9 +302,12 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
|||||||
set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES "{$include}")
|
set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES "{$include}")
|
||||||
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES "{$include}")
|
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES "{$include}")
|
||||||
CMAKE;
|
CMAKE;
|
||||||
// Whoops, linux may need CMAKE_AR sometimes
|
// pin AR/RANLIB so cmake uses zig-ar/zig-ranlib instead of system /usr/bin/ranlib (zig archives need it)
|
||||||
if (PHP_OS_FAMILY === 'Linux') {
|
if (PHP_OS_FAMILY === 'Linux') {
|
||||||
$toolchain .= "\nSET(CMAKE_AR \"ar\")";
|
$ar = getenv('SPC_DEFAULT_AR') ?: getenv('AR') ?: 'ar';
|
||||||
|
$ranlib = getenv('SPC_DEFAULT_RANLIB') ?: (getenv('RANLIB') ?: 'ranlib');
|
||||||
|
$toolchain .= "\nSET(CMAKE_AR \"{$ar}\")";
|
||||||
|
$toolchain .= "\nSET(CMAKE_RANLIB \"{$ranlib}\")";
|
||||||
}
|
}
|
||||||
FileSystem::writeFile(SOURCE_PATH . '/toolchain.cmake', $toolchain);
|
FileSystem::writeFile(SOURCE_PATH . '/toolchain.cmake', $toolchain);
|
||||||
return $created = realpath(SOURCE_PATH . '/toolchain.cmake');
|
return $created = realpath(SOURCE_PATH . '/toolchain.cmake');
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class ClangBrewToolchain extends ClangNativeToolchain
|
|||||||
GlobalEnvManager::putenv("SPC_DEFAULT_CC={$homebrew_prefix}/opt/llvm/bin/clang");
|
GlobalEnvManager::putenv("SPC_DEFAULT_CC={$homebrew_prefix}/opt/llvm/bin/clang");
|
||||||
GlobalEnvManager::putenv("SPC_DEFAULT_CXX={$homebrew_prefix}/opt/llvm/bin/clang++");
|
GlobalEnvManager::putenv("SPC_DEFAULT_CXX={$homebrew_prefix}/opt/llvm/bin/clang++");
|
||||||
GlobalEnvManager::putenv("SPC_DEFAULT_AR={$homebrew_prefix}/opt/llvm/bin/llvm-ar");
|
GlobalEnvManager::putenv("SPC_DEFAULT_AR={$homebrew_prefix}/opt/llvm/bin/llvm-ar");
|
||||||
|
GlobalEnvManager::putenv("SPC_DEFAULT_RANLIB={$homebrew_prefix}/opt/llvm/bin/llvm-ranlib");
|
||||||
GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld');
|
GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld');
|
||||||
GlobalEnvManager::addPathIfNotExists("{$homebrew_prefix}/opt/llvm/bin");
|
GlobalEnvManager::addPathIfNotExists("{$homebrew_prefix}/opt/llvm/bin");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class ClangNativeToolchain implements UnixToolchainInterface
|
|||||||
GlobalEnvManager::putenv('SPC_DEFAULT_CC=clang');
|
GlobalEnvManager::putenv('SPC_DEFAULT_CC=clang');
|
||||||
GlobalEnvManager::putenv('SPC_DEFAULT_CXX=clang++');
|
GlobalEnvManager::putenv('SPC_DEFAULT_CXX=clang++');
|
||||||
GlobalEnvManager::putenv('SPC_DEFAULT_AR=ar');
|
GlobalEnvManager::putenv('SPC_DEFAULT_AR=ar');
|
||||||
|
GlobalEnvManager::putenv('SPC_DEFAULT_RANLIB=ranlib');
|
||||||
GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld');
|
GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
src/StaticPHP/Toolchain/ClangPortsToolchain.php
Normal file
20
src/StaticPHP/Toolchain/ClangPortsToolchain.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace StaticPHP\Toolchain;
|
||||||
|
|
||||||
|
use StaticPHP\Util\GlobalEnvManager;
|
||||||
|
|
||||||
|
class ClangPortsToolchain extends ClangNativeToolchain
|
||||||
|
{
|
||||||
|
public function initEnv(): void
|
||||||
|
{
|
||||||
|
$macports_prefix = getenv('MACPORTS_PREFIX') ?: '/opt/local';
|
||||||
|
GlobalEnvManager::putenv("SPC_DEFAULT_CC={$macports_prefix}/bin/clang");
|
||||||
|
GlobalEnvManager::putenv("SPC_DEFAULT_CXX={$macports_prefix}/bin/clang++");
|
||||||
|
GlobalEnvManager::putenv("SPC_DEFAULT_AR={$macports_prefix}/bin/llvm-ar");
|
||||||
|
GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld');
|
||||||
|
GlobalEnvManager::addPathIfNotExists("{$macports_prefix}/bin");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ class GccNativeToolchain implements UnixToolchainInterface
|
|||||||
GlobalEnvManager::putenv('SPC_DEFAULT_CC=gcc');
|
GlobalEnvManager::putenv('SPC_DEFAULT_CC=gcc');
|
||||||
GlobalEnvManager::putenv('SPC_DEFAULT_CXX=g++');
|
GlobalEnvManager::putenv('SPC_DEFAULT_CXX=g++');
|
||||||
GlobalEnvManager::putenv('SPC_DEFAULT_AR=ar');
|
GlobalEnvManager::putenv('SPC_DEFAULT_AR=ar');
|
||||||
|
GlobalEnvManager::putenv('SPC_DEFAULT_RANLIB=ranlib');
|
||||||
GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld');
|
GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class ToolchainManager
|
|||||||
'Windows' => MSVCToolchain::class,
|
'Windows' => MSVCToolchain::class,
|
||||||
'Darwin' => match (getenv('SPC_USE_LLVM') ?: 'system') {
|
'Darwin' => match (getenv('SPC_USE_LLVM') ?: 'system') {
|
||||||
'brew' => ClangBrewToolchain::class,
|
'brew' => ClangBrewToolchain::class,
|
||||||
|
'port' => ClangPortsToolchain::class,
|
||||||
default => ClangNativeToolchain::class,
|
default => ClangNativeToolchain::class,
|
||||||
},
|
},
|
||||||
default => throw new WrongUsageException('Unsupported OS family: ' . PHP_OS_FAMILY),
|
default => throw new WrongUsageException('Unsupported OS family: ' . PHP_OS_FAMILY),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class ZigToolchain implements UnixToolchainInterface
|
|||||||
GlobalEnvManager::putenv('SPC_DEFAULT_CC=zig-cc');
|
GlobalEnvManager::putenv('SPC_DEFAULT_CC=zig-cc');
|
||||||
GlobalEnvManager::putenv('SPC_DEFAULT_CXX=zig-c++');
|
GlobalEnvManager::putenv('SPC_DEFAULT_CXX=zig-c++');
|
||||||
GlobalEnvManager::putenv('SPC_DEFAULT_AR=zig-ar');
|
GlobalEnvManager::putenv('SPC_DEFAULT_AR=zig-ar');
|
||||||
|
GlobalEnvManager::putenv('SPC_DEFAULT_RANLIB=zig-ranlib');
|
||||||
GlobalEnvManager::putenv('SPC_DEFAULT_LD=zig-ld.lld');
|
GlobalEnvManager::putenv('SPC_DEFAULT_LD=zig-ld.lld');
|
||||||
|
|
||||||
// Generate additional objects needed for zig toolchain
|
// Generate additional objects needed for zig toolchain
|
||||||
|
|||||||
@@ -134,10 +134,10 @@ class GlobalEnvManager
|
|||||||
}
|
}
|
||||||
// test bison
|
// test bison
|
||||||
if (PHP_OS_FAMILY === 'Darwin') {
|
if (PHP_OS_FAMILY === 'Darwin') {
|
||||||
if ($bison = MacOSUtil::findCommand('bison', ['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin'])) {
|
if ($bison = MacOSUtil::findCommand('bison', ['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin', '/opt/local/bin/bison'])) {
|
||||||
self::putenv("BISON={$bison}");
|
self::putenv("BISON={$bison}");
|
||||||
}
|
}
|
||||||
if ($yacc = MacOSUtil::findCommand('yacc', ['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin'])) {
|
if ($yacc = MacOSUtil::findCommand('yacc', ['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin', '/opt/local/bin/yacc'])) {
|
||||||
self::putenv("YACC={$yacc}");
|
self::putenv("YACC={$yacc}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -256,10 +256,30 @@ function clean_spaces(string $string): string
|
|||||||
*/
|
*/
|
||||||
function deduplicate_flags(string $flags): string
|
function deduplicate_flags(string $flags): string
|
||||||
{
|
{
|
||||||
$tokens = preg_split('/\s+/', trim($flags));
|
// Flags that take their value as a separate token.
|
||||||
|
static $paired = [
|
||||||
|
'-Xclang', '-Xpreprocessor', '-Xlinker', '-Xassembler',
|
||||||
|
'-framework', '-arch', '-target',
|
||||||
|
'-include', '-imacros', '-isystem', '-isysroot', '-iquote', '-idirafter',
|
||||||
|
'-MT', '-MF', '-MQ',
|
||||||
|
];
|
||||||
|
|
||||||
|
$tokens = preg_split('/\s+/', trim($flags)) ?: [];
|
||||||
|
|
||||||
|
// Group paired flag+value into a single atom before dedup.
|
||||||
|
$atoms = [];
|
||||||
|
$n = count($tokens);
|
||||||
|
for ($i = 0; $i < $n; ++$i) {
|
||||||
|
if (in_array($tokens[$i], $paired, true) && $i + 1 < $n) {
|
||||||
|
$atoms[] = $tokens[$i] . ' ' . $tokens[$i + 1];
|
||||||
|
++$i;
|
||||||
|
} else {
|
||||||
|
$atoms[] = $tokens[$i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reverse, unique, reverse back - keeps last occurrence of duplicates
|
// Reverse, unique, reverse back - keeps last occurrence of duplicates
|
||||||
$deduplicated = array_reverse(array_unique(array_reverse($tokens)));
|
$deduplicated = array_reverse(array_unique(array_reverse($atoms)));
|
||||||
|
|
||||||
return implode(' ', $deduplicated);
|
return implode(' ', $deduplicated);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user