diff --git a/package-lock.json b/package-lock.json index ff22429221..a35899115b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "react-redux": "8.1.3", "react-transition-group": "4.4.5", "sha.js": "2.4.11", + "tmp-promise": "3.0.3", "wasm-feature-detect": "1.8.0", "xml2js": "0.6.2", "yargs": "17.7.2" @@ -48506,12 +48507,20 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "dev": true, "license": "MIT", "engines": { "node": ">=14.14" } }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", diff --git a/package.json b/package.json index a5a2a117a6..5b5d74c171 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "react-redux": "8.1.3", "react-transition-group": "4.4.5", "sha.js": "2.4.11", + "tmp-promise": "3.0.3", "wasm-feature-detect": "1.8.0", "xml2js": "0.6.2", "yargs": "17.7.2" diff --git a/packages/php-wasm/compile/php/php_wasm.c b/packages/php-wasm/compile/php/php_wasm.c index 3e61b2332a..5a977fa779 100644 --- a/packages/php-wasm/compile/php/php_wasm.c +++ b/packages/php-wasm/compile/php/php_wasm.c @@ -241,7 +241,7 @@ EM_JS(int, wasm_poll_socket, (php_socket_t socketd, int events, int timeout), { if (stream.stream_ops?.poll) { mask = stream.stream_ops.poll(stream, -1); } - + mask &= events | POLLERR | POLLHUP; if (mask) { return mask; @@ -418,7 +418,7 @@ EM_JS(__wasi_errno_t, js_fd_read, (__wasi_fd_t fd, const __wasi_iovec_t *iov, si HEAPU32[pnum >> 2] = num; return wakeUp(returnCode); } - + // It's a blocking stream and we Blocking stream with no data available yet. // Let's poll up to a timeout. await new Promise(resolve => setTimeout(resolve, interval)); @@ -1026,8 +1026,8 @@ int main(int argc, char *argv[]); int run_cli() { // See wasm_sapi_request_init() for details on why we need to redirect stdout and stderr. - stdout_replacement = redirect_stream_to_file(stdout, "/internal/stdout"); - stderr_replacement = redirect_stream_to_file(stderr, "/internal/stderr"); + stdout_replacement = redirect_stream_to_file(stdout, "/request/stdout"); + stderr_replacement = redirect_stream_to_file(stderr, "/request/stderr"); if (stdout_replacement == -1 || stderr_replacement == -1) { return -1; @@ -1337,7 +1337,7 @@ void wasm_set_request_port(int port) * * stream: The stream to redirect, e.g. stdout or stderr. * - * path: The path to the file to redirect to, e.g. "/internal/stdout". + * path: The path to the file to redirect to, e.g. "/request/stdout". * * returns: The exit code: 0 on success, -1 on failure. */ @@ -1559,11 +1559,11 @@ int wasm_sapi_request_init() // Write to files instead of stdout and stderr because Emscripten truncates null // bytes from stdout and stderr, and null bytes are a valid output when streaming // binary data. - // We use our custom Emscripten-defined /internal/std* devices and handle the output in JavaScript. + // We use our custom Emscripten-defined /request/std* devices and handle the output in JavaScript. // These /internal devices are not thread-safe and should always stay in per-process MEMFS space. // Sharing them between PHP instances may cause intertwined output. - stdout_replacement = redirect_stream_to_file(stdout, "/internal/stdout"); - stderr_replacement = redirect_stream_to_file(stderr, "/internal/stderr"); + stdout_replacement = redirect_stream_to_file(stdout, "/request/stdout"); + stderr_replacement = redirect_stream_to_file(stderr, "/request/stderr"); if (stdout_replacement == -1 || stderr_replacement == -1) { return -1; @@ -1821,7 +1821,7 @@ FILE *headers_file; */ static int wasm_sapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) { - headers_file = fopen("/internal/headers", "w"); + headers_file = fopen("/request/headers", "w"); if (headers_file == NULL) { return FAILURE; diff --git a/packages/php-wasm/compile/php/phpwasm-emscripten-library.js b/packages/php-wasm/compile/php/phpwasm-emscripten-library.js index 2e0af59349..7b35ab3e9f 100644 --- a/packages/php-wasm/compile/php/phpwasm-emscripten-library.js +++ b/packages/php-wasm/compile/php/phpwasm-emscripten-library.js @@ -10,7 +10,7 @@ const LibraryExample = { // Emscripten dependencies: $PHPWASM__deps: ['$allocateUTF8OnStack'], - $PHPWASM__postset: 'PHPWASM.init();', + $PHPWASM__postset: 'PHPWASM.init(PHPLoader?.phpWasmInitOptions);', // Functions not exposed to C but available in the generated // JavaScript library under the PHPWASM object: @@ -28,7 +28,7 @@ const LibraryExample = { // emscripten_O_NDELAY | // emscripten_O_DIRECT | // emscripten_O_NOATIME - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -38,28 +38,48 @@ const LibraryExample = { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true }, ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -77,7 +97,7 @@ const LibraryExample = { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -89,7 +109,7 @@ const LibraryExample = { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -101,7 +121,7 @@ const LibraryExample = { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE diff --git a/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm b/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm index a6621b540f..97528f598c 100755 Binary files a/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm and b/packages/php-wasm/node/asyncify/7_2_34/php_7_2.wasm differ diff --git a/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm b/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm index 936bf1f874..53fea62279 100755 Binary files a/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm and b/packages/php-wasm/node/asyncify/7_3_33/php_7_3.wasm differ diff --git a/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm b/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm index 69330d549e..d9baff388e 100755 Binary files a/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm and b/packages/php-wasm/node/asyncify/7_4_33/php_7_4.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm b/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm index a189a47dec..6a11c2d334 100755 Binary files a/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm and b/packages/php-wasm/node/asyncify/8_0_30/php_8_0.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_1_33/php_8_1.wasm b/packages/php-wasm/node/asyncify/8_1_33/php_8_1.wasm index a3b649d05e..4548d77d65 100755 Binary files a/packages/php-wasm/node/asyncify/8_1_33/php_8_1.wasm and b/packages/php-wasm/node/asyncify/8_1_33/php_8_1.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_2_29/php_8_2.wasm b/packages/php-wasm/node/asyncify/8_2_29/php_8_2.wasm index b74fd68e7c..bcc859cb42 100755 Binary files a/packages/php-wasm/node/asyncify/8_2_29/php_8_2.wasm and b/packages/php-wasm/node/asyncify/8_2_29/php_8_2.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_3_24/php_8_3.wasm b/packages/php-wasm/node/asyncify/8_3_24/php_8_3.wasm index 90f8044bca..641a5eae71 100755 Binary files a/packages/php-wasm/node/asyncify/8_3_24/php_8_3.wasm and b/packages/php-wasm/node/asyncify/8_3_24/php_8_3.wasm differ diff --git a/packages/php-wasm/node/asyncify/8_4_11/php_8_4.wasm b/packages/php-wasm/node/asyncify/8_4_11/php_8_4.wasm index ff34fd9ad4..7e1f53712c 100755 Binary files a/packages/php-wasm/node/asyncify/8_4_11/php_8_4.wasm and b/packages/php-wasm/node/asyncify/8_4_11/php_8_4.wasm differ diff --git a/packages/php-wasm/node/asyncify/php_7_2.js b/packages/php-wasm/node/asyncify/php_7_2.js index 7ab1d4768d..dbf13ce8fc 100644 --- a/packages/php-wasm/node/asyncify/php_7_2.js +++ b/packages/php-wasm/node/asyncify/php_7_2.js @@ -6691,7 +6691,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6701,28 +6701,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6740,7 +6760,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6752,7 +6772,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6764,7 +6784,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -32034,7 +32054,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/asyncify/php_7_3.js b/packages/php-wasm/node/asyncify/php_7_3.js index e322b6618f..0b22e28a0b 100644 --- a/packages/php-wasm/node/asyncify/php_7_3.js +++ b/packages/php-wasm/node/asyncify/php_7_3.js @@ -6691,7 +6691,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6701,28 +6701,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6740,7 +6760,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6752,7 +6772,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6764,7 +6784,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -32034,7 +32054,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/asyncify/php_7_4.js b/packages/php-wasm/node/asyncify/php_7_4.js index ed3471ee5f..a73453c545 100644 --- a/packages/php-wasm/node/asyncify/php_7_4.js +++ b/packages/php-wasm/node/asyncify/php_7_4.js @@ -6691,7 +6691,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6701,28 +6701,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6740,7 +6760,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6752,7 +6772,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6764,7 +6784,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -32034,7 +32054,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/asyncify/php_8_0.js b/packages/php-wasm/node/asyncify/php_8_0.js index d6ac9e0aba..8d6d2c1df4 100644 --- a/packages/php-wasm/node/asyncify/php_8_0.js +++ b/packages/php-wasm/node/asyncify/php_8_0.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '8_0_30', 'php_8_0.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 31318489; +export const dependenciesTotalSize = 31318473; const phpVersionString = '8.0.30'; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js @@ -6691,7 +6691,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6701,28 +6701,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6740,7 +6760,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6752,7 +6772,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6764,7 +6784,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -32034,7 +32054,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; @@ -32100,13 +32120,13 @@ export function init(RuntimeName, PHPLoader) { // End JS library code var ASM_CONSTS = { - 12160717: ($0) => { + 12160701: ($0) => { if (!$0) { AL.alcErr = 0xa004; return 1; } }, - 12160765: ($0) => { + 12160749: ($0) => { if (!AL.currentCtx) { err('alGetProcAddress() called without a valid context'); return 1; diff --git a/packages/php-wasm/node/asyncify/php_8_1.js b/packages/php-wasm/node/asyncify/php_8_1.js index 7da97dc366..c4fb07e92e 100644 --- a/packages/php-wasm/node/asyncify/php_8_1.js +++ b/packages/php-wasm/node/asyncify/php_8_1.js @@ -6691,7 +6691,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6701,28 +6701,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6740,7 +6760,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6752,7 +6772,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6764,7 +6784,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -32034,7 +32054,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/asyncify/php_8_2.js b/packages/php-wasm/node/asyncify/php_8_2.js index 0176e3bb9e..2d7ddb2d61 100644 --- a/packages/php-wasm/node/asyncify/php_8_2.js +++ b/packages/php-wasm/node/asyncify/php_8_2.js @@ -6691,7 +6691,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6701,28 +6701,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6740,7 +6760,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6752,7 +6772,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6764,7 +6784,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -32034,7 +32054,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/asyncify/php_8_3.js b/packages/php-wasm/node/asyncify/php_8_3.js index 7e049dbea0..5619174861 100644 --- a/packages/php-wasm/node/asyncify/php_8_3.js +++ b/packages/php-wasm/node/asyncify/php_8_3.js @@ -6691,7 +6691,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6701,28 +6701,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6740,7 +6760,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6752,7 +6772,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6764,7 +6784,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -32034,7 +32054,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/asyncify/php_8_4.js b/packages/php-wasm/node/asyncify/php_8_4.js index 4c8d7f5e91..2e66c42189 100644 --- a/packages/php-wasm/node/asyncify/php_8_4.js +++ b/packages/php-wasm/node/asyncify/php_8_4.js @@ -6691,7 +6691,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6701,28 +6701,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6740,7 +6760,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6752,7 +6772,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6764,7 +6784,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -32034,7 +32054,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/jspi/7_2_34/php_7_2.wasm b/packages/php-wasm/node/jspi/7_2_34/php_7_2.wasm index f84794999a..2265db5871 100755 Binary files a/packages/php-wasm/node/jspi/7_2_34/php_7_2.wasm and b/packages/php-wasm/node/jspi/7_2_34/php_7_2.wasm differ diff --git a/packages/php-wasm/node/jspi/7_3_33/php_7_3.wasm b/packages/php-wasm/node/jspi/7_3_33/php_7_3.wasm index e694c3f8d1..b539393905 100755 Binary files a/packages/php-wasm/node/jspi/7_3_33/php_7_3.wasm and b/packages/php-wasm/node/jspi/7_3_33/php_7_3.wasm differ diff --git a/packages/php-wasm/node/jspi/7_4_33/php_7_4.wasm b/packages/php-wasm/node/jspi/7_4_33/php_7_4.wasm index 2c13d847d3..4d2a36b0c0 100755 Binary files a/packages/php-wasm/node/jspi/7_4_33/php_7_4.wasm and b/packages/php-wasm/node/jspi/7_4_33/php_7_4.wasm differ diff --git a/packages/php-wasm/node/jspi/8_0_30/php_8_0.wasm b/packages/php-wasm/node/jspi/8_0_30/php_8_0.wasm index e35c86e5e7..2131b2bf18 100755 Binary files a/packages/php-wasm/node/jspi/8_0_30/php_8_0.wasm and b/packages/php-wasm/node/jspi/8_0_30/php_8_0.wasm differ diff --git a/packages/php-wasm/node/jspi/8_1_33/php_8_1.wasm b/packages/php-wasm/node/jspi/8_1_33/php_8_1.wasm index 3b1f335cca..6c0280d31f 100755 Binary files a/packages/php-wasm/node/jspi/8_1_33/php_8_1.wasm and b/packages/php-wasm/node/jspi/8_1_33/php_8_1.wasm differ diff --git a/packages/php-wasm/node/jspi/8_2_29/php_8_2.wasm b/packages/php-wasm/node/jspi/8_2_29/php_8_2.wasm index 91334a557a..956868f08c 100755 Binary files a/packages/php-wasm/node/jspi/8_2_29/php_8_2.wasm and b/packages/php-wasm/node/jspi/8_2_29/php_8_2.wasm differ diff --git a/packages/php-wasm/node/jspi/8_3_24/php_8_3.wasm b/packages/php-wasm/node/jspi/8_3_24/php_8_3.wasm index 76fd277e7d..e3ce8abde3 100755 Binary files a/packages/php-wasm/node/jspi/8_3_24/php_8_3.wasm and b/packages/php-wasm/node/jspi/8_3_24/php_8_3.wasm differ diff --git a/packages/php-wasm/node/jspi/8_4_11/php_8_4.wasm b/packages/php-wasm/node/jspi/8_4_11/php_8_4.wasm index 99e4cfe8ac..95f380abe6 100755 Binary files a/packages/php-wasm/node/jspi/8_4_11/php_8_4.wasm and b/packages/php-wasm/node/jspi/8_4_11/php_8_4.wasm differ diff --git a/packages/php-wasm/node/jspi/php_7_2.js b/packages/php-wasm/node/jspi/php_7_2.js index ad4631cb37..98eaef033d 100644 --- a/packages/php-wasm/node/jspi/php_7_2.js +++ b/packages/php-wasm/node/jspi/php_7_2.js @@ -6578,7 +6578,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6588,28 +6588,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6627,7 +6647,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6639,7 +6659,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6651,7 +6671,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -31196,7 +31216,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/jspi/php_7_3.js b/packages/php-wasm/node/jspi/php_7_3.js index df78f6fc6f..82f87193f5 100644 --- a/packages/php-wasm/node/jspi/php_7_3.js +++ b/packages/php-wasm/node/jspi/php_7_3.js @@ -6578,7 +6578,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6588,28 +6588,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6627,7 +6647,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6639,7 +6659,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6651,7 +6671,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -31196,7 +31216,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/jspi/php_7_4.js b/packages/php-wasm/node/jspi/php_7_4.js index 0a2fcc493e..e3cb6607e7 100644 --- a/packages/php-wasm/node/jspi/php_7_4.js +++ b/packages/php-wasm/node/jspi/php_7_4.js @@ -6578,7 +6578,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6588,28 +6588,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6627,7 +6647,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6639,7 +6659,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6651,7 +6671,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -31196,7 +31216,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/jspi/php_8_0.js b/packages/php-wasm/node/jspi/php_8_0.js index 237786fa88..bb0e29c7f8 100644 --- a/packages/php-wasm/node/jspi/php_8_0.js +++ b/packages/php-wasm/node/jspi/php_8_0.js @@ -6578,7 +6578,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6588,28 +6588,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6627,7 +6647,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6639,7 +6659,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6651,7 +6671,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -31196,7 +31216,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/jspi/php_8_1.js b/packages/php-wasm/node/jspi/php_8_1.js index 9c63b3f75f..1b2c3e5a8a 100644 --- a/packages/php-wasm/node/jspi/php_8_1.js +++ b/packages/php-wasm/node/jspi/php_8_1.js @@ -6578,7 +6578,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6588,28 +6588,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6627,7 +6647,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6639,7 +6659,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6651,7 +6671,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -31196,7 +31216,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/jspi/php_8_2.js b/packages/php-wasm/node/jspi/php_8_2.js index ba5e121b61..7941dacf00 100644 --- a/packages/php-wasm/node/jspi/php_8_2.js +++ b/packages/php-wasm/node/jspi/php_8_2.js @@ -6578,7 +6578,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6588,28 +6588,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6627,7 +6647,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6639,7 +6659,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6651,7 +6671,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -31196,7 +31216,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/jspi/php_8_3.js b/packages/php-wasm/node/jspi/php_8_3.js index ac1ac8bc9a..4d4482fc69 100644 --- a/packages/php-wasm/node/jspi/php_8_3.js +++ b/packages/php-wasm/node/jspi/php_8_3.js @@ -6578,7 +6578,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6588,28 +6588,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6627,7 +6647,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6639,7 +6659,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6651,7 +6671,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -31196,7 +31216,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; diff --git a/packages/php-wasm/node/jspi/php_8_4.js b/packages/php-wasm/node/jspi/php_8_4.js index 6eeaed481b..c0ce251233 100644 --- a/packages/php-wasm/node/jspi/php_8_4.js +++ b/packages/php-wasm/node/jspi/php_8_4.js @@ -8,7 +8,7 @@ import path from 'path'; const dependencyFilename = path.join(__dirname, '8_4_11', 'php_8_4.wasm'); export { dependencyFilename }; -export const dependenciesTotalSize = 36373389; +export const dependenciesTotalSize = 36373325; const phpVersionString = '8.4.11'; export function init(RuntimeName, PHPLoader) { // The rest of the code comes from the built php.js file and esm-suffix.js @@ -848,7 +848,7 @@ export function init(RuntimeName, PHPLoader) { }, }; - var ___heap_base = 18314400; + var ___heap_base = 18314336; var alignMemory = (size, alignment) => { return Math.ceil(size / alignment) * alignment; @@ -1743,13 +1743,13 @@ export function init(RuntimeName, PHPLoader) { 1024 ); - var ___stack_high = 18314400; + var ___stack_high = 18314336; - var ___stack_low = 17265824; + var ___stack_low = 17265760; var ___stack_pointer = new WebAssembly.Global( { value: 'i32', mutable: true }, - 18314400 + 18314336 ); var PATH = { @@ -6578,7 +6578,7 @@ export function init(RuntimeName, PHPLoader) { O_NONBLOCK: 2048, POLLHUP: 16, SETFL_MASK: 3072, - init: function () { + init: function (phpWasmInitOptions) { Module['ENV'] = Module['ENV'] || {}; // Ensure a platform-level bin directory for a fallback `php` binary. Module['ENV']['PATH'] = [ @@ -6588,28 +6588,48 @@ export function init(RuntimeName, PHPLoader) { .filter(Boolean) .join(':'); - // The /internal directory is required by the C module. It's where the + // The /request directory is required by the C module. It's where the // stdout, stderr, and headers information are written for the JavaScript - // code to read later on. + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains wp-config.php, constants, etc. FS.mkdir('/internal'); + + if (phpWasmInitOptions?.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: phpWasmInitOptions.nativeInternalDirPath }, + '/internal' + ); + } + // The files from the shared directory are shared between all the // PHP processes managed by PHPProcessManager. - FS.mkdir('/internal/shared'); + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the // auto_prepend_file php.ini directive. - FS.mkdir('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/preload'); // Platform-level bin directory for a fallback `php` binary. Without it, // PHP may not populate the PHP_BINARY constant. - FS.mkdir('/internal/shared/bin'); + FS.mkdirTree('/internal/shared/bin'); const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; Module['onRuntimeInitialized'] = () => { - // Dummy PHP binary for PHP to populate the PHP_BINARY constant. - FS.writeFile( + const { node: phpBinaryNode } = FS.lookupPath( '/internal/shared/bin/php', - new TextEncoder().encode('#!/bin/sh\nphp "$@"') + { noent_okay: true } ); - // It must be executable to be used by PHP. - FS.chmod('/internal/shared/bin/php', 0o755); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 0o755); + } originalOnRuntimeInitialized(); }; @@ -6627,7 +6647,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stdout', FS.makedev(64, 0)); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); FS.registerDevice(FS.makedev(63, 0), { open: () => {}, @@ -6639,7 +6659,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/stderr', FS.makedev(63, 0)); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); FS.registerDevice(FS.makedev(62, 0), { open: () => {}, @@ -6651,7 +6671,7 @@ export function init(RuntimeName, PHPLoader) { return length; }, }); - FS.mkdev('/internal/headers', FS.makedev(62, 0)); + FS.mkdev('/request/headers', FS.makedev(62, 0)); // Handle events. PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE @@ -31196,7 +31216,7 @@ export function init(RuntimeName, PHPLoader) { if (ENVIRONMENT_IS_NODE) { NODEFS.staticInit(); } - PHPWASM.init(); + PHPWASM.init(PHPLoader?.phpWasmInitOptions); Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; Module['pauseMainLoop'] = MainLoop.pause; @@ -31262,13 +31282,13 @@ export function init(RuntimeName, PHPLoader) { // End JS library code var ASM_CONSTS = { - 16328046: ($0) => { + 16327982: ($0) => { if (!$0) { AL.alcErr = 0xa004; return 1; } }, - 16328094: ($0) => { + 16328030: ($0) => { if (!AL.currentCtx) { err('alGetProcAddress() called without a valid context'); return 1; diff --git a/packages/php-wasm/node/src/lib/load-runtime.ts b/packages/php-wasm/node/src/lib/load-runtime.ts index ca911145ef..6c1455b399 100644 --- a/packages/php-wasm/node/src/lib/load-runtime.ts +++ b/packages/php-wasm/node/src/lib/load-runtime.ts @@ -56,6 +56,18 @@ type PHPLoaderOptionsForNode = PHPLoaderOptions & { * @param args - Arguments to the format string. */ trace?: (processId: number, format: string, ...args: any[]) => void; + + /** + * An optional object to pass to the PHP-WASM library's `init` function. + * + * phpWasmInitOptions.nativeInternalDirPath is used to mount a + * real, native directory as the php-wasm /internal directory. + * + * @see https://github.com/php-wasm/php-wasm/blob/main/compile/php/phpwasm-emscripten-library.js#L100 + */ + phpWasmInitOptions?: { + nativeInternalDirPath?: string; + }; }; }; diff --git a/packages/php-wasm/supported-php-versions.mjs b/packages/php-wasm/supported-php-versions.mjs index adf7d9b0cb..5d65755313 100644 --- a/packages/php-wasm/supported-php-versions.mjs +++ b/packages/php-wasm/supported-php-versions.mjs @@ -6,7 +6,7 @@ * @property {string} lastRelease */ -export const lastRefreshed = '2025-08-04T12:45:56.385Z'; +export const lastRefreshed = '2025-08-11T19:42:22.755Z'; /** * @type {PhpVersion[]} diff --git a/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts b/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts index ae4409503a..8acc457d8b 100644 --- a/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts +++ b/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts @@ -67,7 +67,8 @@ export class BlueprintsV1Handler { async bootPrimaryWorker( phpPort: NodeMessagePort, - fileLockManagerPort: NodeMessagePort + fileLockManagerPort: NodeMessagePort, + nativeInternalDirPath: string ) { const compiledBlueprint = await this.compileInputBlueprint( this.args['additional-blueprint-steps'] || [] @@ -147,9 +148,9 @@ export class BlueprintsV1Handler { await playground.useFileLockManager(fileLockManagerPort); await playground.bootAsPrimaryWorker({ - phpVersion: this.phpVersion, + php: this.phpVersion, wpVersion: compiledBlueprint.versions.wp, - absoluteUrl: this.siteUrl, + siteUrl: this.siteUrl, mountsBeforeWpInstall, mountsAfterWpInstall, wordPressZip: wordPressZip && (await wordPressZip!.arrayBuffer()), @@ -161,6 +162,7 @@ export class BlueprintsV1Handler { trace, internalCookieStore: this.args.internalCookieStore, withXdebug: this.args.xdebug, + nativeInternalDirPath, }); if ( @@ -183,10 +185,12 @@ export class BlueprintsV1Handler { worker, fileLockManagerPort, firstProcessId, + nativeInternalDirPath, }: { worker: SpawnedWorker; fileLockManagerPort: NodeMessagePort; firstProcessId: number; + nativeInternalDirPath: string; }) { const additionalPlayground = consumeAPI( worker.phpPort @@ -195,17 +199,10 @@ export class BlueprintsV1Handler { await additionalPlayground.isConnected(); await additionalPlayground.useFileLockManager(fileLockManagerPort); await additionalPlayground.bootAsSecondaryWorker({ - phpVersion: this.phpVersion, - absoluteUrl: this.siteUrl, + php: this.phpVersion!, + siteUrl: this.siteUrl, mountsBeforeWpInstall: this.args['mount-before-install'] || [], mountsAfterWpInstall: this.args['mount'] || [], - // Skip WordPress zip because we share the /wordpress directory - // populated by the initial worker. - wordPressZip: undefined, - // Skip SQLite integration plugin for now because we - // will copy it from primary's `/internal` directory. - sqliteIntegrationPluginZip: undefined, - dataSqlPath: '/wordpress/wp-content/database/.ht.sqlite', firstProcessId, processIdSpaceLength: this.processIdSpaceLength, followSymlinks: this.args.followSymlinks === true, @@ -214,6 +211,7 @@ export class BlueprintsV1Handler { // will have a separate cookie store. internalCookieStore: this.args.internalCookieStore, withXdebug: this.args.xdebug, + nativeInternalDirPath, }); await additionalPlayground.isReady(); return additionalPlayground; diff --git a/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts b/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts index 9263ac1ef3..0eba6006d1 100644 --- a/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts +++ b/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts @@ -11,7 +11,7 @@ import { } from '@php-wasm/universal'; import { sprintf } from '@php-wasm/util'; import { RecommendedPHPVersion } from '@wp-playground/common'; -import { bootWordPress } from '@wp-playground/wordpress'; +import { bootWordPress, bootRequestHandler } from '@wp-playground/wordpress'; import { rootCertificates } from 'tls'; import { jspi } from 'wasm-feature-detect'; import { MessageChannel, type MessagePort, parentPort } from 'worker_threads'; @@ -23,16 +23,12 @@ export interface Mount { } export type WorkerBootOptions = { - wpVersion?: string; - phpVersion?: SupportedPHPVersion; - absoluteUrl: string; + php: SupportedPHPVersion; + siteUrl: string; mountsBeforeWpInstall: Array; mountsAfterWpInstall: Array; - wordPressZip?: ArrayBuffer; - sqliteIntegrationPluginZip?: ArrayBuffer; firstProcessId: number; processIdSpaceLength: number; - dataSqlPath?: string; followSymlinks: boolean; trace: boolean; /** @@ -44,8 +40,26 @@ export type WorkerBootOptions = { */ internalCookieStore?: boolean; withXdebug?: boolean; + nativeInternalDirPath: string; }; +export type PrimaryWorkerBootOptions = WorkerBootOptions & { + wpVersion?: string; + wordPressZip?: ArrayBuffer; + sqliteIntegrationPluginZip?: ArrayBuffer; + dataSqlPath?: string; +}; + +interface WorkerBootRequestHandlerOptions { + siteUrl: string; + allow?: string; + php: SupportedPHPVersion; + firstProcessId: number; + processIdSpaceLength: number; + trace: boolean; + nativeInternalDirPath: string; +} + /** * Print trace messages from PHP-WASM. * @@ -104,10 +118,10 @@ export class PlaygroundCliBlueprintV1Worker extends PHPWorker { } async bootAsPrimaryWorker({ - absoluteUrl, + siteUrl, mountsBeforeWpInstall, mountsAfterWpInstall, - phpVersion = RecommendedPHPVersion, + php = RecommendedPHPVersion, wordPressZip, sqliteIntegrationPluginZip, firstProcessId, @@ -117,7 +131,8 @@ export class PlaygroundCliBlueprintV1Worker extends PHPWorker { trace, internalCookieStore, withXdebug, - }: WorkerBootOptions) { + nativeInternalDirPath, + }: PrimaryWorkerBootOptions) { if (this.booted) { throw new Error('Playground already booted'); } @@ -135,8 +150,8 @@ export class PlaygroundCliBlueprintV1Worker extends PHPWorker { }; const requestHandler = await bootWordPress({ - siteUrl: absoluteUrl, - createPhpRuntime: async () => { + siteUrl, + createPhpRuntime: async (isPrimary) => { const processId = nextProcessId; if (nextProcessId < lastProcessId) { @@ -146,11 +161,18 @@ export class PlaygroundCliBlueprintV1Worker extends PHPWorker { nextProcessId = firstProcessId; } - return await loadNodeRuntime(phpVersion, { + return await loadNodeRuntime(php, { emscriptenOptions: { fileLockManager: this.fileLockManager!, processId, trace: trace ? tracePhpWasm : undefined, + phpWasmInitOptions: isPrimary + ? // Only pass a native /internal dir to the primary PHP process + // because the secondary PHP process will proxy to it. + { + nativeInternalDirPath, + } + : {}, }, followSymlinks, withXdebug, @@ -202,7 +224,72 @@ export class PlaygroundCliBlueprintV1Worker extends PHPWorker { } async bootAsSecondaryWorker(args: WorkerBootOptions) { - return this.bootAsPrimaryWorker(args); + await this.bootRequestHandler(args); + const primaryPhp = this.__internal_getPHP()!; + // When secondary workers are spawned, WordPress is already installed. + await mountResources(primaryPhp, args.mountsBeforeWpInstall || []); + await mountResources(primaryPhp, args.mountsAfterWpInstall || []); + } + + async bootRequestHandler({ + siteUrl, + allow, + php, + firstProcessId, + processIdSpaceLength, + trace, + nativeInternalDirPath, + }: WorkerBootRequestHandlerOptions) { + if (this.booted) { + throw new Error('Playground already booted'); + } + this.booted = true; + + let nextProcessId = firstProcessId; + const lastProcessId = firstProcessId + processIdSpaceLength - 1; + + try { + const requestHandler = await bootRequestHandler({ + siteUrl, + createPhpRuntime: async () => { + const processId = nextProcessId; + + if (nextProcessId < lastProcessId) { + nextProcessId++; + } else { + // We've reached the end of the process ID space. Start over. + nextProcessId = firstProcessId; + } + + return await loadNodeRuntime(php!, { + emscriptenOptions: { + fileLockManager: this.fileLockManager!, + processId, + trace: trace ? tracePhpWasm : undefined, + ENV: { + DOCROOT: '/wordpress', + }, + phpWasmInitOptions: { + nativeInternalDirPath, + }, + }, + followSymlinks: allow?.includes('follow-symlinks'), + }); + }, + sapiName: 'cli', + cookieStore: false, + spawnHandler: sandboxedSpawnHandlerFactory, + }); + this.__internal_setRequestHandler(requestHandler); + + const primaryPhp = await requestHandler.getPrimaryPhp(); + await this.setPrimaryPHP(primaryPhp); + + setApiReady(); + } catch (e) { + setAPIError(e as Error); + throw e; + } } // Provide a named disposal method that can be invoked via comlink. diff --git a/packages/playground/cli/src/blueprints-v2/blueprints-v2-handler.ts b/packages/playground/cli/src/blueprints-v2/blueprints-v2-handler.ts index 332855bf6b..f21998bb64 100644 --- a/packages/playground/cli/src/blueprints-v2/blueprints-v2-handler.ts +++ b/packages/playground/cli/src/blueprints-v2/blueprints-v2-handler.ts @@ -55,7 +55,8 @@ export class BlueprintsV2Handler { async bootPrimaryWorker( phpPort: NodeMessagePort, - fileLockManagerPort: NodeMessagePort + fileLockManagerPort: NodeMessagePort, + nativeInternalDirPath: string ) { const playground: RemoteAPI = consumeAPI(phpPort); @@ -70,6 +71,7 @@ export class BlueprintsV2Handler { processIdSpaceLength: this.processIdSpaceLength, trace: this.args.debug || false, blueprint: this.args.blueprint!, + nativeInternalDirPath, }; await playground.bootAsPrimaryWorker(workerBootArgs); @@ -80,10 +82,12 @@ export class BlueprintsV2Handler { worker, fileLockManagerPort, firstProcessId, + nativeInternalDirPath, }: { worker: SpawnedWorker; fileLockManagerPort: NodeMessagePort; firstProcessId: number; + nativeInternalDirPath: string; }) { const playground: RemoteAPI = consumeAPI(worker.phpPort); @@ -98,6 +102,7 @@ export class BlueprintsV2Handler { processIdSpaceLength: this.processIdSpaceLength, trace: this.args.debug || false, blueprint: this.args.blueprint!, + nativeInternalDirPath, }; await playground.bootAsSecondaryWorker(workerBootArgs); diff --git a/packages/playground/cli/src/blueprints-v2/worker-thread-v2.ts b/packages/playground/cli/src/blueprints-v2/worker-thread-v2.ts index f1046f627b..093e657b9b 100644 --- a/packages/playground/cli/src/blueprints-v2/worker-thread-v2.ts +++ b/packages/playground/cli/src/blueprints-v2/worker-thread-v2.ts @@ -2,7 +2,12 @@ import { errorLogPath } from '@php-wasm/logger'; import type { FileLockManager } from '@php-wasm/node'; import { createNodeFsMountHandler, loadNodeRuntime } from '@php-wasm/node'; import { EmscriptenDownloadMonitor } from '@php-wasm/progress'; -import type { PHP, RemoteAPI, SupportedPHPVersion } from '@php-wasm/universal'; +import type { + FileTree, + PHP, + RemoteAPI, + SupportedPHPVersion, +} from '@php-wasm/universal'; import { PHPExecutionFailureError, PHPResponse, @@ -26,6 +31,7 @@ import { MessageChannel, type MessagePort, parentPort } from 'worker_threads'; import type { Mount } from '../mounts'; import { jspi } from 'wasm-feature-detect'; import { type RunCLIArgs } from '../run-cli'; +import type { PhpIniOptions } from '@wp-playground/wordpress'; async function mountResources(php: PHP, mounts: Mount[]) { for (const mount of mounts) { @@ -111,6 +117,7 @@ export type WorkerBootArgs = RunCLIArgs & { processIdSpaceLength: number; trace: boolean; blueprint: BlueprintV2Declaration | ParsedBlueprintV2Declaration; + nativeInternalDirPath: string; }; type WorkerRunBlueprintArgs = RunCLIArgs & { @@ -120,11 +127,16 @@ type WorkerRunBlueprintArgs = RunCLIArgs & { interface WorkerBootRequestHandlerOptions { siteUrl: string; - php: SupportedPHPVersion; allow?: string; + php: SupportedPHPVersion; + phpIniEntries?: PhpIniOptions; + constants?: Record; + createFiles?: FileTree; firstProcessId: number; processIdSpaceLength: number; trace: boolean; + nativeInternalDirPath: string; + withXdebug?: boolean; } export class PlaygroundCliBlueprintV2Worker extends PHPWorker { @@ -169,7 +181,22 @@ export class PlaygroundCliBlueprintV2Worker extends PHPWorker { } async bootAsPrimaryWorker(args: WorkerBootArgs) { - await this.bootRequestHandler(args); + const constants = { + WP_DEBUG: true, + WP_DEBUG_LOG: true, + WP_DEBUG_DISPLAY: false, + }; + const requestHandlerOptions = { + ...args, + createFiles: { + '/internal/shared/ca-bundle.crt': rootCertificates.join('\n'), + }, + constants, + phpIniEntries: { + 'openssl.cafile': '/internal/shared/ca-bundle.crt', + }, + }; + await this.bootRequestHandler(requestHandlerOptions); const primaryPhp = this.__internal_getPHP()!; await mountResources(primaryPhp, args['mount-before-install'] || []); @@ -339,9 +366,14 @@ export class PlaygroundCliBlueprintV2Worker extends PHPWorker { siteUrl, allow, php, + createFiles, + constants, + phpIniEntries, firstProcessId, processIdSpaceLength, trace, + nativeInternalDirPath, + withXdebug, }: WorkerBootRequestHandlerOptions) { if (this.booted) { throw new Error('Playground already booted'); @@ -352,16 +384,9 @@ export class PlaygroundCliBlueprintV2Worker extends PHPWorker { const lastProcessId = firstProcessId + processIdSpaceLength - 1; try { - const constants: Record = - { - WP_DEBUG: true, - WP_DEBUG_LOG: true, - WP_DEBUG_DISPLAY: false, - }; - const requestHandler = await bootRequestHandler({ siteUrl, - createPhpRuntime: async () => { + createPhpRuntime: async (isPrimary) => { const processId = nextProcessId; if (nextProcessId < lastProcessId) { @@ -379,19 +404,22 @@ export class PlaygroundCliBlueprintV2Worker extends PHPWorker { ENV: { DOCROOT: '/wordpress', }, + phpWasmInitOptions: isPrimary + ? // Only pass a native /internal dir to the primary PHP process + // because the secondary PHP process will proxy to it. + { + nativeInternalDirPath, + } + : {}, }, followSymlinks: allow?.includes('follow-symlinks'), + withXdebug, }); }, sapiName: 'cli', - createFiles: { - '/internal/shared/ca-bundle.crt': - rootCertificates.join('\n'), - }, + createFiles, constants, - phpIniEntries: { - 'openssl.cafile': '/internal/shared/ca-bundle.crt', - }, + phpIniEntries, cookieStore: false, spawnHandler: sandboxedSpawnHandlerFactory, }); diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index 0bec82a7e7..192f142c26 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -15,12 +15,8 @@ import type { BlueprintDeclaration, } from '@wp-playground/blueprints'; import { runBlueprintSteps } from '@wp-playground/blueprints'; -import { - RecommendedPHPVersion, - unzipFile, - zipDirectory, -} from '@wp-playground/common'; -import fs from 'fs'; +import { RecommendedPHPVersion } from '@wp-playground/common'; +import fs, { mkdirSync } from 'fs'; import type { Server } from 'http'; import { MessageChannel as NodeMessageChannel, Worker } from 'worker_threads'; // @ts-ignore @@ -48,6 +44,11 @@ import { resolveBlueprint } from './resolve-blueprint'; import { BlueprintsV2Handler } from './blueprints-v2/blueprints-v2-handler'; import { BlueprintsV1Handler } from './blueprints-v1/blueprints-v1-handler'; import { startBridge } from '@php-wasm/xdebug-bridge'; +import { + dir as tmpDir, + setGracefulCleanup as tmpSetGracefulCleanup, +} from 'tmp-promise'; +import path from 'path'; export async function parseOptionsAndRunCLI() { try { @@ -227,20 +228,6 @@ export async function parseOptionsAndRunCLI() { 'The --experimentalMultiWorker flag must be a positive integer greater than 1.' ); } - - const isMountingWordPressDir = (mount: Mount) => - mount.vfsPath === '/wordpress'; - if ( - !args.mount?.some(isMountingWordPressDir) && - !(args['mount-before-install'] as any)?.some( - isMountingWordPressDir - ) - ) { - throw new Error( - 'Please mount a real filesystem directory as the /wordpress directory before using the --experimentalMultiWorker flag. For example: ' + - '--mount-dir-before-install ./empty-dir /wordpress' - ); - } } return true; }); @@ -392,6 +379,51 @@ export async function runCLI(args: RunCLIArgs): Promise { Number.MAX_SAFE_INTEGER / totalWorkerCount ); + /* + * Use a real temp dir as a target for Playground /wordpress and /internal paths + * so that multiple worker threads can share the same files. + * Sharing the same files leads to faster boot times and uses less memory + * because we don't have to create or maintain multiple copies of the same files. + */ + // TODO: How can we name this to help cleanup when starting Playground CLI after a crash? + const nativeDirPath = ( + await tmpDir({ + /* + * Remove the temp dir and all contents on process exit. + * + * NOTE: I worried about whether this cleanup would follow symlinks + * and delete target files instead of unlinking the symlink, + * but this feature uses rimraf under the hood which respects symlinks: + * https://github.com/raszi/node-tmp/blob/3d2fe387f3f91b13830b9182faa02c3231ea8258/lib/tmp.js#L318 + */ + unsafeCleanup: true, + }) + ).path; + // Request graceful cleanup on process exit. + tmpSetGracefulCleanup(); + + const nativeInternalDirPath = path.join(nativeDirPath, 'internal'); + mkdirSync(nativeInternalDirPath); + + if (args['mount-before-install'] === undefined) { + args['mount-before-install'] = []; + } + if (!args['mount-before-install'].some(isMountingWordPressDir)) { + // The user isn't mounting a real /wordpress directory, + // so we can create a real one in the temp directory. + const nativeWordPressDirPath = path.join( + nativeDirPath, + 'wordpress' + ); + mkdirSync(nativeWordPressDirPath); + + // Make the real /wordpress mount first so any /wordpress subdirs are mounted into it. + args['mount-before-install'].unshift({ + vfsPath: '/wordpress', + hostPath: nativeWordPressDirPath, + }); + } + let handler: BlueprintsV1Handler | BlueprintsV2Handler; if (args['experimental-blueprints-v2-runner']) { handler = new BlueprintsV2Handler(args, { @@ -449,7 +481,8 @@ export async function runCLI(args: RunCLIArgs): Promise { // Boot the primary worker using the handler playground = await handler.bootPrimaryWorker( initialWorker.phpPort, - fileLockManagerPort + fileLockManagerPort, + nativeInternalDirPath ); playgroundsToCleanUp.push({ playground, @@ -491,13 +524,6 @@ export async function runCLI(args: RunCLIArgs): Promise { ) { logger.log(`Preparing additional workers...`); - // Save /internal directory from initial worker so we can replicate it - // in each additional worker. - const internalZip = await zipDirectory( - playground, - '/internal' - ); - // Boot additional workers using the handler const initialWorkerProcessIdSpace = processIdSpaceLength; await Promise.all( @@ -514,6 +540,7 @@ export async function runCLI(args: RunCLIArgs): Promise { worker, fileLockManagerPort, firstProcessId, + nativeInternalDirPath, }); playgroundsToCleanUp.push({ @@ -521,28 +548,14 @@ export async function runCLI(args: RunCLIArgs): Promise { worker: worker.worker, }); - // Replicate the Blueprint-initialized /internal directory - await additionalPlayground.writeFile( - '/tmp/internal.zip', - internalZip - ); - await unzipFile( - additionalPlayground, - '/tmp/internal.zip', - '/internal' - ); - await additionalPlayground.unlink( - '/tmp/internal.zip' - ); - loadBalancer.addWorker(additionalPlayground); }) ); - - logger.log(`Ready!`); } - logger.log(`WordPress is running on ${absoluteUrl}`); + logger.log( + `WordPress is running on ${absoluteUrl} with ${totalWorkerCount} worker(s)` + ); if (args.experimentalDevtools && args.xdebug) { const bridge = await startBridge({ @@ -725,3 +738,7 @@ async function zipSite( const zip = await playground.readFileAsBuffer('/tmp/build.zip'); fs.writeFileSync(outfile, zip); } + +function isMountingWordPressDir(mount: Mount) { + return mount.vfsPath === '/wordpress'; +} diff --git a/packages/playground/cli/tests/run-cli.spec.ts b/packages/playground/cli/tests/run-cli.spec.ts index 4394b25090..0ed3fea532 100644 --- a/packages/playground/cli/tests/run-cli.spec.ts +++ b/packages/playground/cli/tests/run-cli.spec.ts @@ -260,7 +260,7 @@ describe('run-cli', () => { 'symlinked-script' ); - mkdirSync( path.dirname( symlinkPath ), { recursive: true } ); + mkdirSync(path.dirname(symlinkPath), { recursive: true }); try { if (existsSync(symlinkPath)) { diff --git a/packages/playground/wordpress/src/boot.ts b/packages/playground/wordpress/src/boot.ts index ccf1951154..5d0bd17f4b 100644 --- a/packages/playground/wordpress/src/boot.ts +++ b/packages/playground/wordpress/src/boot.ts @@ -37,7 +37,7 @@ export interface Hooks { export type DatabaseType = 'sqlite' | 'mysql' | 'custom'; export interface BootRequestHandlerOptions { - createPhpRuntime: () => Promise; + createPhpRuntime: (isPrimary?: boolean) => Promise; onPHPInstanceCreated?: (php: PHP) => Promise; /** * PHP SAPI name to be returned by get_sapi_name(). Overriding @@ -232,7 +232,8 @@ export async function bootRequestHandler(options: BootRequestHandlerOptions) { requestHandler: PHPRequestHandler, isPrimary: boolean ) { - const php = new PHP(await options.createPhpRuntime()); + const runtimeId = await options.createPhpRuntime(isPrimary); + const php = new PHP(runtimeId); if (options.sapiName) { php.setSapiName(options.sapiName); } @@ -243,7 +244,7 @@ export async function bootRequestHandler(options: BootRequestHandlerOptions) { setPhpIniEntries(php, options.phpIniEntries); } /** - * Set up mu-plugins in /internal/shared/mu-plugins + * Set up mu-plugins in /internal/mu-plugins * using auto_prepend_file to provide platform-level * customization without altering the installed WordPress * site. @@ -265,8 +266,7 @@ export async function bootRequestHandler(options: BootRequestHandlerOptions) { proxyFileSystem(await requestHandler.getPrimaryPhp(), php, [ '/tmp', requestHandler.documentRoot, - '/internal/shared', - '/internal/symlinks', + '/internal', ]); } diff --git a/packages/playground/wordpress/src/index.ts b/packages/playground/wordpress/src/index.ts index 3a125e0c81..89889444a3 100644 --- a/packages/playground/wordpress/src/index.ts +++ b/packages/playground/wordpress/src/index.ts @@ -8,6 +8,7 @@ export { bootRequestHandler, getFileNotFoundActionForWordPress, } from './boot'; +export type { PhpIniOptions } from './boot'; export { defineWpConfigConstants, ensureWpConfig } from './rewrite-wp-config'; export { getLoadedWordPressVersion } from './version-detect';