fix(nano): support Apple native application targets

master
韩天峰 2 weeks ago
parent 81b9c3741b
commit d2824d2d08
  1. 43
      examples/apple-native/README.md
  2. 17
      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 native UI operations. Clicking the native button returns a control ID to
TypePHP, which updates the status label. 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 ## macOS
Requirements: Requirements:
- macOS with Xcode Command Line Tools - macOS with Xcode Command Line Tools
- PHP 8.4 or 8.5 built with the embed SAPI (`libphp.dylib`) - PHP 8.4 or 8.5 CLI for running `tpc.php`
- a matching PHPX build - the compiler Composer dependencies, including php-nano and PHPX sources
- `PHPX_HOME` pointing to the PHPX source tree - `PHPX_HOME` pointing to the PHPX source tree
Build from the TypePHP repository root: Build from the TypePHP repository root:
```sh ```sh
export PHPX_HOME=/path/to/phpx 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: Run the native executable:
```sh ```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: To create a standard `.app` bundle that can be launched from Finder:
```sh ```sh
sh examples/objective-c-macos/package-app.sh sh examples/apple-native/package-app.sh
open "examples/objective-c-macos/dist/TypePHP macOS Hello.app" open "examples/apple-native/dist/TypePHP macOS Hello.app"
``` ```
## iPhoneOS (physical iPhone) ## 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 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 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 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) ## 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 simulator needs archives compiled for `arm64-apple-ios-simulator`.
The physical iPhone SDK above cannot be reused. Build the separate runtime The physical iPhone SDK above cannot be reused. Build the separate runtime
and PHPX SDK on the Mac: and PHPX SDK on the Mac:

@ -20,13 +20,26 @@ case "${1:-}" in
package_script=package-ios-simulator-app.sh 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 exit 2
;; ;;
esac esac
cd "$repo_dir" cd "$repo_dir"
php bin/tpc.php "examples/apple-native/$config" --no-progress 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 ] && if [ "$package_script" = package-ios-app.sh ] &&
{ [ -z "${TYPEPHP_IOS_PROVISIONING_PROFILE:-}" ] || [ -z "${TYPEPHP_IOS_CODE_SIGN_IDENTITY:-}" ]; }; then { [ -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 public function testAndroidResolvesSelfContainedSdkAndSystemLibraries(): void
{ {
$phpxDir = $this->temporaryDirectory('phpx-android-host'); $phpxDir = $this->temporaryDirectory('phpx-android-host');
@ -283,6 +294,11 @@ final class NativeBuildConfigurationTest extends TestCase
return $this->getCommonCompileCommandOptions()->toArray(); return $this->getCommonCompileCommandOptions()->toArray();
} }
public function getLinkCommandOptionsForTest(): array
{
return $this->getLinkCommandOptions()->toArray();
}
public function enableNanoForTest(): void public function enableNanoForTest(): void
{ {
$this->nanoMode = true; $this->nanoMode = true;

@ -178,6 +178,30 @@ final class CompilationStatisticsTest extends BaseTest
self::assertStringNotContainsString('date_module_entry', $registry); 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 public function testNanoComposerAlwaysIncludesSharedStandardSources(): void
{ {
$statistics = new CompilationStatistics(); $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 public function testExtensionWithoutMetadataKeepsNullInfoAndVersion(): void
{ {
global $translator; global $translator;

@ -187,7 +187,9 @@ trait NativeCommandOptionsTrait
$ldflags = trim('-static -B ' . escapeshellarg($this->getFullStaticMuslDir()) . ' ' . $ldflags); $ldflags = trim('-static -B ' . escapeshellarg($this->getFullStaticMuslDir()) . ' ' . $ldflags);
} }
if ($this->isNanoMode()) { 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); $ldflags = trim($gcSections . ' ' . $ldflags);
if ($this->isWasiTarget()) { if ($this->isWasiTarget()) {
$ldflags = trim( $ldflags = trim(

@ -1962,7 +1962,10 @@ CODE;
$moduleVersion = $this->extensionVersion === '' $moduleVersion = $this->extensionVersion === ''
? 'nullptr' ? 'nullptr'
: $this->genCharPtr($this->extensionVersion, true); : $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 . ')'; $moduleInfoFunction = 'PHP_MINFO(' . $moduleName . ')';
$code .= PHP_EOL . 'PHP_MINFO_FUNCTION(' . $moduleName . ') {' . PHP_EOL; $code .= PHP_EOL . 'PHP_MINFO_FUNCTION(' . $moduleName . ') {' . PHP_EOL;
$code .= ' php_info_print_table_start();' . PHP_EOL; $code .= ' php_info_print_table_start();' . PHP_EOL;

Loading…
Cancel
Save