saltstack / relenv

Re-producible and Re-relocatable Python Environments
Apache License 2.0
25 stars 16 forks source link

Not compatible with VSCode debug protocol adapter #171

Closed max-arnold closed 8 months ago

max-arnold commented 8 months ago

Salt 3006.5, Ubuntu 22.04:

salt-pip install debugpy
/opt/saltstack/salt/bin/python3 -c 'import debugpy; debugpy.listen(5678); debugpy.wait_for_client()'
0.69s - Error patching args (debugger not attached to subprocess).

Traceback (most recent call last):
  File "/opt/saltstack/salt/extras-3.10/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py", line 526, in patch_args
    host, port = _get_host_port()
  File "/opt/saltstack/salt/extras-3.10/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py", line 212, in _get_host_port
    host, port = pydevd.dispatch()
  File "/opt/saltstack/salt/extras-3.10/debugpy/_vendored/pydevd/pydevd.py", line 3084, in dispatch
    host = setup['client']
TypeError: 'NoneType' object is not subscriptable

The same command works with system Python binary.

dwoz commented 8 months ago

@max-arnold Did you install the debug header package?

max-arnold commented 8 months ago

Yes, I did run apt install salt-dbg

dwoz commented 8 months ago

@max-arnold

does RELENV_BUILDENV=1 /opt/saltstack/salt/bin/python3 -c 'import debugpy; debugpy.listen(5678); debugpy.wait_for_client()' give you the desired results?

dwoz commented 8 months ago

Looks like relenv.runtime is shelling out to the system python more than one time while executing the example. This patch seems to help

diff --git a/relenv/runtime.py b/relenv/runtime.py
index 2fa7483..f519511 100644
--- a/relenv/runtime.py
+++ b/relenv/runtime.py
@@ -148,6 +148,7 @@ def get_config_var_wrapper(func):
     Return a wrapper to resolve paths relative to the relenv root.
     """

+    @functools.wraps(func)
     def wrapped(name):
         if name == "BINDIR":
             orig = func(name)
@@ -179,35 +180,45 @@ _CONFIG_VARS_DEFAULTS = {
     "LDSHARED": "gcc -shared",
 }

+_SYSTEM_CONFIG_VARS = None
+
+def get_system_sysconfig():
+    global _SYSTEM_CONFIG_VARS
+    if _SYSTEM_CONFIG_VARS:
+        return _SYSTEM_CONFIG_VARS
+    pyexec = pathlib.Path("/usr/bin/python3")
+    if pyexec.exists():
+        p = subprocess.run(
+            [
+                str(pyexec),
+                "-c",
+                "import json, sysconfig; print(json.dumps(sysconfig.get_config_vars()))",
+            ],
+            capture_output=True,
+        )
+        try:
+            _SYSTEM_CONFIG_VARS = json.loads(p.stdout.strip())
+        except json.JSONDecodeError:
+            debug(f"Failed to load JSON from: {p.stdout.strip()}")
+            _SYSTEM_CONFIG_VARS = _CONFIG_VARS_DEFAULTS
+    else:
+        debug("System python not found")
+        _SYSTEM_CONFIG_VARS = _CONFIG_VARS_DEFAULTS
+    return _SYSTEM_CONFIG_VARS
+

 def get_config_vars_wrapper(func, mod):
     """
     Return a wrapper to resolve paths relative to the relenv root.
     """

+    @functools.wraps(func)
     def wrapped(*args):
         if sys.platform == "win32" or "RELENV_BUILDENV" in os.environ:
             return func(*args)

         _CONFIG_VARS = func()
-        pyexec = pathlib.Path("/usr/bin/python3")
-        if pyexec.exists():
-            p = subprocess.run(
-                [
-                    str(pyexec),
-                    "-c",
-                    "import json, sysconfig; print(json.dumps(sysconfig.get_config_vars()))",
-                ],
-                capture_output=True,
-            )
-            try:
-                _SYSTEM_CONFIG_VARS = json.loads(p.stdout.strip())
-            except json.JSONDecodeError:
-                debug(f"Failed to load JSON from: {p.stdout.strip()}")
-                _SYSTEM_CONFIG_VARS = _CONFIG_VARS_DEFAULTS
-        else:
-            debug("System python not found")
-            _SYSTEM_CONFIG_VARS = _CONFIG_VARS_DEFAULTS
+        _SYSTEM_CONFIG_VARS = get_system_sysconfig()

         for name in [
             "AR",
@@ -234,6 +245,7 @@ def get_paths_wrapper(func, default_scheme):
     Return a wrapper to resolve paths relative to the relenv root.
     """

+    @functools.wraps(func)
     def wrapped(scheme=default_scheme, vars=None, expand=True):
         paths = func(scheme=scheme, vars=vars, expand=expand)
         if "RELENV_PIP_DIR" in os.environ:
@@ -251,6 +263,7 @@ def finalize_options_wrapper(func):
     Used to add the relenv environment's include path.
     """

+    @functools.wraps(func)
     def wrapper(self, *args, **kwargs):
         func(self, *args, **kwargs)
         if "RELENV_BUILDENV" in os.environ:
@@ -436,7 +449,7 @@ class RelenvImporter:
     def __init__(self, wrappers=None, _loads=None):
         if wrappers is None:
             wrappers = []
-        self.wrappers = wrappers
+        self.wrappers = set(wrappers)
         if _loads is None:
             _loads = {}
         self._loads = _loads
dwoz commented 8 months ago

Fixed in 0.15.1