Example #1
0
    async def test(self, *, additional_hooks=None):  # noqa: D102
        pkg = self.context.pkg
        args = self.context.args

        logger.info("Testing Python package in '{args.path}'".format_map(
            locals()))

        try:
            env = await get_command_environment('setup_py', args.build_base,
                                                self.context.dependencies)
        except RuntimeError as e:
            logger.error(str(e))
            return 1
        setup_py_data = get_setup_data(self.context.pkg, env)

        # select the step extension which should perform the python testing
        if args.python_testing:
            key = args.python_testing
            extension = get_python_testing_step_extension(key)
        else:
            extensions = get_python_testing_step_extensions()
            for key, extension in extensions.items():
                logger.log(1,
                           "test() by extension '{key}'".format_map(locals()))
                try:
                    matched = extension.match(self.context, env, setup_py_data)
                except Exception as e:  # noqa: F841
                    # catch exceptions raised in python testing step extension
                    exc = traceback.format_exc()
                    logger.error(
                        'Exception in Python testing step extension '
                        "'{extension.STEP_TYPE}': {e}\n{exc}".format_map(
                            locals()))
                    # skip failing extension, continue with next one
                    continue
                if matched:
                    break
            else:
                logger.warning(
                    "No Python Testing Step extension matched in '{args.path}'"
                    .format_map(locals()))
                return

        logger.log(1, "test.step() by extension '{key}'".format_map(locals()))
        try:
            return await extension.step(self.context, env, setup_py_data)
        except Exception as e:  # noqa: F841
            # catch exceptions raised in python testing step extension
            exc = traceback.format_exc()
            logger.error('Exception in Python testing step extension '
                         "'{extension.STEP_TYPE}': {e}\n{exc}".format_map(
                             locals()))
            return 1
def test_pytest_match():
    extension = PytestPythonTestingStep()
    env = {}
    desc = PackageDescriptor('/dev/null')
    context = TaskContext(pkg=desc, args=None, dependencies=None)

    desc.name = 'pkg-name'
    desc.type = 'python'

    # no test requirements
    desc.metadata['get_python_setup_options'] = lambda env: {}
    assert not extension.match(context, env, get_setup_data(desc, env))

    # pytest not in tests_require
    desc.metadata['get_python_setup_options'] = lambda env: {
        'tests_require': ['nose'],
    }
    assert not extension.match(context, env, get_setup_data(desc, env))

    # pytest not in extras_require.test
    desc.metadata['get_python_setup_options'] = lambda env: {
        'extras_require': {
            'test': ['nose']
        },
    }
    assert not extension.match(context, env, get_setup_data(desc, env))

    # pytest in tests_require
    desc.metadata['get_python_setup_options'] = lambda env: {
        'tests_require': ['pytest'],
    }
    assert extension.match(context, env, get_setup_data(desc, env))

    # pytest in extras_require.test
    desc.metadata['get_python_setup_options'] = lambda env: {
        'extras_require': {
            'test': ['pytest']
        },
    }
    assert extension.match(context, env, get_setup_data(desc, env))
Example #3
0
    async def build(self, *, additional_hooks=None):  # noqa: D102
        pkg = self.context.pkg
        args = self.context.args

        logger.info(
            "Building Python package in '{args.path}'".format_map(locals()))

        try:
            env = await get_command_environment(
                'setup_py', args.build_base, self.context.dependencies)
        except RuntimeError as e:
            logger.error(str(e))
            return 1
        setup_py_data = get_setup_data(self.context.pkg, env)

        # `setup.py egg_info` requires the --egg-base to exist
        os.makedirs(args.build_base, exist_ok=True)
        # `setup.py develop|install` requires the python lib path to exist
        python_lib = os.path.join(
            args.install_base, self._get_python_lib(args))
        os.makedirs(python_lib, exist_ok=True)
        # and being in the PYTHONPATH
        env = dict(env)
        env['PYTHONPATH'] = python_lib + os.pathsep + \
            env.get('PYTHONPATH', '')

        if not args.symlink_install:
            rc = await self._undo_develop(pkg, args, env)
            if rc and rc.returncode:
                return rc.returncode

            # invoke `setup.py install` step with lots of arguments
            # to avoid placing any files in the source space
            cmd = [
                executable, 'setup.py',
                'egg_info', '--egg-base', args.build_base,
                'build', '--build-base', os.path.join(
                    args.build_base, 'build'),
                'install', '--prefix', args.install_base,
                '--record', os.path.join(args.build_base, 'install.log'),
                # prevent installation of dependencies specified in setup.py
                '--single-version-externally-managed',
            ]
            self._append_install_layout(args, cmd)
            rc = await check_call(self.context, cmd, cwd=args.path, env=env)
            if rc and rc.returncode:
                return rc.returncode

        else:
            self._undo_install(pkg, args, setup_py_data, python_lib)
            self._symlinks_in_build(args, setup_py_data)

            # invoke `setup.py develop` step in build space
            # to avoid placing any files in the source space

            # --editable causes this to skip creating/editing the
            # easy-install.pth file
            cmd = [
                executable, 'setup.py',
                'develop', '--prefix', args.install_base,
                '--editable',
                '--build-directory', os.path.join(args.build_base, 'build'),
                '--no-deps',
            ]
            if setup_py_data.get('data_files'):
                cmd += ['install_data', '--install-dir', args.install_base]
            self._append_install_layout(args, cmd)
            rc = await check_call(
                self.context, cmd, cwd=args.build_base, env=env)
            if rc and rc.returncode:
                return rc.returncode

            # explicitly add the build directory to the PYTHONPATH
            # to maintain the desired order
            if additional_hooks is None:
                additional_hooks = []
            base_path = args.build_base
            # if the Python packages are in a subdirectory
            # that needs to be appended to the build directory
            package_dir = setup_py_data.get('package_dir') or {}
            if '' in package_dir:
                base_path = os.path.join(base_path, package_dir[''])
            additional_hooks += create_environment_hook(
                'pythonpath_develop', Path(base_path), pkg.name, 'PYTHONPATH',
                base_path, mode='prepend')

        hooks = create_environment_hooks(args.install_base, pkg.name)
        create_environment_scripts(
            pkg, args, default_hooks=hooks, additional_hooks=additional_hooks)
Example #4
0
    async def build(self):  # noqa: D102
        args = self.context.args
        logger.info(
            "Building ROS package in '{args.path}' with build type "
            "'ament_python'".format_map(locals()))

        # reuse Python build task with additional logic
        extension = PythonBuildTask()
        extension.set_context(context=self.context)

        # additional hooks
        additional_hooks = create_environment_hook(
            'ament_prefix_path', Path(args.install_base),
            self.context.pkg.name, 'AMENT_PREFIX_PATH', '', mode='prepend')

        # get options from the Python manifest
        try:
            env = await get_command_environment(
                'setup_py', args.build_base, self.context.dependencies)
        except RuntimeError as e:
            logger.error(str(e))
            return 1
        setup_py_data = get_setup_data(self.context.pkg, env)

        # check if the package index and manifest are being installed
        data_files = get_data_files_mapping(
            setup_py_data.get('data_files', []))
        installs_package_index = False
        installs_package_manifest = False

        # check if package index and manifest are being installed
        for source, destination in data_files.items():
            if sys.platform == 'win32':
                destination = Path(destination).as_posix()
            # work around data files incorrectly defined as not relative
            if os.path.isabs(source):
                source = os.path.relpath(source, args.path)
            if (
                destination == 'share/ament_index/resource_index/packages/' +
                self.context.pkg.name
            ):
                installs_package_index = True
            elif (
                source == 'package.xml' and
                destination == 'share/{self.context.pkg.name}/package.xml'
                .format_map(locals())
            ):
                installs_package_manifest = True

        # warn about missing explicit installation
        # for now implicitly install the marker and the manifest
        if not installs_package_index:
            # TODO remove magic helper in the future
            logger.warn(
                "Package '{self.context.pkg.name}' doesn't explicitly install "
                'a marker in the package index (colcon-ros currently does it '
                'implicitly but that fallback will be removed in the future)'
                .format_map(locals()))
            # create package marker in ament resource index
            create_file(
                args,
                'share/ament_index/resource_index/packages/'
                '{self.context.pkg.name}'.format_map(locals()))
        if not installs_package_manifest:
            # TODO remove magic helper in the future
            logger.warn(
                "Package '{self.context.pkg.name}' doesn't explicitly install "
                "the 'package.xml' file (colcon-ros currently does it "
                'implicitly but that fallback will be removed in the future)'
                .format_map(locals()))
            # copy / symlink package manifest
            install(
                args, 'package.xml',
                'share/{self.context.pkg.name}/package.xml'
                .format_map(locals()))

        return await extension.build(additional_hooks=additional_hooks)
Example #5
0
    async def build(self, *, additional_hooks=None):  # noqa: D102
        pkg = self.context.pkg
        args = self.context.args

        logger.info(
            "Building Python package in '{args.path}'".format_map(locals()))

        try:
            env = await get_command_environment(
                'setup_py', args.build_base, self.context.dependencies)
        except RuntimeError as e:
            logger.error(str(e))
            return 1
        setup_py_data = get_setup_data(self.context.pkg, env)

        # `setup.py egg_info` requires the --egg-base to exist
        os.makedirs(args.build_base, exist_ok=True)
        # `setup.py develop|install` requires the python lib path to exist
        python_lib = os.path.join(
            args.install_base, self._get_python_lib(args))
        os.makedirs(python_lib, exist_ok=True)
        # and being in the  PYTHONPATH
        env = dict(env)
        env['PYTHONPATH'] = python_lib + os.pathsep + \
            env.get('PYTHONPATH', '')

        if not args.symlink_install:
            rc = await self._undo_develop(pkg, args, env)
            if rc and rc.returncode:
                return rc.returncode

            # invoke `setup.py install` step with lots of arguments
            # to avoid placing any files in the source space
            cmd = [
                executable, 'setup.py',
                'egg_info', '--egg-base', args.build_base,
                'build', '--build-base', os.path.join(
                    args.build_base, 'build'),
                'install', '--prefix', args.install_base,
                '--record', os.path.join(args.build_base, 'install.log'),
                # prevent installation of dependencies specified in setup.py
                '--single-version-externally-managed',
            ]
            self._append_install_layout(args, cmd)
            cmd += [
                'bdist_egg', '--dist-dir', os.path.join(
                    args.build_base, 'dist'),
            ]
            rc = await check_call(self.context, cmd, cwd=args.path, env=env)
            if rc and rc.returncode:
                return rc.returncode

        else:
            self._undo_install(pkg, args, setup_py_data, python_lib)
            self._symlinks_in_build(args, setup_py_data)

            # invoke `setup.py develop` step in build space
            # to avoid placing any files in the source space
            cmd = [
                executable, 'setup.py',
                'develop', '--prefix', args.install_base,
                '--no-deps',
            ]
            if setup_py_data.get('data_files', []):
                cmd += ['install_data', '--install-dir', args.install_base]
            self._append_install_layout(args, cmd)
            rc = await check_call(
                self.context, cmd, cwd=args.build_base, env=env)
            if rc and rc.returncode:
                return rc.returncode

        hooks = create_environment_hooks(args.install_base, pkg.name)
        create_environment_scripts(
            pkg, args, default_hooks=hooks, additional_hooks=additional_hooks)