Imported Upstream version 1.0.0 upstream/1.0.0
authorTizenOpenSource <tizenopensrc@samsung.com>
Tue, 6 Feb 2024 08:28:21 +0000 (17:28 +0900)
committerTizenOpenSource <tizenopensrc@samsung.com>
Tue, 6 Feb 2024 08:28:21 +0000 (17:28 +0900)
27 files changed:
LICENSE [new file with mode: 0644]
PKG-INFO
README.rst
setup.cfg
setup.py
src/catkin_pkg.egg-info/PKG-INFO
src/catkin_pkg.egg-info/SOURCES.txt
src/catkin_pkg.egg-info/requires.txt
src/catkin_pkg/__init__.py
src/catkin_pkg/changelog.py
src/catkin_pkg/cli/package_version.py
src/catkin_pkg/cli/prepare_release.py
src/catkin_pkg/cmake.py
src/catkin_pkg/package.py
src/catkin_pkg/package_version.py
src/catkin_pkg/packages.py
src/catkin_pkg/python_setup.py
src/catkin_pkg/topological_order.py
test/test_changelog.py
test/test_flake8.py
test/test_metapackage.py
test/test_package.py
test/test_package_version.py
test/test_packages.py
test/test_templates.py
test/test_topological_order.py
test/test_workspaces.py

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..7855782
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,31 @@
+Software License Agreement (BSD License)
+
+Copyright (c) 2012, Willow Garage, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the following
+  disclaimer in the documentation and/or other materials provided
+  with the distribution.
+* Neither the name of Willow Garage, Inc. nor the names of its
+  contributors may be used to endorse or promote products derived
+  from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
index 9eb06f9cf79d9c4174088bc25750049b69016817..d0396843310c042a2352173ad8b7fdbd1e437414 100644 (file)
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,13 +1,21 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: catkin_pkg
-Version: 0.4.23
+Version: 1.0.0
 Summary: catkin package library
 Home-page: http://wiki.ros.org/catkin_pkg
 Author: Dirk Thomas
 Author-email: dthomas@osrfoundation.org
+Maintainer: ROS Infrastructure Team
 License: BSD
-Description: Library for retrieving information about catkin packages.
+Project-URL: Source code, https://github.com/ros-infrastructure/catkin_pkg
+Project-URL: Issue tracker, https://github.com/ros-infrastructure/catkin_pkg/issues
 Keywords: catkin,ROS
 Platform: UNKNOWN
 Classifier: Programming Language :: Python
 Classifier: License :: OSI Approved :: BSD License
+Requires-Python: >=3.6
+Provides-Extra: test
+License-File: LICENSE
+
+Library for retrieving information about catkin packages.
+
index 56e7fc733d09c29aef0afa788acc0b422948cfe0..0be973f6c0162cef89d4c88ea550bbce83a9a713 100644 (file)
@@ -16,6 +16,5 @@ Code & tickets
 Continuous Integration
 ----------------------
 
-+--------------------------------------------------------------------------+--------------------------------------------------------------------+
-| `Build Status <https://travis-ci.org/ros-infrastructure/catkin_pkg>`_.   | .. image:: https://travis-ci.org/ros-infrastructure/catkin_pkg.png |
-+--------------------------------------------------------------------------+--------------------------------------------------------------------+
+.. image:: https://github.com/ros-infrastructure/catkin_pkg/actions/workflows/ci.yaml/badge.svg?branch=master&event=push
+   :target: https://github.com/ros-infrastructure/catkin_pkg/actions/workflows/ci.yaml?query=branch%3Amaster+event%3Apush
index 8bfd5a12f85b8fbb6c058cf67dd23da690835ea0..bd2d0722eca32192db5787c6308b827eb472c0ab 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,20 @@
+[flake8]
+extend-ignore = C403,C404,C405,D100,D101,D102,D103,D104,D105,D106,D107,D203,D212,D404,I202,W605
+import-order-style = google
+max-complexity = 54
+max-line-length = 200
+show-source = true
+statistics = true
+
+[tool:pytest]
+junit_suite_name = catkin_pkg
+markers = 
+       flake8
+       linter
+
+[coverage:run]
+source = catkin_pkg
+
 [egg_info]
 tag_build = 
 tag_date = 0
index 3d9a55359f4183164e00fc7a00567b4547221a50..23368e4d22e6a59d7870390e0e198569f1945a25 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -1,26 +1,16 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import os
-import sys
 
 from setuptools import setup
 
-install_requires = [
-    'docutils',
-    'python-dateutil',
-    'pyparsing',
-]
-
-# argparse is part of the standard library since Python 2.7
-if sys.version_info[0] == 2 and sys.version_info[1] < 7:
-    install_requires.append('argparse')
 
 kwargs = {
     'name': 'catkin_pkg',
     # same version as in:
     # - src/catkin_pkg/__init__.py
     # - stdeb.cfg
-    'version': '0.4.23',
+    'version': '1.0.0',
     'packages': ['catkin_pkg', 'catkin_pkg.cli'],
     'package_dir': {'': 'src'},
     'package_data': {'catkin_pkg': ['templates/*.in']},
@@ -36,16 +26,42 @@ kwargs = {
         ]},
     'author': 'Dirk Thomas',
     'author_email': 'dthomas@osrfoundation.org',
+    'maintainer': 'ROS Infrastructure Team',
+    'project_urls': {
+        'Source code':
+        'https://github.com/ros-infrastructure/catkin_pkg',
+        'Issue tracker':
+        'https://github.com/ros-infrastructure/catkin_pkg/issues',
+    },
     'url': 'http://wiki.ros.org/catkin_pkg',
     'keywords': ['catkin', 'ROS'],
     'classifiers': [
         'Programming Language :: Python',
         'License :: OSI Approved :: BSD License'
     ],
+    'python_requires': '>=3.6',
     'description': 'catkin package library',
     'long_description': 'Library for retrieving information about catkin packages.',
     'license': 'BSD',
-    'install_requires': install_requires,
+    'install_requires': [
+        'docutils',
+        'python-dateutil',
+        'pyparsing',
+        'setuptools',
+    ],
+    'extras_require': {
+        'test': [
+            'flake8',
+            'flake8-blind-except',
+            'flake8-builtins',
+            'flake8-class-newline',
+            'flake8-comprehensions',
+            'flake8-deprecated',
+            'flake8-docstrings',
+            'flake8-import-order',
+            'flake8-quotes',
+            'pytest',
+        ]},
 }
 if 'SKIP_PYTHON_MODULES' in os.environ:
     kwargs['packages'] = []
index 3663e1841b4d864540df2e531365f70eace73f9f..7000e2a2e69e9c43c5de1254db7b40f290b95324 100644 (file)
@@ -1,13 +1,21 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: catkin-pkg
-Version: 0.4.23
+Version: 1.0.0
 Summary: catkin package library
 Home-page: http://wiki.ros.org/catkin_pkg
 Author: Dirk Thomas
 Author-email: dthomas@osrfoundation.org
+Maintainer: ROS Infrastructure Team
 License: BSD
-Description: Library for retrieving information about catkin packages.
+Project-URL: Source code, https://github.com/ros-infrastructure/catkin_pkg
+Project-URL: Issue tracker, https://github.com/ros-infrastructure/catkin_pkg/issues
 Keywords: catkin,ROS
 Platform: UNKNOWN
 Classifier: Programming Language :: Python
 Classifier: License :: OSI Approved :: BSD License
+Requires-Python: >=3.6
+Provides-Extra: test
+License-File: LICENSE
+
+Library for retrieving information about catkin packages.
+
index 5c48cf1db56866422ab546ceee892f584311ae58..9578505d7ad85c1a397e3ad1e1c05c90cd867df7 100644 (file)
@@ -1,4 +1,6 @@
+LICENSE
 README.rst
+setup.cfg
 setup.py
 src/catkin_pkg/__init__.py
 src/catkin_pkg/changelog.py
index 03407709829f0caa45a9718d6c8cc5ebbbaa3792..8f326a34e5274c30bfadf647d26f1aca674e5993 100644 (file)
@@ -1,3 +1,16 @@
 docutils
-pyparsing
 python-dateutil
+pyparsing
+setuptools
+
+[test]
+flake8
+flake8-blind-except
+flake8-builtins
+flake8-class-newline
+flake8-comprehensions
+flake8-deprecated
+flake8-docstrings
+flake8-import-order
+flake8-quotes
+pytest
index f1ba20986ddf1bdadf6c501dde3981cccb077a05..39940e5add06282ad78b27057665b4f26636fd20 100644 (file)
@@ -35,4 +35,4 @@
 # same version as in:
 # - setup.py
 # - stdeb.cfg
-__version__ = '0.4.23'
+__version__ = '1.0.0'
index 9198f889b0eccf71d509cf7c2e70f2992f5f6236..c3f030408433505a9cc39c5609d1bebbf23f6bef 100644 (file)
@@ -231,9 +231,9 @@ def processes_changelog_children(changelog, children):
         elif isinstance(child, docutils.nodes.title) or isinstance(child, docutils.nodes.subtitle):
             version, date = None, None
             # See if the title has a text element in it
-            if len(child.children) > 0 and isinstance(child.children[0], docutils.nodes.Text):
+            if len(child.children) > 0 and any(isinstance(c, docutils.nodes.Text) for c in child.traverse()):
                 # Extract version and date from (sub-)title
-                title_text = child.children[0].rawsource
+                title_text = child.astext()
                 try:
                     version, date = version_and_date_from_title(title_text)
                 except InvalidSectionTitle:
index f5556cae9e780dc931c71695fda573c3840942de..62eee185c49095d43ef73df5ae28d6ff6b343e8d 100644 (file)
@@ -33,7 +33,7 @@ def main():
         else:
             # bump the version number
             new_version = bump_version(version, args.bump)
-            update_versions(packages.keys(), new_version)
+            update_versions(packages, new_version)
             print('%s -> %s' % (version, new_version))
-    except Exception as e:
+    except Exception as e:  # noqa: B902
         sys.exit(str(e))
index dbf154913ec0e63b3bdd5fcfb4be2429d0572ef3..9171adba7750086848c7ebefbf1298c7c108656a 100644 (file)
@@ -133,6 +133,7 @@ def check_clean_working_copy(base_path, vcs_type):
 def commit_files(base_path, vcs_type, packages, packages_with_changelogs, message, dry_run=False):
     cmd = [_find_executable(vcs_type), 'commit', '-m', message]
     cmd += [os.path.join(p, PACKAGE_MANIFEST_FILENAME) for p in packages.keys()]
+    cmd += [s for s in [os.path.join(p, 'setup.py') for p in packages.keys()] if os.path.exists(s)]
     cmd += [path for path, _, _ in packages_with_changelogs.values()]
     if not dry_run:
         try:
@@ -266,18 +267,18 @@ def _main():
     # complain about packages with upper case character since they won't be releasable with bloom
     unsupported_pkg_names = []
     invalid_pkg_names = []
+    valid_build_types = ['catkin', 'ament_cmake', 'ament_python']
     for package in packages.values():
-        build_types = [export.content for export in package.exports if export.tagname == 'build_type']
-        build_type = build_types[0] if build_types else 'catkin'
-        if build_type not in ('catkin', 'ament_cmake'):
+        build_types = package.get_unconditional_build_types()
+        if any(build_type not in valid_build_types for build_type in build_types):
             unsupported_pkg_names.append(package.name)
         if package.name != package.name.lower():
             invalid_pkg_names.append(package.name)
     if unsupported_pkg_names:
         print(
             fmt(
-                "@{yf}Warning: the following package are not of build_type catkin or ament_cmake and may require manual steps to release': %s" %
-                ', '.join([('@{boldon}%s@{boldoff}' % p) for p in sorted(unsupported_pkg_names)])
+                "@{yf}Warning: the following package are not of build_type %s and may require manual steps to release': %s" %
+                (str(valid_build_types), ', '.join([('@{boldon}%s@{boldoff}' % p) for p in sorted(unsupported_pkg_names)]))
             ), file=sys.stderr)
         if not args.non_interactive and not prompt_continue('Continue anyway', default=False):
             raise RuntimeError(fmt('@{rf}Aborted release, verify that unsupported packages are ready to be released or release manually.'))
@@ -304,6 +305,10 @@ def _main():
                 raise RuntimeError(fmt(
                     "@{rf}Invalid metapackage at path '@{boldon}%s@{boldoff}':\n  %s\n\nSee requirements for metapackages: %s" %
                     (os.path.abspath(pkg_path), str(e), metapackage.DEFINITION_URL)))
+        # verify that the setup.py files don't have modifications pending
+        setup_py_path = os.path.join(pkg_path, 'setup.py')
+        if os.path.exists(setup_py_path) and has_changes(base_path, setup_py_path, vcs_type):
+            local_modifications.append(setup_py_path)
 
     # fetch current version and verify that all packages have same version number
     old_version = verify_equal_package_versions(packages.values())
@@ -380,7 +385,7 @@ def _main():
         (new_version, ', '.join([('@{boldon}%s@{boldoff}' % p) for p in sorted(missing_changelogs_but_forthcoming.keys())]))))
 
     # bump version number
-    update_versions(packages.keys(), new_version)
+    update_versions(packages, new_version)
     print(fmt("@{gf}Bump version@{reset} of all packages from '@{bf}%s@{reset}' to '@{bf}@{boldon}%s@{boldoff}@{reset}'" % (old_version, new_version)))
 
     pushed = None
index 05be78c5466ed5f3ab781fcbda554f8f6a5ec4b2..11aaf2815c10414912eb51c85f108a1e3555d3f7 100644 (file)
@@ -47,7 +47,7 @@ def get_metapackage_cmake_template_path():
     return os.path.join(os.path.dirname(__file__), rel_path)
 
 
-def configure_file(template_file, environment):  # noqa: D402
+def configure_file(template_file, environment):
     """
     Evaluate a .in template file used in CMake with configure_file().
 
@@ -57,7 +57,7 @@ def configure_file(template_file, environment):  # noqa: D402
     :returns: string with evaluates template
     :raises: KeyError for placeholders in the template which are not
       in the environment
-    """
+    """  # noqa: D402
     with open(template_file, 'r') as f:
         template = f.read()
         return configure_string(template, environment)
index bdc88a3ba99be4d61befba187ac20ee666dfc6ed..2843c697cb6b7358db0fee219c00009d174efa01 100644 (file)
@@ -39,6 +39,7 @@ import os
 import re
 import sys
 import xml.dom.minidom as dom
+from xml.parsers.expat import ExpatError
 
 from catkin_pkg.condition import evaluate_condition
 
@@ -59,6 +60,7 @@ class Package(object):
         'version',
         'version_compatibility',
         'description',
+        'plaintext_description',
         'maintainers',
         'licenses',
         'urls',
@@ -164,6 +166,18 @@ class Package(object):
             return build_type_exports[0]
         raise InvalidPackage('Only one <build_type> element is permitted.', self.filename)
 
+    def get_unconditional_build_types(self):
+        """
+        Return values of export/build_type elements without conditional filtering, or ['catkin'] if unspecified.
+
+        :returns: package build types
+        :rtype: List[str]
+        """
+        build_type_exports = [e.content for e in self.exports if e.tagname == 'build_type']
+        if not build_type_exports:
+            return ['catkin']
+        return build_type_exports
+
     def has_invalid_metapackage_dependencies(self):
         """
         Return True if this package has invalid dependencies for a metapackage.
@@ -539,7 +553,7 @@ def has_ros_schema_reference_string(data):
         data = data.encode('utf-8')
     try:
         root = dom.parseString(data)
-    except Exception:
+    except ExpatError:
         # invalid XML
         return False
 
@@ -594,7 +608,7 @@ def parse_package_string(data, filename=None, warnings=None):
         data = data.encode('utf-8')
     try:
         root = dom.parseString(data)
-    except Exception as ex:
+    except ExpatError as ex:
         raise InvalidPackage('The manifest contains invalid XML:\n%s' % ex, filename)
 
     pkg = Package(filename)
@@ -623,6 +637,7 @@ def parse_package_string(data, filename=None, warnings=None):
 
     # description
     pkg.description = _get_node_value(_get_node(root, 'description', filename), allow_xml=True, apply_str=False)
+    pkg.plaintext_description = re.sub(' +(\n+) +', r'\1', _get_node_text(_get_node(root, 'description', filename)), flags=re.MULTILINE)
 
     # at least one maintainer, all must have email
     maintainers = _get_nodes(root, 'maintainer')
@@ -803,6 +818,23 @@ def _get_node_value(node, allow_xml=False, apply_str=True):
     return value
 
 
+def _get_node_text(node, strip=True):
+    value = ''
+    for child in node.childNodes:
+        if child.nodeType == child.TEXT_NODE:
+            value += re.sub(r'\s+', ' ', child.data)
+        elif child.nodeType == child.ELEMENT_NODE:
+            if child.tagName == 'br':
+                value += '\n'
+            else:
+                value += _get_node_text(child, strip=False)
+        else:
+            assert 'unreachable'
+    if strip:
+        value = value.strip()
+    return value
+
+
 def _get_node_attr(node, attr, default=False):
     """:param default: False means value is required."""
     if node.hasAttribute(attr):
index 0368a73ad07f8f742e575ff8c293d636b4ac591f..f7d204e3a579af9f46decfc6effd5a4166c6bc42 100644 (file)
@@ -84,6 +84,28 @@ def _replace_version(package_str, new_version):
     return new_package_str
 
 
+def _replace_setup_py_version(setup_py_str, new_version):
+    """
+    Replace the version tag in contents if there is only one instance and it is using a literal as the version.
+
+    :param str package_str: contents of setup.py
+    :param str new_version: new version number
+    :returns: new setup.py string
+    :rtype: str
+    :raises RuntimeError:
+    """
+    # try to replace contents
+    new_setup_py_str, number_of_subs = re.subn(
+        r'version=([\'"])\d+\.\d+\.\d+([\'"]),',
+        r'version=\g<1>%s\g<2>,' % new_version,
+        setup_py_str)
+    if number_of_subs == 0:
+        raise RuntimeError("Failed to find a normal version statement, e.g.: version='1.2.3',")
+    if number_of_subs != 1:
+        raise RuntimeError('Illegal number of version statements: %s' % (number_of_subs))
+    return new_setup_py_str
+
+
 def _check_for_version_comment(package_str, new_version):
     """
     Check if a comment is present behind the version tag and return it.
@@ -101,16 +123,17 @@ def _check_for_version_comment(package_str, new_version):
     return comment
 
 
-def update_versions(paths, new_version):
+def update_versions(packages, new_version):
     """
-    Bulk replace of version: searches for package.xml files directly in given folders and replaces version tag within.
+    Bulk replace of version: searches for package.xml and setup.py files directly in given folders and replaces version tag within.
 
-    :param list paths: folder names
+    :param dict packages: dict from folder names to package xml objects in those folders
     :param str new_version: version string "int.int.int"
     :raises RuntimeError: if any one package.xml cannot be updated
     """
     files = {}
-    for path in paths:
+    for path, package_obj in packages.items():
+        # Update any package.xml files.
         package_path = os.path.join(path, 'package.xml')
         with open(package_path, 'r') as f:
             package_str = f.read()
@@ -122,6 +145,20 @@ def update_versions(paths, new_version):
         except RuntimeError as rue:
             raise RuntimeError('Could not bump version number in file %s: %s' % (package_path, str(rue)))
         files[package_path] = new_package_str
+        # Update any setup.py files.
+        setup_py_path = os.path.join(path, 'setup.py')
+        if os.path.exists(setup_py_path):
+            # Only update setup.py for ament_python packages.
+            build_types = package_obj.get_unconditional_build_types()
+            if 'ament_python' in build_types:
+                with open(setup_py_path, 'r') as f:
+                    setup_py_str = f.read()
+                try:
+                    new_setup_py_str = _replace_setup_py_version(setup_py_str, new_version)
+                except RuntimeError as exc:
+                    raise RuntimeError('Could not bump version number in file %s: %s' % (setup_py_path, str(exc)))
+                files[setup_py_path] = new_setup_py_str
+
     # if all replacements successful, write back modified package.xml
     for package_path, new_package_str in files.items():
         with open(package_path, 'w') as f:
@@ -140,7 +177,7 @@ def get_forthcoming_label(rst):
             if len(section.children) > 0 and isinstance(section.children[0], docutils.nodes.title):
                 title = section.children[0]
         if title and len(title.children) > 0 and isinstance(title.children[0], docutils.nodes.Text):
-            title_text = title.children[0].rawsource
+            title_text = title.children[0].astext()
             if FORTHCOMING_LABEL.lower() in title_text.lower():
                 if forthcoming_label:
                     raise RuntimeError('Found multiple forthcoming sections')
index aa7208b01e351b6464cc9bc09cf3383b5e1343c2..36b514494cf31ed362859a75ae7540b32732e81f 100644 (file)
@@ -40,11 +40,14 @@ from .package import PACKAGE_MANIFEST_FILENAME
 from .package import parse_package_string
 
 
-def find_package_paths(basepath, exclude_paths=None, exclude_subspaces=False):
+DEFAULT_IGNORE_MARKERS = {'AMENT_IGNORE', 'CATKIN_IGNORE', 'COLCON_IGNORE'}
+
+
+def find_package_paths(basepath, exclude_paths=None, exclude_subspaces=False, ignore_markers=DEFAULT_IGNORE_MARKERS):
     """
     Crawls the filesystem to find package manifest files.
 
-    When a subfolder contains either of the following files it is ignored:
+    When a subfolder contains either of the files mentioned in ``ignore_markers`` it is ignored. By default, these are:
     - ``AMENT_IGNORE``
     - ``CATKIN_IGNORE``
     - ``COLCON_IGNORE``
@@ -53,12 +56,13 @@ def find_package_paths(basepath, exclude_paths=None, exclude_subspaces=False):
     :param exclude_paths: A list of paths which should not be searched, ``list``
     :param exclude_subspaces: The flag is subfolders containing a .catkin file should not be
         searched, ``bool``
+    :param ignore_markers: Names of files that indicate that a folder should be ignored, ``set``
     :returns: A list of relative paths containing package manifest files ``list``
     """
     paths = []
     real_exclude_paths = [os.path.realpath(p) for p in exclude_paths] if exclude_paths is not None else []
     for dirpath, dirnames, filenames in os.walk(basepath, followlinks=True):
-        if set(dirnames + filenames) & {'AMENT_IGNORE', 'CATKIN_IGNORE', 'COLCON_IGNORE'} or \
+        if set(dirnames + filenames) & ignore_markers or \
             os.path.realpath(dirpath) in real_exclude_paths or \
                 (exclude_subspaces and '.catkin' in filenames):
             del dirnames[:]
@@ -72,7 +76,7 @@ def find_package_paths(basepath, exclude_paths=None, exclude_subspaces=False):
     return paths
 
 
-def find_packages(basepath, exclude_paths=None, exclude_subspaces=False, warnings=None):
+def find_packages(basepath, exclude_paths=None, exclude_subspaces=False, warnings=None, ignore_markers=DEFAULT_IGNORE_MARKERS):
     """
     Crawls the filesystem to find package manifest files and parses them.
 
@@ -80,11 +84,14 @@ def find_packages(basepath, exclude_paths=None, exclude_subspaces=False, warning
     :param exclude_paths: A list of paths which should not be searched, ``list``
     :param exclude_subspaces: The flag is subfolders containing a .catkin file should not be
         searched, ``bool``
-    :param warnings: Print warnings if None or return them in the given list
+    :param warnings: Print warnings if None or return them in the given list, ``bool``
+    :param ignore_markers: Names of files that indicate that a folder should be ignored, ``set``
     :returns: A dict mapping relative paths to ``Package`` objects ``dict``
     :raises: :exc:RuntimeError` If multiple packages have the same name
     """
-    packages = find_packages_allowing_duplicates(basepath, exclude_paths=exclude_paths, exclude_subspaces=exclude_subspaces, warnings=warnings)
+    packages = find_packages_allowing_duplicates(basepath, exclude_paths=exclude_paths,
+                                                 exclude_subspaces=exclude_subspaces, warnings=warnings,
+                                                 ignore_markers=ignore_markers)
     package_paths_by_name = {}
     for path, package in packages.items():
         if package.name not in package_paths_by_name:
@@ -109,7 +116,7 @@ class _PackageParser(object):
         return (path, parsed_package), warnings
 
 
-def find_packages_allowing_duplicates(basepath, exclude_paths=None, exclude_subspaces=False, warnings=None):
+def find_packages_allowing_duplicates(basepath, exclude_paths=None, exclude_subspaces=False, warnings=None, ignore_markers=DEFAULT_IGNORE_MARKERS):
     """
     Crawls the filesystem to find package manifest files and parses them.
 
@@ -118,9 +125,10 @@ def find_packages_allowing_duplicates(basepath, exclude_paths=None, exclude_subs
     :param exclude_subspaces: The flag is subfolders containing a .catkin file should not be
         searched, ``bool``
     :param warnings: Print warnings if None or return them in the given list
+    :param ignore_markers: Names of files that indicate that a folder should be ignored, ``set``
     :returns: A dict mapping relative paths to ``Package`` objects ``dict``
     """
-    package_paths = find_package_paths(basepath, exclude_paths=exclude_paths, exclude_subspaces=exclude_subspaces)
+    package_paths = find_package_paths(basepath, exclude_paths=exclude_paths, exclude_subspaces=exclude_subspaces, ignore_markers=ignore_markers)
 
     xmls = {}
     for path in package_paths:
index c18ded8c86450dede36a6fffd19049c908ce656b..84bd64d42bec71e0f5e650958011a7a129d07447 100644 (file)
@@ -57,8 +57,9 @@ def generate_distutils_setup(package_xml_path=os.path.curdir, **kwargs):
     the "url" field.
 
     The "description" is taken from the eponymous tag if it does not
-    exceed 200 characters. If it does "description" contains the
-    truncated text while "description_long" contains the complete.
+    exceed 200 characters and has no newlines. If it does "description"
+    contains the truncated text while "long_description" contains the
+    complete.
 
     All licenses are merged into the "license" field.
 
@@ -98,11 +99,13 @@ def generate_distutils_setup(package_xml_path=os.path.curdir, **kwargs):
     elif package.urls:
         data['url'] = package.urls[0].url
 
-    if len(package.description) <= 200:
-        data['description'] = package.description
-    else:
-        data['description'] = package.description[:197] + '...'
-        data['long_description'] = package.description
+    description = package.plaintext_description.splitlines()[0]
+    if len(description) > 200:
+        description = description[:197] + '...'
+
+    data['description'] = description
+    if description != package.plaintext_description:
+        data['long_description'] = package.plaintext_description
 
     data['license'] = ', '.join(package.licenses)
 
index 2bb396a637f01aeebf89a37021ae04d25a4a3833..b9b003ebb5a284721c26d8b544ec57d87f47160a 100644 (file)
@@ -222,7 +222,7 @@ def _reduce_cycle_set(packages_orig):
     :param packages: A dict mapping package name to ``_PackageDecorator`` objects ``dict``
     :returns: A list of package names from the input which could not easily be detected as not being part of a cycle.
     """
-    assert(packages_orig)
+    assert packages_orig
     packages = copy.copy(packages_orig)
     last_depended = None
     while len(packages) > 0:
index 047aff933ab53c7946a1e44f2b2b44b6f8e9a6ab..673d11eba6535d9055c3afe4a2af6fca0a9210e1 100644 (file)
@@ -31,30 +31,30 @@ class TestSectionTitleParsing(unittest.TestCase):
 
 def check_0_1_26(content):
     assert len(content) == 1
-    assert type(content[0]) == BulletList
+    assert type(content[0]) is BulletList
     assert len(content[0].bullets) == 3
 
 
 def check_0_1_25(content):
     assert len(content) == 3
-    assert type(content[0]) == BulletList
+    assert type(content[0]) is BulletList
     assert len(content[0].bullets) == 5
     mtext = content[0].bullets[3]
-    assert type(mtext) == MixedText
+    assert type(mtext) is MixedText
     assert len(mtext.texts) == 2
-    assert type(content[1]) == Transition
-    assert type(content[2]) == MixedText
+    assert type(content[1]) is Transition
+    assert type(content[2]) is MixedText
 
 
 def check_0_1_0(content):
     assert len(content) == 1
-    assert type(content[0]) == MixedText
+    assert type(content[0]) is MixedText
     assert len(content[0].texts) == 4
 
 
 def check_0_0_1(content):
     assert len(content) == 1
-    assert type(content[0]) == BulletList
+    assert type(content[0]) is BulletList
     assert content[0].bullet_type == 'enumerated'
 
 
index 3c5e5bfc85a012bdcd2cc5bbd5d57064ad4048e5..2390a373a5f15bc3a10af254ad5209987bbcb206 100644 (file)
@@ -18,8 +18,11 @@ import os
 import sys
 
 from flake8.api.legacy import get_style_guide
+import pytest
 
 
+@pytest.mark.flake8
+@pytest.mark.linter
 def test_flake8():
     # Configure flake8 using the .flake8 file in the root of this repository.
     style_guide = get_style_guide()
index e5b4ff631d4d92c4526bd565534805dd47409976..c2408266adec86b63a1aef3c49454353dcbfb0bc 100644 (file)
@@ -61,14 +61,6 @@ def assert_warning(warnreg):
         sys.stderr = orig_stderr
 
 
-def _validate_metapackage(path, package):
-    try:
-        validate_metapackage(path, package)
-    except Exception:
-        # print('on package ' + package.name, file=sys.stderr)
-        raise
-
-
 class TestMetapackageValidation(unittest.TestCase):
     """Tests the metapackage validator."""
 
@@ -85,12 +77,12 @@ class TestMetapackageValidation(unittest.TestCase):
                 if exc is not None:
                     if excreg is not None:
                         with self.assertRaisesRegex(exc, excreg):
-                            _validate_metapackage(path, package)
+                            validate_metapackage(path, package)
                     else:
                         with self.assertRaises(exc):
-                            _validate_metapackage(path, package)
+                            validate_metapackage(path, package)
                 else:
-                    _validate_metapackage(path, package)
+                    validate_metapackage(path, package)
 
     def test_collect_warnings(self):
         """Tests warnings collection."""
index b988a9f3fa4e604db4ae76cfa764073c497d9ae1..12d8ffdc23a30d074ed536925eec1526b0cf1b1e 100644 (file)
@@ -2,8 +2,10 @@ import os.path
 # Redirect stderr to stdout to suppress output in tests
 import sys
 import unittest
+from unittest.mock import Mock
 
 import xml.dom.minidom as dom
+from xml.parsers.expat import ExpatError
 
 from catkin_pkg.package import (
     _check_known_attributes,
@@ -19,8 +21,6 @@ from catkin_pkg.package import (
     Person,
 )
 
-from mock import Mock
-
 sys.stderr = sys.stdout
 
 test_data_dir = os.path.join(os.path.dirname(__file__), 'data', 'package')
@@ -337,14 +337,14 @@ class PackageTest(unittest.TestCase):
 
         try:
             create_node('tag', {'key': 'value'})
-        except Exception as e:
+        except ExpatError as e:
             self.fail('create_node() raised %s "%s" unexpectedly!' % (type(e), str(e)))
 
         self.assertRaisesRegex(Exception, 'unbound prefix: line 1, column 0', create_node, 'tag', {'ns:key': 'value'})
 
         try:
             create_node('tag', {'ns:key': 'value', 'xmlns:ns': 'urn:ns'})
-        except Exception as e:
+        except ExpatError as e:
             self.fail('create_node() raised %s "%s" unexpectedly!' % (type(e), str(e)))
 
         def check(attrs, known, res=[]):
@@ -377,6 +377,17 @@ class PackageTest(unittest.TestCase):
         filename = os.path.join(test_data_dir, 'invalid_package.xml')
         self.assertRaises(InvalidPackage, parse_package, filename)
 
+    def test_parse_package_xhtml_description(self):
+        filename = os.path.join(test_data_dir, 'xhtml_description.xml')
+        expected_plaintext_description = None
+        with open(os.path.join(test_data_dir, 'xhtml_description.txt'), 'r') as f:
+            # Strip the trailing newline from the data file.
+            expected_plaintext_description = f.read().rstrip('\n')
+        package = parse_package(filename)
+        assert package.description
+
+        assert package.plaintext_description == expected_plaintext_description
+
     def test_parse_package_string(self):
         filename = os.path.join(test_data_dir, 'valid_package.xml')
         xml = _get_package_xml(filename)[0]
@@ -392,6 +403,38 @@ class PackageTest(unittest.TestCase):
             assert isinstance(xml, bytes)
         parse_package_string(xml)
 
+        xml_string = """
+<package>
+  <name>valid_package</name>
+  <version>0.1.0</version>
+  <description>valid_package description</description>
+  <maintainer email="user@todo.todo>Forgotten end quote</maintainer>
+  <license>BSD</license>
+</package>
+"""
+        self.assertRaises(InvalidPackage, parse_package_string, xml_string)
+
+        xml_string = """
+<package>
+  <name>valid_package</name>
+  <version>0.1.0</version>
+  <description>Invalid < character in description</description>
+  <maintainer email="user@todo.todo">user</maintainer>
+  <license>BSD</license>
+</package>
+"""
+        self.assertRaises(InvalidPackage, parse_package_string, xml_string)
+        xml_string = """
+<package>
+  <name>valid_package</name>
+  <version>0.1.0</version>
+  <description>valid_package description</description>
+  <maintainer email="user@todo.todo">user</maintainer>
+  <license>BSD</license>
+</package><extra>Unwanted junk</extra>
+"""
+        self.assertRaises(InvalidPackage, parse_package_string, xml_string)
+
     def test_has_ros_schema_reference_string(self):
         self.assertFalse(
             has_ros_schema_reference_string(
index 6265347a2b4d382a0f4d8f40cf05a609740acba7..dddc99510234f629eba0aa026c239a6954216ee9 100644 (file)
@@ -3,14 +3,14 @@ import os
 import shutil
 import tempfile
 import unittest
+from unittest.mock import Mock
 
+from catkin_pkg.package_version import _replace_setup_py_version
 from catkin_pkg.package_version import _replace_version
 from catkin_pkg.package_version import bump_version
 from catkin_pkg.package_version import update_changelog_sections
 from catkin_pkg.package_version import update_versions
 
-import mock
-
 from .util import in_temporary_directory
 
 
@@ -42,6 +42,34 @@ class PackageVersionTest(unittest.TestCase):
         self.assertRaises(RuntimeError, _replace_version, '<package></package>', '0.1.1')
         self.assertRaises(RuntimeError, _replace_version, '<package><version>0.1.1</version><version>0.1.1</version></package>', '0.1.1')
 
+    def test_replace_setup_py_version(self):
+        self.assertEqual(
+            'version="1.0.0",',
+            _replace_setup_py_version('version="0.0.1",', '1.0.0'))
+        self.assertEqual(
+            "version='1.0.0',",
+            _replace_setup_py_version("version='0.0.1',", '1.0.0'))
+        self.assertRaises(
+            RuntimeError,
+            _replace_setup_py_version,
+            '',
+            '1.0.0')
+        self.assertRaises(
+            RuntimeError,
+            _replace_setup_py_version,
+            "version='0.1'",
+            '1.0.0')
+        self.assertRaises(
+            RuntimeError,
+            _replace_setup_py_version,
+            'version=something,',
+            '1.0.0')
+        self.assertRaises(
+            RuntimeError,
+            _replace_setup_py_version,
+            'version="0.0.1",version="0.0.1",',
+            '1.0.0')
+
     def test_update_versions(self):
         try:
             root_dir = tempfile.mkdtemp()
@@ -52,7 +80,7 @@ class PackageVersionTest(unittest.TestCase):
             with open(os.path.join(sub_dir, 'package.xml'), 'w') as fhand:
                 fhand.write('<package><version>1.5.4</version></package>')
 
-            update_versions([root_dir, sub_dir], '7.6.5')
+            update_versions({root_dir: Mock(), sub_dir: Mock()}, '7.6.5')
 
             with open(os.path.join(root_dir, 'package.xml'), 'r') as fhand:
                 contents = fhand.read()
@@ -69,7 +97,7 @@ class PackageVersionTest(unittest.TestCase):
         temp_file = os.path.join(directory, 'changelog')
         missing_changelogs_but_forthcoming = {}
         # Mock the Changelog object from catkin_pkg
-        mock_changelog = mock.Mock()
+        mock_changelog = Mock()
         # Create a changelog entry with a unicode char.
         mock_changelog.rst = ('^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
                               'Changelog for package fake_pkg\n'
index fdae79949169dabb4069c26066609eed6ce73392..72761d9426900b3ec29ee46f01966e7b63f7d6ab 100644 (file)
@@ -8,6 +8,9 @@ from catkin_pkg.packages import find_packages_allowing_duplicates
 from .util import in_temporary_directory
 
 
+test_data_dir = os.path.join(os.path.dirname(__file__), 'data', 'ignored_packages')
+
+
 def _create_pkg_in_dir(path, version='0.1.0'):
     path = os.path.abspath(path)
     os.makedirs(path)
@@ -58,3 +61,15 @@ def test_find_packages_invalid_version():
         exception_message = str(e)
         assert version in exception_message
         assert path in exception_message
+
+
+def test_find_no_ignored_packages():
+    result = find_packages(test_data_dir)
+    assert 'catkin_ignore' not in result
+    assert 'custom_ignore' in result
+
+
+def test_find_no_ignored_packages_with_custom_ignore():
+    custom_result = find_packages(test_data_dir, ignore_markers={'CUSTOM_IGNORE'})
+    assert 'custom_ignore' not in custom_result
+    assert 'catkin_ignore' in custom_result
index 561ec288687ef8224c8b4a5f2d73676c17d1bc91..a01806b0d1f0a0e0970af808c2de81f5820b5e0d 100644 (file)
@@ -2,14 +2,13 @@ import os
 import shutil
 import tempfile
 import unittest
+from unittest.mock import MagicMock, Mock
 
 from catkin_pkg.package import Dependency, Export, PACKAGE_MANIFEST_FILENAME, parse_package, Url
 from catkin_pkg.package_templates import _create_include_macro, _create_targetlib_args, _safe_write_files, \
     create_cmakelists, create_package_files, create_package_xml, PackageTemplate
 from catkin_pkg.python_setup import generate_distutils_setup
 
-from mock import MagicMock, Mock
-
 
 def u(line):
     try:
index 651cb6bbdfda97f7ed6f01c63d8a35d5c05718a0..3b3e3bcb56d003d47eb51abaaf5716983ddebfac 100644 (file)
@@ -2,8 +2,7 @@ from __future__ import print_function
 
 import sys
 import unittest
-
-from mock import Mock
+from unittest.mock import Mock
 
 try:
     from catkin_pkg.topological_order import topological_order_packages, _PackageDecorator, \
index 9a6f9e4b503544b2786471789f15f8ca325d65e3..be5a76adbce29c81ae195eddd6ff86abbca9e285 100644 (file)
@@ -6,7 +6,7 @@ import tempfile
 import unittest
 
 try:
-    from catkin_pkg.workspaces import ensure_workspace_marker, get_spaces, order_paths,\
+    from catkin_pkg.workspaces import ensure_workspace_marker, get_spaces, order_paths, \
         CATKIN_WORKSPACE_MARKER_FILE
 except ImportError as e:
     raise ImportError('Please adjust your PYTHONPATH before running this test: %s' % str(e))