Skip to content

Commit 65bfdeb

Browse files
committed
Don't use process isolation when bootstrapping
1 parent 1864386 commit 65bfdeb

File tree

1 file changed

+58
-19
lines changed

1 file changed

+58
-19
lines changed

colcon_core/package_identification/python.py

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Copyright 2019 Rover Robotics
33
# Licensed under the Apache License, Version 2.0
44

5+
import distutils.core
56
import multiprocessing
67
import os
78
from typing import Optional
@@ -36,7 +37,14 @@ def identify(self, desc): # noqa: D102
3637
# after this point, we are convinced this is a Python package,
3738
# so we should fail with an Exception instead of silently
3839

39-
config = get_setup_result(setup_py, env=None)
40+
if os.path.realpath(__file__).startswith(os.path.realpath(desc.path)):
41+
# Bootstrapping colcon.
42+
# todo: is this really necessary?
43+
get_setup_fn = get_setup_result_in_process
44+
else:
45+
get_setup_fn = get_setup_result
46+
47+
config = get_setup_fn(setup_py, env=None)
4048

4149
name = config['metadata'].name
4250
if not name:
@@ -64,8 +72,8 @@ def identify(self, desc): # noqa: D102
6472
for d in config[option_name] or ()}
6573

6674
def getter(env):
67-
nonlocal setup_py
68-
return get_setup_result(setup_py, env=env)
75+
nonlocal setup_py, get_setup_fn
76+
return get_setup_fn(setup_py, env=env)
6977

7078
desc.metadata['get_python_setup_options'] = getter
7179

@@ -134,7 +142,6 @@ def _get_setup_result_target(setup_py: str, env: dict, conn_send):
134142
:param conn_send: Connection to send the result as either a dict or an
135143
error string
136144
"""
137-
import distutils.core
138145
import traceback
139146
try:
140147
# need to be in setup.py's parent dir to detect any setup.cfg
@@ -146,25 +153,57 @@ def _get_setup_result_target(setup_py: str, env: dict, conn_send):
146153
result = distutils.core.run_setup(
147154
str(setup_py), ('--dry-run',), stop_after='config')
148155

149-
# could just return all attrs in result.__dict__, but we take this
150-
# opportunity to filter a few things that don't need to be there
151-
conn_send.send({
152-
attr: value for attr, value in result.__dict__.items()
153-
if (
154-
# These *seem* useful but always have the value 0.
155-
# Look for their values in the 'metadata' object instead.
156-
attr not in result.display_option_names
157-
# Getter methods
158-
and not callable(value)
159-
# Private properties
160-
and not attr.startswith('_')
161-
# Objects that are generally not picklable
162-
and attr not in ('cmdclass', 'distclass', 'ext_modules')
163-
)})
156+
conn_send.send(_distribution_to_dict(result))
164157
except BaseException:
165158
conn_send.send(traceback.format_exc())
166159

167160

161+
def get_setup_result_in_process(setup_py, *, env: Optional[dict]):
162+
"""
163+
Run setup.py in this process.
164+
165+
Prefer get_setup_result, since it provides process isolation is
166+
threadsafe, and returns predictable errors.
167+
:param setup_py: Path to a setup.py script
168+
:param env: Environment variables to set before running setup.py
169+
:return: Dictionary of data describing the package.
170+
:raise: RuntimeError if the script doesn't appear to be a setup script.
171+
Any exception raised in the setup.py script.
172+
"""
173+
save_env = os.environ.copy()
174+
save_cwd = os.getcwd()
175+
176+
try:
177+
if env is not None:
178+
os.environ.update(env)
179+
os.chdir(setup_py.parent)
180+
dist = distutils.core.run_setup(
181+
'setup.py', ('--dry-run',), stop_after='config')
182+
finally:
183+
if env is not None:
184+
os.environ.clear()
185+
os.environ.update(save_env)
186+
os.chdir(save_cwd)
187+
return _distribution_to_dict(dist)
188+
189+
190+
def _distribution_to_dict(distribution_object) -> dict:
191+
"""Turn a distribution into a dict, discarding unpicklable attributes."""
192+
return {
193+
attr: value for attr, value in distribution_object.__dict__.items()
194+
if (
195+
# These *seem* useful but always have the value 0.
196+
# Look for their values in the 'metadata' object instead.
197+
attr not in distribution_object.display_option_names
198+
# Getter methods
199+
and not callable(value)
200+
# Private properties
201+
and not attr.startswith('_')
202+
# Objects that are generally not picklable
203+
and attr not in ('cmdclass', 'distclass', 'ext_modules')
204+
)}
205+
206+
168207
def create_dependency_descriptor(requirement_string):
169208
"""
170209
Create a DependencyDescriptor from a PEP440 compliant string.

0 commit comments

Comments
 (0)