def create_environment_scripts_only(prefix_path, pkg, *, default_hooks=None, additional_hooks=None): """ Create the environment scripts for a package. :param prefix_path: The prefix path :param pkg: The package descriptor :param list default_hooks: If none are parsed explicitly the hooks provided by :function:`create_environment_hooks` are used :param list additional_hooks: Any additional hooks which should be referenced by the generated scripts """ logger.log(1, 'create_environment_scripts_only(%s)', pkg.name) hooks = [] if default_hooks is None: default_hooks = create_environment_hooks(prefix_path, pkg.name) hooks += default_hooks if additional_hooks: hooks += additional_hooks hooks += pkg.hooks # ensure each hook is presented by a tuple # with the first element being a relative path # and the second element being a list of arguments hook_tuples = [] for hook in hooks: hook_args = [] if isinstance(hook, list) or isinstance(hook, tuple): hook_args = hook[1:] hook = hook[0] if os.path.isabs(str(hook)): hook = os.path.relpath(str(hook), start=str(prefix_path)) hook_tuples.append((hook, hook_args)) extensions = get_shell_extensions() for priority in extensions.keys(): extensions_same_prio = extensions[priority] for extension in extensions_same_prio.values(): try: retval = extension.create_package_script( prefix_path, pkg.name, hook_tuples) assert retval is None, \ 'create_package_script() should return None' except Exception as e: # noqa: F841 # catch exceptions raised in shell extension exc = traceback.format_exc() logger.error( "Exception in shell extension '{extension.SHELL_NAME}': " '{e}\n{exc}'.format_map(locals()))
async def build(self): # noqa: D102 args = self.context.args logger.info("Building ROS package in '{args.path}' with build type " "'ament_cmake'".format_map(locals())) # reuse CMake build task with additional logic extension = CmakeBuildTask() extension.set_context(context=self.context) # additional arguments if args.test_result_base: if args.cmake_args is None: args.cmake_args = [] # ament_cmake appends the project name itself args.cmake_args.append('-DAMENT_TEST_RESULTS_DIR=' + os.path.dirname(args.test_result_base)) if args.symlink_install: if args.cmake_args is None: args.cmake_args = [] args.cmake_args.append('-DAMENT_CMAKE_SYMLINK_INSTALL=1') if args.ament_cmake_args: if args.cmake_args is None: args.cmake_args = [] args.cmake_args += args.ament_cmake_args rc = await extension.build(skip_hook_creation=False, environment_callback=add_app_to_cpp) # if the build has failed getting targets might not be possible try: has_install_target = await has_target(args.build_base, 'install') except Exception: # noqa: B902 if not rc: raise has_install_target = False # add a hook for each available shell # only if the package has an install target additional_hooks = [] if has_install_target: shell_extensions = get_shell_extensions() file_extensions = [] for shell_extensions_same_prio in shell_extensions.values(): for shell_extension in shell_extensions_same_prio.values(): file_extensions += shell_extension.get_file_extensions() for ext in sorted(file_extensions): additional_hooks.append( 'share/{self.context.pkg.name}/local_setup.{ext}'. format_map(locals())) create_environment_scripts(self.context.pkg, args, additional_hooks=additional_hooks) return rc
def test_get_command_environment(): with EntryPointContext(extension1=Extension1, extension2=Extension2): extensions = get_shell_extensions() # one not implemented, one skipped extension extensions[90]['extension1'].generate_command_environment = Mock( side_effect=SkipExtensionException()) coroutine = get_command_environment(None, '/build/base', None) with patch('colcon_core.shell.logger.debug') as debug: with patch('colcon_core.shell.logger.info') as info: with pytest.raises(RuntimeError) as e: run_until_complete(coroutine) assert 'Could not find a shell extension for the command environment' \ in str(e.value) assert extensions[90]['extension1'].generate_command_environment \ .call_count == 1 # the raised exceptions are catched and result in a debug/info message assert debug.call_count == 1 assert len(debug.call_args[0]) == 1 assert debug.call_args[0][0] == \ "Skip shell extension 'extension2' for command environment" assert info.call_count == 1 assert len(info.call_args[0]) == 1 assert info.call_args[0][0].startswith( "Skip shell extension 'extension1' for command environment: ") # raise runtime error extensions[100]['extension2'].generate_command_environment = Mock( side_effect=RuntimeError('custom exception')) extensions[90]['extension1'].generate_command_environment.reset_mock() coroutine = get_command_environment(None, '/build/base', None) with pytest.raises(RuntimeError) as e: run_until_complete(coroutine) assert str(e.value) == 'custom exception' assert extensions[90]['extension1'].generate_command_environment \ .call_count == 0 # one exception, one successful extensions[100]['extension2'].generate_command_environment = Mock( side_effect=Exception('custom exception')) extensions[90]['extension1'].generate_command_environment = Mock( side_effect=generate_command_environment) coroutine = get_command_environment(None, '/build/base', None) with patch('colcon_core.shell.logger.error') as error: env = run_until_complete(coroutine) assert env == {'key': 'value'} # the raised exception is catched and results in an error message assert error.call_count == 1 assert len(error.call_args[0]) == 1 assert error.call_args[0][0].startswith( "Exception in shell extension 'extension2': custom exception\n")
def _create_prefix_scripts(self, install_base, merge_install): extensions = get_shell_extensions() for priority in extensions.keys(): extensions_same_prio = extensions[priority] for extension in extensions_same_prio.values(): try: retval = extension.create_prefix_script( Path(install_base), merge_install) assert retval is None, \ 'create_prefix_script() should return None' except Exception as e: # noqa: F841 # catch exceptions raised in shell extension exc = traceback.format_exc() logger.error( 'Exception in shell extension ' "'{extension.SHELL_NAME}': {e}\n{exc}".format_map( locals()))
def test_create_environment_scripts(): with TemporaryDirectory(prefix='test_colcon_') as basepath: pkg = Mock() pkg.name = 'name' pkg.dependencies = {} pkg.hooks = [] args = Mock() args.install_base = basepath # no hooks at all with patch( 'colcon_core.environment.create_environment_hooks', return_value=[] ): with patch( 'colcon_core.environment.get_shell_extensions', return_value={} ): create_environment_scripts(pkg, args) pkg.hooks = [os.path.join(basepath, 'subA')] with EntryPointContext(extension3=Extension3, extension4=Extension4): extensions = get_shell_extensions() # one invalid return value, one check correct hooks argument extensions[100]['extension3'].create_package_script = Mock() extensions[100]['extension4'].create_package_script = Mock( return_value=None) with patch('colcon_core.environment.logger.error') as error: create_environment_scripts( pkg, args, default_hooks=[('subB', )], additional_hooks=[['subC', 'arg1', 'arg2']]) # the raised exception is catched and results in an error message assert error.call_count == 1 assert len(error.call_args[0]) == 1 assert error.call_args[0][0].startswith( "Exception in shell extension 'extension3': " 'create_package_script() should return None\n') # check for correct hooks argument mock = extensions[100]['extension4'].create_package_script assert mock.call_count == 1 assert len(mock.call_args[0]) == 3 assert mock.call_args[0][0] == Path(args.install_base) assert mock.call_args[0][1] == pkg.name hook_tuples = mock.call_args[0][2] assert len(hook_tuples) == 3 assert hook_tuples[0] == ('subB', ()) assert hook_tuples[1] == ('subC', ['arg1', 'arg2']) assert hook_tuples[2] == ('subA', [])
async def build(self): # noqa: D102 args = self.context.args logger.info("Building ROS package in '{args.path}' with build type " "'ament_cmake'".format_map(locals())) # reuse CMake build task with additional logic extension = CmakeBuildTask() extension.set_context(context=self.context) # add a hook for each available shell additional_hooks = create_environment_hook('ament_current_prefix', Path(args.install_base), self.context.pkg.name, 'AMENT_CURRENT_PREFIX', '', mode='prepend') shell_extensions = get_shell_extensions() file_extensions = [] for shell_extensions_same_prio in shell_extensions.values(): for shell_extension in shell_extensions_same_prio.values(): file_extensions += shell_extension.get_file_extensions() for file_extension in sorted(file_extensions): additional_hooks.append( 'share/{self.context.pkg.name}/local_setup.{file_extension}'. format_map(locals())) # additional arguments if args.test_result_base: if args.cmake_args is None: args.cmake_args = [] # ament_cmake appends the project name itself args.cmake_args.append('-DAMENT_TEST_RESULTS_DIR=' + os.path.dirname(args.test_result_base)) if args.symlink_install: if args.cmake_args is None: args.cmake_args = [] args.cmake_args.append('-DAMENT_CMAKE_SYMLINK_INSTALL=1') if args.ament_cmake_args: if args.cmake_args is None: args.cmake_args = [] args.cmake_args += args.ament_cmake_args return await extension.build(environment_callback=add_app_to_cpp, additional_hooks=additional_hooks)
def test_create_environment_hook(): with EntryPointContext(extension1=Extension1, extension2=Extension2): # no primary shell extension with pytest.raises(RuntimeError) as e: create_environment_hook(None, None, None, None, None) assert str(e.value).endswith( 'Could not find a primary shell extension for creating an ' 'environment hook') with EntryPointContext(extension3=Extension3, extension4=Extension4, extension5=Extension5): extensions = get_shell_extensions() # one invalid, two valid return values extensions[105]['extension3'].create_hook_prepend_value = Mock() extensions[101]['extension4'].create_hook_prepend_value = Mock( return_value=Path('/some/path/sub/hookA')) extensions[110]['extension5'].create_hook_prepend_value = Mock( return_value=Path('/some/path/sub/hookB')) with patch('colcon_core.shell.logger.error') as error: hooks = create_environment_hook(None, None, None, None, None) assert len(hooks) == 2 assert str(hooks[0]) == '/some/path/sub/hookB'.replace('/', os.sep) assert str(hooks[1]) == '/some/path/sub/hookA'.replace('/', os.sep) # the raised exception is catched and results in an error message assert error.call_count == 1 assert len(error.call_args[0]) == 1 assert error.call_args[0][0].startswith( "Exception in shell extension 'extension3': " 'create_hook_prepend_value() should return a Path object') # invalid mode with pytest.raises(NotImplementedError): create_environment_hook(None, None, None, None, None, mode='invalid')
def test_get_shell_extensions(): with EntryPointContext(extension1=Extension1, extension2=Extension2): extensions = get_shell_extensions() assert list(extensions.keys()) == [100, 90] assert list(extensions[100].keys()) == ['extension2'] assert list(extensions[90].keys()) == ['extension1']
async def build(self): # noqa: D102 args = self.context.args logger.info( "Building ROS package in '{args.path}' with build type 'catkin'". format_map(locals())) # reuse CMake build task with additional logic extension = CmakeBuildTask() extension.set_context(context=self.context) # additional arguments if args.cmake_args is None: args.cmake_args = [] args.cmake_args += ['-DCATKIN_INSTALL_INTO_PREFIX_ROOT=0'] if args.test_result_base: # catkin appends the project name itself args.cmake_args.append('-DCATKIN_TEST_RESULTS_DIR=' + os.path.dirname(args.test_result_base)) if args.catkin_cmake_args: args.cmake_args += args.catkin_cmake_args # invoke the build additional_targets = [] # if no specific target is specified consider building the 'tests' # target and continue if such a target doesn't exist if args.cmake_target is None: if not args.catkin_skip_building_tests: additional_targets.append('tests') args.cmake_target_skip_unavailable = True rc = await extension.build(skip_hook_creation=True, additional_targets=additional_targets) # for catkin packages add additional hooks after the package has # been built and installed depending on the installed files additional_hooks = create_environment_hook('ros_package_path', Path(args.install_base), self.context.pkg.name, 'ROS_PACKAGE_PATH', 'share', mode='prepend') additional_hooks += create_pythonpath_environment_hook( Path(args.install_base), self.context.pkg.name) additional_hooks += create_pkg_config_path_environment_hooks( Path(args.install_base), self.context.pkg.name) # register hooks created via catkin_add_env_hooks shell_extensions = get_shell_extensions() file_extensions = OrderedDict() for shell_extensions_same_prio in shell_extensions.values(): for shell_extension in shell_extensions_same_prio.values(): for file_extension in shell_extension.get_file_extensions(): file_extensions[file_extension] = shell_extension custom_hooks_path = Path(args.install_base) / \ 'share' / self.context.pkg.name / 'catkin_env_hook' for file_extension, shell_extension in file_extensions.items(): file_extension_hooks = sorted( custom_hooks_path.glob('*.{file_extension}'.format_map( locals()))) if file_extension_hooks: try: # try to set CATKIN_ENV_HOOK_WORKSPACE explicitly before # sourcing these hooks additional_hooks.append( shell_extension.create_hook_set_value( 'catkin_env_hook_workspace', Path(args.install_base), self.context.pkg.name, 'CATKIN_ENV_HOOK_WORKSPACE', '$COLCON_CURRENT_PREFIX')) except NotImplementedError: # since not all shell extensions might implement this pass additional_hooks += file_extension_hooks create_environment_scripts(self.context.pkg, args, additional_hooks=additional_hooks) # ensure that the install base has the marker file # identifying it as a catkin workspace marker = Path(args.install_base) / '.catkin' marker.touch(exist_ok=True) return rc
def create_environment_scripts( pkg, args, *, default_hooks=None, additional_hooks=None ): """ Create the environment scripts for a package. Also create a file with the runtime dependencies of each packages which can be used by the prefix scripts to source all packages in topological order. :param pkg: The package descriptor :param args: The parsed command line arguments :param list default_hooks: If none are parsed explicitly the hooks provided by :function:`create_environment_hooks` are used :param list additional_hooks: Any additional hooks which should be referenced by the generated scripts :returns: iterable of the generated hook paths :rtype: Iterable """ logger.log(1, 'create_environment_scripts()') prefix_path = Path(args.install_base) hooks = [] if default_hooks is None: default_hooks = create_environment_hooks(prefix_path, pkg.name) hooks += default_hooks if additional_hooks: hooks += additional_hooks hooks += pkg.hooks # ensure each hook is presented by a tuple # with the first element being a relative path # and the second element being a list of arguments hook_tuples = [] for hook in hooks: hook_args = [] if isinstance(hook, list) or isinstance(hook, tuple): hook_args = hook[1:] hook = hook[0] if os.path.isabs(str(hook)): hook = os.path.relpath(str(hook), start=str(prefix_path)) hook_tuples.append((hook, hook_args)) extensions = get_shell_extensions() for priority in extensions.keys(): extensions_same_prio = extensions[priority] for extension in extensions_same_prio.values(): try: retval = extension.create_package_script( prefix_path, pkg.name, hook_tuples) assert retval is None, \ 'create_package_script() should return None' except Exception as e: # catch exceptions raised in shell extension exc = traceback.format_exc() logger.error( "Exception in shell extension '{extension.SHELL_NAME}': " '{e}\n{exc}'.format_map(locals())) # skip failing extension, continue with next one # create file containing the runtime dependencies path = prefix_path / get_relative_package_index_path() / pkg.name path.parent.mkdir(parents=True, exist_ok=True) path.write_text( os.pathsep.join(sorted(pkg.dependencies.get('run', set()))))