diff --git a/examples/apple-native/README.md b/examples/apple-native/README.md index 4fe8198f..1fdf7b8a 100644 --- a/examples/apple-native/README.md +++ b/examples/apple-native/README.md @@ -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: diff --git a/examples/apple-native/build-ios.sh b/examples/apple-native/build-ios.sh index 9048aa56..5563f43e 100755 --- a/examples/apple-native/build-ios.sh +++ b/examples/apple-native/build-ios.sh @@ -20,13 +20,26 @@ case "${1:-}" in package_script=package-ios-simulator-app.sh ;; *) - echo "Usage: $0 " >&2 + echo "Usage: $0 [--nano]" >&2 + exit 2 + ;; +esac + +case "${2:-}" in + "") nano_arg= ;; + --nano) nano_arg=--nano ;; + *) + echo "Usage: $0 [--nano]" >&2 exit 2 ;; esac 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 ] && { [ -z "${TYPEPHP_IOS_PROVISIONING_PROFILE:-}" ] || [ -z "${TYPEPHP_IOS_CODE_SIGN_IDENTITY:-}" ]; }; then diff --git a/phpunit/src/Build/NativeBuildConfigurationTest.php b/phpunit/src/Build/NativeBuildConfigurationTest.php index e2f0d5ca..8fb3d3c1 100644 --- a/phpunit/src/Build/NativeBuildConfigurationTest.php +++ b/phpunit/src/Build/NativeBuildConfigurationTest.php @@ -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; diff --git a/phpunit/src/CompilationStatisticsTest.php b/phpunit/src/CompilationStatisticsTest.php index ba6b4982..0ecd3c97 100644 --- a/phpunit/src/CompilationStatisticsTest.php +++ b/phpunit/src/CompilationStatisticsTest.php @@ -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(); diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 32049290..7c1befad 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -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; diff --git a/src/Build/NativeCommandOptionsTrait.php b/src/Build/NativeCommandOptionsTrait.php index 15373b80..103d45b5 100644 --- a/src/Build/NativeCommandOptionsTrait.php +++ b/src/Build/NativeCommandOptionsTrait.php @@ -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( diff --git a/src/Translator.php b/src/Translator.php index 0487aa80..497bcc28 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -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;