fix(nano): support Apple native application targets

master
韩天峰 2 weeks ago
parent 81b9c3741b
commit d2824d2d08
  1. 43
      examples/apple-native/README.md
  2. 15
      examples/apple-native/build-ios.sh
  3. 16
      phpunit/src/Build/NativeBuildConfigurationTest.php
  4. 24
      phpunit/src/CompilationStatisticsTest.php
  5. 33
      phpunit/src/CompilerBaseApiTest.php
  6. 4
      src/Build/NativeCommandOptionsTrait.php
  7. 5
      src/Translator.php

@ -6,37 +6,55 @@ application state and click behavior, while Objective-C++ only exposes small
native UI operations. Clicking the native button returns a control ID to
TypePHP, which updates the status label.
All three Apple targets support Nano mode. The `tpc.php` compiler itself still
runs dynamically on the host PHP and loads its Composer dependencies; `--nano`
only changes the generated native application. A Nano application compiles the
selected php-nano and PHPX sources directly and does not link a host `libphp` or
an iOS PHP/PHPX SDK archive.
## macOS
Requirements:
- macOS with Xcode Command Line Tools
- PHP 8.4 or 8.5 built with the embed SAPI (`libphp.dylib`)
- a matching PHPX build
- PHP 8.4 or 8.5 CLI for running `tpc.php`
- the compiler Composer dependencies, including php-nano and PHPX sources
- `PHPX_HOME` pointing to the PHPX source tree
Build from the TypePHP repository root:
```sh
export PHPX_HOME=/path/to/phpx
php bin/tpc.php examples/objective-c-macos/project.yml --no-progress
php bin/tpc.php examples/apple-native/project.yml --nano --no-progress
```
Run the native executable:
```sh
./examples/objective-c-macos/typephp_macos_hello
./examples/apple-native/typephp_macos_hello
```
To create a standard `.app` bundle that can be launched from Finder:
```sh
sh examples/objective-c-macos/package-app.sh
open "examples/objective-c-macos/dist/TypePHP macOS Hello.app"
sh examples/apple-native/package-app.sh
open "examples/apple-native/dist/TypePHP macOS Hello.app"
```
## iPhoneOS (physical iPhone)
For a Nano build, only full Xcode and the Composer source dependencies are
needed for compilation; the dedicated `iphoneos-arm64` PHP/PHPX SDK described
below is required only by the non-Nano build:
```sh
export PHPX_HOME=/path/to/phpx
sh examples/apple-native/build-ios.sh device --nano
```
Packaging and installing on a physical iPhone still requires a valid Apple
Development identity and provisioning profile.
The iPhone build is cross-compiled on macOS. It requires full Xcode (Command
Line Tools alone do not contain the iPhoneOS SDK), an Apple Development signing
identity, a provisioning profile for `org.swoole.typephp.ios-hello`, and an
@ -123,6 +141,19 @@ xcrun devicectl device install app \
## iOS Simulator (Apple silicon)
Nano mode does not need simulator PHP/PHPX archives. On Apple silicon, build,
package, install and launch it with:
```sh
export PHPX_HOME=/path/to/phpx
sh examples/apple-native/build-ios.sh simulator --nano
xcrun simctl install booted 'examples/apple-native/dist/TypePHP iOS Simulator Hello.app'
xcrun simctl launch booted org.swoole.typephp.ios-simulator-hello
```
The separate simulator SDK described below is required only by the non-Nano
build.
The simulator needs archives compiled for `arm64-apple-ios-simulator`.
The physical iPhone SDK above cannot be reused. Build the separate runtime
and PHPX SDK on the Mac:

@ -20,13 +20,26 @@ case "${1:-}" in
package_script=package-ios-simulator-app.sh
;;
*)
echo "Usage: $0 <device|simulator>" >&2
echo "Usage: $0 <device|simulator> [--nano]" >&2
exit 2
;;
esac
case "${2:-}" in
"") nano_arg= ;;
--nano) nano_arg=--nano ;;
*)
echo "Usage: $0 <device|simulator> [--nano]" >&2
exit 2
;;
esac
cd "$repo_dir"
if [ -n "$nano_arg" ]; then
php bin/tpc.php "examples/apple-native/$config" "$nano_arg" --no-progress
else
php bin/tpc.php "examples/apple-native/$config" --no-progress
fi
if [ "$package_script" = package-ios-app.sh ] &&
{ [ -z "${TYPEPHP_IOS_PROVISIONING_PROFILE:-}" ] || [ -z "${TYPEPHP_IOS_CODE_SIGN_IDENTITY:-}" ]; }; then

@ -134,6 +134,17 @@ final class NativeBuildConfigurationTest extends TestCase
);
}
public function testIosNanoUsesDarwinDeadStripLinkerFlag(): void
{
$compiler = $this->newCompiler(new Ios());
$compiler->enableNanoForTest();
$options = $compiler->getLinkCommandOptionsForTest();
self::assertStringContainsString('-Wl,-dead_strip', $options['ldflags']);
self::assertStringNotContainsString('-Wl,--gc-sections', $options['ldflags']);
}
public function testAndroidResolvesSelfContainedSdkAndSystemLibraries(): void
{
$phpxDir = $this->temporaryDirectory('phpx-android-host');
@ -283,6 +294,11 @@ final class NativeBuildConfigurationTest extends TestCase
return $this->getCommonCompileCommandOptions()->toArray();
}
public function getLinkCommandOptionsForTest(): array
{
return $this->getLinkCommandOptions()->toArray();
}
public function enableNanoForTest(): void
{
$this->nanoMode = true;

@ -178,6 +178,30 @@ final class CompilationStatisticsTest extends BaseTest
self::assertStringNotContainsString('date_module_entry', $registry);
}
public function testNanoSplCompositionIncludesItsNativeLinkDependencies(): void
{
$statistics = new CompilationStatistics();
$statistics->begin();
$statistics->record(CompilationStatistics::CLASSES, 'ArrayObject');
$statistics->finish();
$composition = (new NanoSourceComposer(TYPEPHP_ROOT_PATH))->compose(
sys_get_temp_dir() . '/typephp-nano-spl-dependencies-test',
'spl_dependencies_test',
false,
$statistics,
);
self::assertSame(['json', 'spl'], $composition['selection']->extensions);
self::assertContains('PHP_NANO_STANDARD_VAR=1', $composition['defines']);
self::assertTrue($this->containsSource($composition['packageSources'], 'ext/pcre/php_pcre.c'));
self::assertTrue($this->containsSource($composition['packageSources'], 'ext/standard/var.c'));
self::assertTrue($this->containsSource($composition['packageSources'], 'ext/standard/var_unserializer.c'));
$registry = file_get_contents($composition['registry']);
self::assertStringContainsString('pcre_module_entry', $registry);
self::assertStringContainsString('spl_module_entry', $registry);
}
public function testNanoComposerAlwaysIncludesSharedStandardSources(): void
{
$statistics = new CompilationStatistics();

@ -752,6 +752,39 @@ YAML);
);
}
public function testNanoBuildDoesNotGeneratePhpInfoHandlerForProjectMetadata(): void
{
global $translator;
$translator = $this->compiler;
$this->setPropertyValue('nanoMode', true);
$projectFile = $this->createProjectFile(<<<'YAML'
name: nano_metadata_demo
sources:
- main.php
version: 2.4.1
info:
Maintainer: TypePHP Team
YAML);
$files = $this->invokeMethod('parseProjectYaml', $projectFile);
$this->compiler->addFiles($files);
foreach ($files as $file) {
$this->compiler->prepareFile($file);
$this->compiler->convertFile($file);
}
$extension = file_get_contents($this->compiler->genExtension());
$this->assertStringNotContainsString('PHP_MINFO_FUNCTION(nano_metadata_demo)', $extension);
$this->assertStringNotContainsString('php_info_print_table_', $extension);
$this->assertStringContainsString(
" PHP_RSHUTDOWN(typephp_nano_metadata_demo),\n"
. " nullptr,\n"
. " \"2.4.1\",\n"
. ' STANDARD_MODULE_PROPERTIES,',
$extension,
);
}
public function testExtensionWithoutMetadataKeepsNullInfoAndVersion(): void
{
global $translator;

@ -187,7 +187,9 @@ trait NativeCommandOptionsTrait
$ldflags = trim('-static -B ' . escapeshellarg($this->getFullStaticMuslDir()) . ' ' . $ldflags);
}
if ($this->isNanoMode()) {
$gcSections = $this->isMacos() ? '-Wl,-dead_strip' : '-Wl,--gc-sections';
$gcSections = ($this->isMacos() || $this->isIosTarget())
? '-Wl,-dead_strip'
: '-Wl,--gc-sections';
$ldflags = trim($gcSections . ' ' . $ldflags);
if ($this->isWasiTarget()) {
$ldflags = trim(

@ -1962,7 +1962,10 @@ CODE;
$moduleVersion = $this->extensionVersion === ''
? 'nullptr'
: $this->genCharPtr($this->extensionVersion, true);
if ($this->extensionVersion !== '' || $this->extensionInfo !== []) {
// Nano executables do not expose phpinfo(), and pulling the generated
// MINFO handler into the module would force every Nano runtime to link
// ext/standard/info.c solely for php_info_print_table_* symbols.
if (!$this->isNanoMode() && ($this->extensionVersion !== '' || $this->extensionInfo !== [])) {
$moduleInfoFunction = 'PHP_MINFO(' . $moduleName . ')';
$code .= PHP_EOL . 'PHP_MINFO_FUNCTION(' . $moduleName . ') {' . PHP_EOL;
$code .= ' php_info_print_table_start();' . PHP_EOL;

Loading…
Cancel
Save