Browse Source

Improvements for env handling with node.js and emscripten

- Initializes the environment so that getenv et al works properly
- No longer calls JS functions at static init time; the JS preamble will
  pass the right env vars to ExecutionEnvironment
- Sets the binary name to the path of the .js file
rdb 10 months ago
parent
commit
6452907b64

+ 50 - 0
dtool/src/dtoolutil/console_preamble.js

@@ -0,0 +1,50 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file console_preamble.js
+ * @author rdb
+ * @date 2025-02-03
+ */
+
+if (ENVIRONMENT_IS_NODE) {
+  Module["preInit"] = Module["preInit"] || [];
+  Module["preInit"].push(function() {
+    if (typeof process === "object" && typeof process.env === "object") {
+      // These are made up by emscripten if we don't set them to undefined
+      ENV['USER'] = undefined;
+      ENV['LOGNAME'] = undefined;
+      ENV['PATH'] = undefined;
+      ENV['PWD'] = undefined;
+      ENV['HOME'] = undefined;
+      ENV['LANG'] = undefined;
+      ENV['_'] = undefined;
+      for (var variable in process.env) {
+        ENV[variable] = process.env[variable];
+      }
+    }
+
+    addOnPreMain(function preloadNodeEnv() {
+      var sp = stackSave();
+      var set_binary_name = wasmExports["_set_binary_name"];
+      if (set_binary_name && typeof __filename === "string") {
+        set_binary_name(stringToUTF8OnStack(__filename));
+      }
+
+      var set_env_var = wasmExports["_set_env_var"];
+      if (set_env_var) {
+        for (var variable in ENV) {
+          var value = ENV[variable];
+          if (value !== undefined) {
+            set_env_var(stringToUTF8OnStack(variable), stringToUTF8OnStack(value));
+          }
+        }
+      }
+      stackRestore(sp);
+    });
+  });
+}

+ 12 - 13
dtool/src/dtoolutil/executionEnvironment.cxx

@@ -125,12 +125,18 @@ static const char *const libp3dtool_filenames[] = {
 
 #if defined(__EMSCRIPTEN__) && !defined(CPPPARSER)
 extern "C" void EMSCRIPTEN_KEEPALIVE
-_set_env_var(ExecutionEnvironment *ptr, const char *var, const char *value) {
+_set_env_var(const char *var, const char *value) {
+  ExecutionEnvironment *ptr = ExecutionEnvironment::get_ptr();
   ptr->_variables[std::string(var)] = std::string(value);
 }
+
+extern "C" void EMSCRIPTEN_KEEPALIVE
+_set_binary_name(const char *path) {
+  ExecutionEnvironment::set_binary_name(std::string(path));
+}
 #endif
 
-// Linux with GNU libc does have global argvargc variables, but we can't
+// Linux with GNU libc does have global argv/argc variables, but we can't
 // safely access them at stat init time--at least, not in libc5. (It does seem
 // to work with glibc2, however.)
 
@@ -584,17 +590,10 @@ read_environment_variables() {
     }
   }
 #elif defined(__EMSCRIPTEN__)
-  // We only have environment variables if we're running in node.js.
-#ifndef CPPPARSER
-  EM_ASM({
-    if (typeof process === 'object' && typeof process.env === 'object') {
-      for (var variable in process.env) {
-        __set_env_var($0, stringToUTF8OnStack(variable),
-                          stringToUTF8OnStack(process.env[variable]));
-      }
-    }
-  }, this);
-#endif
+  // The environment variables get loaded in by the .js file before main()
+  // using the _set_env_var exported function, defined above.  Trying to load
+  // env vars at static init time otherwise makes some optimizations more
+  // difficult, notably wasm-ctor-eval/wizer.
 
 #elif defined(HAVE_PROC_SELF_ENVIRON)
   // In some cases, we may have a file called procselfenviron that may be read

+ 4 - 4
dtool/src/dtoolutil/executionEnvironment.h

@@ -22,10 +22,10 @@
 #include <map>
 
 #if defined(__EMSCRIPTEN__) && !defined(CPPPARSER)
-class ExecutionEnvironment;
-
 extern "C" void EMSCRIPTEN_KEEPALIVE
-_set_env_var(ExecutionEnvironment *ptr, const char *var, const char *value);
+_set_env_var(const char *var, const char *value);
+extern "C" void EMSCRIPTEN_KEEPALIVE
+_set_binary_name(const char *path);
 #endif
 
 /**
@@ -98,7 +98,7 @@ private:
   static ExecutionEnvironment *_global_ptr;
 
 #ifdef __EMSCRIPTEN__
-  friend void ::_set_env_var(ExecutionEnvironment *ptr, const char *var, const char *value);
+  friend void ::_set_env_var(const char *var, const char *value);
 #endif
 };
 

+ 4 - 1
makepanda/makepanda.py

@@ -1913,6 +1913,9 @@ def CompileLink(dll, obj, opts):
             if GetOrigExt(dll) == ".exe":
                 cmd += " -s EXIT_RUNTIME=1"
 
+                if dll.endswith(".js") and "SUBSYSTEM:WINDOWS" not in opts:
+                    cmd += " --pre-js dtool/src/dtoolutil/console_preamble.js"
+
         else:
             cmd += " -pthread"
             if "SYSROOT" in SDK:
@@ -5984,7 +5987,7 @@ if GetLinkAllStatic():
     if not PkgSkip('BULLET'):
         DefSymbol('RUN_TESTS_FLAGS', 'HAVE_BULLET')
 
-    OPTS=['DIR:tests', 'PYTHON', 'RUN_TESTS_FLAGS']
+    OPTS=['DIR:tests', 'PYTHON', 'RUN_TESTS_FLAGS', 'SUBSYSTEM:CONSOLE']
     PyTargetAdd('run_tests-main.obj', opts=OPTS, input='main.c')
     PyTargetAdd('run_tests.exe', input='run_tests-main.obj')
     PyTargetAdd('run_tests.exe', input='core.pyd')

+ 0 - 42
tests/main.c

@@ -19,11 +19,6 @@
 
 #include <Python.h>
 
-#ifdef __EMSCRIPTEN__
-#include <emscripten/emscripten.h>
-#include <emscripten/em_asm.h>
-#endif
-
 #include "pandabase.h"
 
 #ifdef LINK_ALL_STATIC
@@ -55,43 +50,6 @@ int main(int argc, char **argv) {
   PyConfig config;
   PyConfig_InitPythonConfig(&config);
 
-#ifdef __EMSCRIPTEN__
-  // getenv does not work with emscripten, instead read the PYTHONPATH and
-  // PYTHONHOME from the process.env variable if we're running in node.js.
-  char path[4096], home[4096];
-  path[0] = 0;
-  home[0] = 0;
-
-  EM_ASM({
-    if (typeof process === 'object' && typeof process.env === 'object') {
-      var path = process.env.PYTHONPATH;
-      var home = process.env.PYTHONHOME;
-      if (path) {
-        if (process.platform === 'win32') {
-          path = path.replace(/;/g, ':');
-        }
-        stringToUTF8(path, $0, $1);
-      }
-      if (home) {
-        stringToUTF8(home, $2, $3);
-      }
-    }
-  }, path, sizeof(path), home, sizeof(home));
-
-  if (path[0] != 0) {
-    status = PyConfig_SetBytesString(&config, &config.pythonpath_env, path);
-    if (PyStatus_Exception(status)) {
-      goto exception;
-    }
-  }
-  if (home[0] != 0) {
-    status = PyConfig_SetBytesString(&config, &config.home, home);
-    if (PyStatus_Exception(status)) {
-      goto exception;
-    }
-  }
-#endif
-
   PyConfig_SetBytesString(&config, &config.run_module, "pytest");
   config.parse_argv = 0;