22# Copyright 2019 Rover Robotics
33# Licensed under the Apache License, Version 2.0
44
5+ import distutils .core
56import multiprocessing
67import os
78from 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 (desc .path .resolve ()):
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+
168207def create_dependency_descriptor (requirement_string ):
169208 """
170209 Create a DependencyDescriptor from a PEP440 compliant string.
0 commit comments