diff --git a/bin/colcon b/bin/colcon index 28ea5fd9..8e228042 100755 --- a/bin/colcon +++ b/bin/colcon @@ -41,6 +41,8 @@ from colcon_core.command import LOG_LEVEL_ENVIRONMENT_VARIABLE # noqa: E402 from colcon_core.entry_point \ import EXTENSION_BLOCKLIST_ENVIRONMENT_VARIABLE # noqa: E402 from colcon_core.environment.path import PathEnvironment # noqa: E402 +from colcon_core.environment.path \ + import PythonScriptsPathEnvironment # noqa: E402 from colcon_core.environment.pythonpath \ import PythonPathEnvironment # noqa: E402 from colcon_core.event_handler.console_direct \ @@ -79,6 +81,7 @@ custom_entry_points.update({ 'colcon_core.argument_parser': {}, 'colcon_core.environment': { 'path': PathEnvironment, + 'pythonscriptspath': PythonScriptsPathEnvironment, 'pythonpath': PythonPathEnvironment, }, 'colcon_core.environment_variable': { diff --git a/colcon_core/environment/path.py b/colcon_core/environment/path.py index 6dbef3c7..9795108b 100644 --- a/colcon_core/environment/path.py +++ b/colcon_core/environment/path.py @@ -1,13 +1,23 @@ # Copyright 2016-2018 Dirk Thomas # Licensed under the Apache License, Version 2.0 -import os -import sys - from colcon_core import shell from colcon_core.environment import EnvironmentExtensionPoint from colcon_core.environment import logger from colcon_core.plugin_system import satisfies_version +from colcon_core.python_install_path import get_python_install_path + + +def _has_file(path): + logger.log(1, "checking '%s'" % path) + + if not path.is_dir(): + return False + + for child in path.iterdir(): + if child.is_file(): + return True + return False class PathEnvironment(EnvironmentExtensionPoint): @@ -19,29 +29,34 @@ def __init__(self): # noqa: D107 EnvironmentExtensionPoint.EXTENSION_POINT_VERSION, '^1.0') def create_environment_hooks(self, prefix_path, pkg_name): # noqa: D102 - hooks = self._create_environment_hooks(prefix_path, pkg_name, 'bin') - if sys.platform == 'win32': - hooks += self._create_environment_hooks( - prefix_path, pkg_name, 'Scripts', '-scripts') + subdirectory = 'bin' + hooks = [] + bin_path = prefix_path / subdirectory + + if _has_file(bin_path): + hooks += shell.create_environment_hook( + 'path', prefix_path, pkg_name, 'PATH', subdirectory, + mode='prepend') + return hooks - def _create_environment_hooks( - self, prefix_path, pkg_name, subdirectory, suffix='' - ): + +class PythonScriptsPathEnvironment(EnvironmentExtensionPoint): + """Extend the `PATH` variable to find python scripts.""" + + def __init__(self): # noqa: D107 + super().__init__() + satisfies_version( + EnvironmentExtensionPoint.EXTENSION_POINT_VERSION, '^1.0') + + def create_environment_hooks(self, prefix_path, pkg_name): # noqa: D102 hooks = [] - bin_path = prefix_path / subdirectory - logger.log(1, "checking '%s'" % bin_path) - try: - names = os.listdir(str(bin_path)) - except FileNotFoundError: - pass - else: - for name in names: - if not (bin_path / name).is_file(): - continue - hooks += shell.create_environment_hook( - 'path' + suffix, prefix_path, pkg_name, 'PATH', - subdirectory, mode='prepend') - break + bin_path = get_python_install_path('scripts', {'base': prefix_path}) + + if _has_file(bin_path): + rel_bin_path = bin_path.relative_to(prefix_path) + hooks += shell.create_environment_hook( + 'pythonscriptspath', prefix_path, pkg_name, 'PATH', + str(rel_bin_path), mode='prepend') return hooks diff --git a/colcon_core/environment/pythonpath.py b/colcon_core/environment/pythonpath.py index 6b557e4b..9d65e289 100644 --- a/colcon_core/environment/pythonpath.py +++ b/colcon_core/environment/pythonpath.py @@ -1,13 +1,11 @@ # Copyright 2016-2018 Dirk Thomas # Licensed under the Apache License, Version 2.0 -from pathlib import Path -import sysconfig - from colcon_core import shell from colcon_core.environment import EnvironmentExtensionPoint from colcon_core.environment import logger from colcon_core.plugin_system import satisfies_version +from colcon_core.python_install_path import get_python_install_path class PythonPathEnvironment(EnvironmentExtensionPoint): @@ -21,8 +19,7 @@ def __init__(self): # noqa: D107 def create_environment_hooks(self, prefix_path, pkg_name): # noqa: D102 hooks = [] - python_path = Path( - sysconfig.get_path('purelib', vars={'base': prefix_path})) + python_path = get_python_install_path('purelib', {'base': prefix_path}) logger.log(1, "checking '%s'" % python_path) if python_path.exists(): rel_python_path = python_path.relative_to(prefix_path) diff --git a/colcon_core/python_install_path.py b/colcon_core/python_install_path.py new file mode 100644 index 00000000..d5f26d15 --- /dev/null +++ b/colcon_core/python_install_path.py @@ -0,0 +1,24 @@ +# Copyright 2022 Open Source Robotics Foundation, Inc. +# Licensed under the Apache License, Version 2.0 + +from pathlib import Path +import sysconfig + + +def get_python_install_path(name, vars_=()): + """ + Get Python install paths matching Colcon's preferred scheme. + + See sysconfig.get_path for more info about the arguments. + + :param name: Name of the path type + :param vars_: A dictionary of variables updating the values of + sysconfig.get_config_vars() + :rtype: Pathlib.Path + """ + kwargs = {} + kwargs['vars'] = dict(vars_) + if 'deb_system' in sysconfig.get_scheme_names(): + kwargs['scheme'] = 'deb_system' + + return Path(sysconfig.get_path(name, **kwargs)) diff --git a/colcon_core/task/python/build.py b/colcon_core/task/python/build.py index ff7956c0..bc5d73de 100644 --- a/colcon_core/task/python/build.py +++ b/colcon_core/task/python/build.py @@ -8,12 +8,12 @@ import shutil import sys from sys import executable -import sysconfig from colcon_core.environment import create_environment_hooks from colcon_core.environment import create_environment_scripts from colcon_core.logging import colcon_logger from colcon_core.plugin_system import satisfies_version +from colcon_core.python_install_path import get_python_install_path from colcon_core.shell import create_environment_hook from colcon_core.shell import get_command_environment from colcon_core.subprocess import check_output @@ -296,7 +296,7 @@ def _symlinks_in_build(self, args, setup_py_data): return temp_symlinks def _get_python_lib(self, args): - path = sysconfig.get_path('purelib', vars={'base': args.install_base}) + path = get_python_install_path('purelib', {'base': args.install_base}) return os.path.relpath(path, start=args.install_base) def _append_install_layout(self, args, cmd): diff --git a/setup.cfg b/setup.cfg index b4f1b54d..bdcb900d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -81,6 +81,7 @@ colcon_core.argument_parser = colcon_core.environment = path = colcon_core.environment.path:PathEnvironment pythonpath = colcon_core.environment.pythonpath:PythonPathEnvironment + pythonscriptspath = colcon_core.environment.path:PythonScriptsPathEnvironment colcon_core.environment_variable = all_shells = colcon_core.shell:ALL_SHELLS_ENVIRONMENT_VARIABLE default_executor = colcon_core.executor:DEFAULT_EXECUTOR_ENVIRONMENT_VARIABLE diff --git a/test/spell_check.words b/test/spell_check.words index 9ac1863d..8ee496aa 100644 --- a/test/spell_check.words +++ b/test/spell_check.words @@ -74,6 +74,7 @@ pydocstyle pytest pytests pythonpath +pythonscriptspath pythonwarnings readouterr readthedocs diff --git a/test/test_environment_pythonpath.py b/test/test_environment_pythonpath.py index 842e3319..00aacb36 100644 --- a/test/test_environment_pythonpath.py +++ b/test/test_environment_pythonpath.py @@ -2,11 +2,11 @@ # Licensed under the Apache License, Version 2.0 from pathlib import Path -import sysconfig from tempfile import TemporaryDirectory from unittest.mock import patch from colcon_core.environment.pythonpath import PythonPathEnvironment +from colcon_core.python_install_path import get_python_install_path def test_pythonpath(): @@ -23,8 +23,8 @@ def test_pythonpath(): assert len(hooks) == 0 # Python path exists - python_path = Path( - sysconfig.get_path('purelib', vars={'base': prefix_path})) + python_path = get_python_install_path( + 'purelib', {'base': prefix_path}) python_path.mkdir(parents=True) hooks = extension.create_environment_hooks(prefix_path, 'pkg_name') assert len(hooks) == 2 diff --git a/test/test_environment_pythonscriptspath.py b/test/test_environment_pythonscriptspath.py new file mode 100644 index 00000000..74c70120 --- /dev/null +++ b/test/test_environment_pythonscriptspath.py @@ -0,0 +1,45 @@ +# Copyright 2016-2018 Dirk Thomas +# Licensed under the Apache License, Version 2.0 + +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch + +from colcon_core.environment.path import PythonScriptsPathEnvironment +from colcon_core.python_install_path import get_python_install_path + + +def test_path(): + extension = PythonScriptsPathEnvironment() + + with TemporaryDirectory(prefix='test_colcon_') as prefix_path: + prefix_path = Path(prefix_path) + scripts_path = get_python_install_path( + 'scripts', {'base': prefix_path}) + with patch( + 'colcon_core.shell.create_environment_hook', + return_value=['/some/hook', '/other/hook'] + ): + # bin directory does not exist + hooks = extension.create_environment_hooks(prefix_path, 'pkg_name') + assert len(hooks) == 0 + + # bin directory exists, but empty + scripts_path.mkdir() + hooks = extension.create_environment_hooks(prefix_path, 'pkg_name') + assert len(hooks) == 0 + + # bin directory exists, but only subdirectories + (scripts_path / 'subdir').mkdir() + hooks = extension.create_environment_hooks(prefix_path, 'pkg_name') + assert len(hooks) == 0 + + # bin directory exists, with file + (scripts_path / 'hook').write_text('') + hooks = extension.create_environment_hooks(prefix_path, 'pkg_name') + assert len(hooks) == 2 + + # bin directory exists, with files + (scripts_path / 'hook2').write_text('') + hooks = extension.create_environment_hooks(prefix_path, 'pkg_name') + assert len(hooks) == 2