Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 14 additions & 56 deletions colcon_python_setup_py/package_identification/python_setup_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@
import os
from pathlib import Path
import runpy
try:
import setuptools
except ImportError:
pass
import subprocess
import sys
from threading import Lock
Expand All @@ -25,6 +21,8 @@
create_dependency_descriptor
from colcon_core.plugin_system import satisfies_version

from .run_setup_py import run_setup_py


class PythonPackageIdentification(PackageIdentificationExtensionPoint):
"""Identify Python packages with `setup.py` files."""
Expand Down Expand Up @@ -98,6 +96,7 @@ def get_setup_arguments(setup_py):
'colcon_python_setup_py.package_identification.python_setup_py.'
'get_setup_information() instead',
stacklevel=2)
import setuptools
global cwd_lock
if not cwd_lock:
cwd_lock = Lock()
Expand Down Expand Up @@ -237,9 +236,6 @@ def get_setup_arguments_with_context(setup_py, env):
return ast.literal_eval(output)


_process_pool = multiprocessing.Pool()


def get_setup_information(setup_py: Path, *, env: Mapping[str, str]):
"""
Dry run the setup.py file and get the configuration information.
Expand All @@ -250,56 +246,18 @@ def get_setup_information(setup_py: Path, *, env: Mapping[str, str]):
:raise: RuntimeError if the setup script encountered an error
"""
try:
return _process_pool.apply(
run_setup_py,
kwds={
'cwd': os.path.abspath(str(setup_py.parent)),
# might be os.environ, which is not picklable
'env': dict(env),
'script_args': ('--dry-run',),
'stop_after': 'config'
}
)
with multiprocessing.Pool(1) as _process_pool:
return _process_pool.apply(
run_setup_py,
kwds={
'cwd': os.path.abspath(str(setup_py.parent)),
# might be os.environ, which is not picklable
'env': dict(env),
'script_args': ('--dry-run',),
'stop_after': 'config'
}
)
except Exception as e:
raise RuntimeError(
"Failed to dry run setup script '{setup_py}': "
.format_map(locals()) + traceback.format_exc()) from e


def run_setup_py(cwd, env, script_args=(), stop_after='run'):
"""
Modify the current process and run setup.py.

This should be run in a subprocess to not affect the state of the current
process.

:param str cwd: absolute path to a directory containing a setup.py script
:param dict env: environment variables to set before running setup.py
:param script_args: command-line arguments to pass to setup.py
:param stop_after: tells setup() when to stop processing
:returns: the public properties of a Distribution object, minus objects
with are generally not picklable
"""
# need to be in setup.py's parent dir to detect any setup.cfg
os.chdir(cwd)

os.environ.clear()
os.environ.update(env)

result = distutils.core.run_setup(
'setup.py', script_args=script_args, stop_after=stop_after)

return {
key: value for key, value in result.__dict__.items()
if (
# Private properties
not key.startswith('_') and
# Getter methods
not callable(value) and
# Objects that are generally not picklable
key not in ('cmdclass', 'distclass', 'ext_modules') and
# These *seem* useful but always have the value 0.
# Look for their values in the 'metadata' object instead.
key not in result.display_option_names
)
}
51 changes: 51 additions & 0 deletions colcon_python_setup_py/package_identification/run_setup_py.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright 2019 Rover Robotics via Dan Rose
# Licensed under the Apache License, Version 2.0

import distutils.core
import os


def run_setup_py(cwd, env, script_args=(), stop_after='run'):
"""
Modify the current process and run setup.py.

This should be run in a subprocess to not affect the state of the current
process.

:param str cwd: absolute path to a directory containing a setup.py script
:param dict env: environment variables to set before running setup.py
:param script_args: command-line arguments to pass to setup.py
:param stop_after: tells setup() when to stop processing
:returns: the public properties of a Distribution object, minus objects
with are generally not picklable
"""
# need to be in setup.py's parent dir to detect any setup.cfg
os.chdir(cwd)

os.environ.clear()
os.environ.update(env)

result = distutils.core.run_setup(
'setup.py', script_args=script_args, stop_after=stop_after)

try:
# hack to make this class pickle to an ordinary set
from setuptools.extern.ordered_set import OrderedSet
OrderedSet.__reduce__ = lambda self: (set, (list(self),))
except ImportError:
pass

return {
key: value for key, value in result.__dict__.items()
if (
# Private properties
not key.startswith('_') and
# Getter methods
not callable(value) and
# Objects that are generally not picklable
key not in ('cmdclass', 'distclass', 'ext_modules') and
# These *seem* useful but always have the value 0.
# Look for their values in the 'metadata' object instead.
key not in result.display_option_names
)
}