pax_global_header00006660000000000000000000000064131761160600014513gustar00rootroot0000000000000052 comment=a48fc89e5c372e68658ede59a319f1164de7201a catkin_pkg-0.3.9/000077500000000000000000000000001317611606000136365ustar00rootroot00000000000000catkin_pkg-0.3.9/.gitignore000066400000000000000000000001051317611606000156220ustar00rootroot00000000000000*.pyc build bin/catkin_create_pkgc deb_dist doc/_build dist MANIFEST catkin_pkg-0.3.9/.travis.yml000066400000000000000000000004261317611606000157510ustar00rootroot00000000000000language: python dist: trusty sudo: false python: - "2.7" - "3.2" - "3.3" # command to install dependencies install: - pip install nose coverage argparse python-dateutil docutils # command to run tests script: - nosetests -s --tests test notifications: email: false catkin_pkg-0.3.9/LICENSE000066400000000000000000000030011317611606000146350ustar00rootroot00000000000000Software 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. catkin_pkg-0.3.9/Makefile000066400000000000000000000007501317611606000153000ustar00rootroot00000000000000.PHONY: all setup clean_dist distro clean install testsetup test NAME=catkin_pkg VERSION=`./setup.py --version` all: echo "noop for debbuild" setup: echo "building version ${VERSION}" clean_dist: -rm -f MANIFEST -rm -rf dist -rm -rf deb_dist distro: setup clean_dist python setup.py sdist clean: clean_dist echo "clean" install: distro sudo checkinstall python setup.py install testsetup: echo "running ${NAME} tests" test: testsetup cd test && nosetests && nosetests3 catkin_pkg-0.3.9/README.rst000066400000000000000000000017371317611606000153350ustar00rootroot00000000000000catkin_pkg ---------- Standalone Python library for the `Catkin package system `_. Code & tickets -------------- +------------+--------------------------------------------------------+ | catkin_pkg | http://github.com/ros-infrastructure/catkin_pkg | +------------+--------------------------------------------------------+ | Issues | http://github.com/ros-infrastructure/catkin_pkg/issues | +------------+--------------------------------------------------------+ Continuous Integration ---------------------- +--------------------------------------------------------------------------+--------------------------------------------------------------------+ | `Build Status `_. | .. image:: https://travis-ci.org/ros-infrastructure/catkin_pkg.png | +--------------------------------------------------------------------------+--------------------------------------------------------------------+ catkin_pkg-0.3.9/bin/000077500000000000000000000000001317611606000144065ustar00rootroot00000000000000catkin_pkg-0.3.9/bin/catkin_create_pkg000077500000000000000000000001771317611606000177760ustar00rootroot00000000000000#!/usr/bin/env python import sys from catkin_pkg.cli.create_pkg import main if __name__ == '__main__': sys.exit(main()) catkin_pkg-0.3.9/bin/catkin_find_pkg000077500000000000000000000001751317611606000174510ustar00rootroot00000000000000#!/usr/bin/env python import sys from catkin_pkg.cli.find_pkg import main if __name__ == '__main__': sys.exit(main()) catkin_pkg-0.3.9/bin/catkin_generate_changelog000077500000000000000000000002651317611606000214710ustar00rootroot00000000000000#!/usr/bin/env python import sys from catkin_pkg.cli.generate_changelog import main_catching_runtime_error if __name__ == '__main__': sys.exit(main_catching_runtime_error()) catkin_pkg-0.3.9/bin/catkin_tag_changelog000077500000000000000000000002021317611606000204410ustar00rootroot00000000000000#!/usr/bin/env python import sys from catkin_pkg.cli.tag_changelog import main if __name__ == '__main__': sys.exit(main()) catkin_pkg-0.3.9/bin/catkin_test_changelog000077500000000000000000000002031317611606000206460ustar00rootroot00000000000000#!/usr/bin/env python import sys from catkin_pkg.cli.test_changelog import main if __name__ == '__main__': sys.exit(main()) catkin_pkg-0.3.9/doc/000077500000000000000000000000001317611606000144035ustar00rootroot00000000000000catkin_pkg-0.3.9/doc/Makefile000066400000000000000000000005021317611606000160400ustar00rootroot00000000000000.PHONY: html apidoc clean upload SPHINXAPIDOC=sphinx-apidoc html: apidoc $(MAKE) -C _build html apidoc: $(SPHINXAPIDOC) -F -o _build ../src/catkin_pkg sed -i "s/_build/./g" _build/Makefile clean: -rm -rf _build upload: html scp -r _build/html/ rosbot@ros.osuosl.org:/home/rosbot/docs/independent/api/catkin_pkg/ catkin_pkg-0.3.9/setup.py000077500000000000000000000031061317611606000153530ustar00rootroot00000000000000#!/usr/bin/env python import os from setuptools import setup kwargs = { 'name': 'catkin_pkg', 'version': '0.3.9', # same version as in src/catkin_pkg/__init__.py 'packages': ['catkin_pkg', 'catkin_pkg.cli'], 'package_dir': {'': 'src'}, 'package_data': {'catkin_pkg': ['templates/*.in']}, 'entry_points': { 'console_scripts': [ 'catkin_create_pkg = catkin_pkg.cli.create_pkg:main', 'catkin_find_pkg = catkin_pkg.cli.find_pkg:main', 'catkin_generate_changelog = catkin_pkg.cli.generate_changelog:main_catching_runtime_error', 'catkin_tag_changelog = catkin_pkg.cli.tag_changelog:main', 'catkin_test_changelog = catkin_pkg.cli.test_changelog:main', ]}, 'author': 'Dirk Thomas', 'author_email': 'dthomas@osrfoundation.org', 'url': 'http://wiki.ros.org/catkin_pkg', 'download_url': 'http://download.ros.org/downloads/catkin_pkg/', 'keywords': ['catkin', 'ROS'], 'classifiers': [ 'Programming Language :: Python', 'License :: OSI Approved :: BSD License' ], 'description': 'catkin package library', 'long_description': 'Library for retrieving information about catkin packages.', 'license': 'BSD', 'install_requires': [ 'argparse', 'docutils', 'python-dateutil' ], } if 'SKIP_PYTHON_MODULES' in os.environ: kwargs['packages'] = [] kwargs['package_dir'] = {} kwargs['package_data'] = {} if 'SKIP_PYTHON_SCRIPTS' in os.environ: kwargs['name'] += '_modules' kwargs['entry_points'] = {} setup(**kwargs) catkin_pkg-0.3.9/src/000077500000000000000000000000001317611606000144255ustar00rootroot00000000000000catkin_pkg-0.3.9/src/catkin_pkg/000077500000000000000000000000001317611606000165375ustar00rootroot00000000000000catkin_pkg-0.3.9/src/catkin_pkg/__init__.py000066400000000000000000000032751317611606000206570ustar00rootroot00000000000000# 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. """ Library for retrieving information about catkin packages. """ __version__ = '0.3.9' # same version as in setup.py catkin_pkg-0.3.9/src/catkin_pkg/changelog.py000066400000000000000000000456311317611606000210510ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, 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 Open Source Robotics Foundation, 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. ''' Processes ROS changelogs so that they can be used in binary packaging. The Changelog format is described in REP-0132: http://ros.org/reps/rep-0132.html ''' from __future__ import print_function from __future__ import unicode_literals import sys _py3 = sys.version_info[0] >= 3 import dateutil.parser import docutils import docutils.core import logging import os import pkg_resources import re try: _unicode = unicode except NameError: _unicode = str __author__ = "William Woodall" __email__ = "william@osrfoundation.org" __maintainer__ = "William Woodall" log = logging.getLogger('changelog') CHANGELOG_FILENAME = 'CHANGELOG.rst' example_rst = """\ ^^^^^^^^^^^^^^^^^^^^^^^^^ Changelog for package foo ^^^^^^^^^^^^^^^^^^^^^^^^^ 0.1 === Free form text about this minor release. 0.1.27 (forthcoming) -------------------- * Great new feature 0.1.26 (2012-12-26) ------------------- * Utilizes caching to improve query performance (fix https://github.com/ros/ros_comm/pull/2) * Simplified API calls based on (https://github.com/ros/robot_model): * Note that these changes are based on REP 192 * Also they fix a problem related to initialization * Fixed synchronization issue on startup .. not mentioning secret feature on purpose 0.1.25 (2012-11-25) ------------------- - Added thread safety - Replaced custom XML parser with `TinyXML `_. - Fixed regression introduced in 0.1.22 - New syntax for foo:: foo('bar') - Added a safety check for XML parsing ---- The library should now compile under ``Win32`` 0.1.0 (2012-10-01) ------------------ *First* public **stable** release 0.0 === 0.0.1 (2012-01-31) ------------------ 1. Initial release 2. Initial bugs """ def bullet_list_class_from_docutils(bullet_list, bullet_type=None): ''' Processes elements of bullet list into an encapsulating class :param bullet_list: ``docutils.nodes.bullet_list`` list to be processed :param bullet_type: ``str`` either 'bullet' or 'enumerated' :returns: ``BulletList`` object representing a docutils bullet_list ''' content = BulletList(bullet_type=bullet_type) for child in bullet_list.children: if isinstance(child, docutils.nodes.list_item): content.bullets.append(mixed_text_from_docutils(child)) else: log.debug("Skipped bullet_list child: '{0}'".format(child)) return content def mixed_text_from_docutils(node): ''' Takes most Text-ish docutils objects and converts them to MixedText :param node: ``docutils.nodes.{paragraph, list_item, ...}`` text-ish :returns: ``MixedText`` representing the given docutils object ''' content = MixedText() for child in node.children: if isinstance(child, docutils.nodes.paragraph): content.texts.extend(mixed_text_from_docutils(child).texts) elif isinstance(child, docutils.nodes.Text): content.texts.append(child.astext()) elif isinstance(child, docutils.nodes.reference): content.texts.append(reference_from_docutils(child)) elif isinstance(child, docutils.nodes.emphasis): content.texts.append('*{0}*'.format(child.astext())) elif isinstance(child, docutils.nodes.strong): content.texts.append('**{0}**'.format(child.astext())) elif isinstance(child, docutils.nodes.literal): content.texts.append('``{0}``'.format(child.astext())) elif isinstance(child, docutils.nodes.literal_block): content.texts.append('\n\n ' + child.astext() + '\n') elif isinstance(child, docutils.nodes.target): pass elif isinstance(child, docutils.nodes.system_message): log.debug("Skipping system_message: {0}".format(child)) elif isinstance(child, docutils.nodes.bullet_list): content.texts.append(bullet_list_class_from_docutils(child)) else: try: # Try to add it as plain text log.debug("Trying to add {0}'s child of type {1}: '{2}'" .format(type(node), type(child), child)) content.texts.append(child.astext()) except AttributeError: log.debug("Ignored {0} child of type {1}: '{2}'" .format(type(node), type(child), child)) return content def get_changelog_from_path(path, package_name=None): ''' Changelog factory, which reads a changelog file into a class :param path: ``str`` the path of the changelog including or excluding the filename CHANGELOG.rst :param package_name: ``str`` the package name :returns: ``Changelog`` changelog class or None if file was not readable ''' changelog = Changelog(package_name) if os.path.isdir(path): path = os.path.join(path, CHANGELOG_FILENAME) try: with open(path, 'rb') as f: populate_changelog_from_rst(changelog, f.read().decode('utf-8')) except IOError: return None return changelog def populate_changelog_from_rst(changelog, rst): ''' Changelog factory, which converts the raw ReST into a class :param changelog: ``Changelog`` changelog to be populated :param rst: ``str`` raw ReST changelog :returns: ``Changelog`` changelog that was populated ''' document = docutils.core.publish_doctree(rst) processes_changelog_children(changelog, document.children) changelog.rst = rst return changelog def processes_changelog_children(changelog, children): ''' Processes docutils children into a REP-0132 changelog instance. Recurse into sections, check (sub-)titles if they are valid versions. :param changelog: ``Changelog`` changelog to be populated :param section: ``docutils.nodes.section`` section to be processed :returns: ``Changelog`` changelog that was populated ''' for i, child in enumerate(children): if isinstance(child, docutils.nodes.section): processes_changelog_children(changelog, child.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): # Extract version and date from (sub-)title title_text = child.children[0].rawsource try: version, date = version_and_date_from_title(title_text) except InvalidSectionTitle: # Catch invalid section titles log.debug("Ignored non-compliant title: '{0}'".format(title_text)) continue valid_section = None not in (version, date) if valid_section: contents = [] # For each remaining sibling for child in children[i + 1:]: # Skip sections (nesting of valid sections not allowed) if isinstance(child, docutils.nodes.section): log.debug("Ignored section child: '{0}'".format(child)) continue # Skip title if isinstance(child, docutils.nodes.title): continue # Skip comments if isinstance(child, docutils.nodes.comment): log.debug("Ignored section child: '{0}'".format(child)) continue # Process other elements into the contents if isinstance(child, docutils.nodes.bullet_list): contents.append(bullet_list_class_from_docutils(child)) elif isinstance(child, docutils.nodes.enumerated_list): contents.append(bullet_list_class_from_docutils(child, bullet_type='enumerated')) elif isinstance(child, docutils.nodes.transition): contents.append(Transition()) elif isinstance(child, docutils.nodes.paragraph): contents.append(mixed_text_from_docutils(child)) else: log.debug("Skipped section child: '{0}'".format(child)) changelog.add_version_section(version, date, contents) break else: log.debug("Ignored non-compliant title: '{0}'".format(child)) def reference_from_docutils(reference): ''' Turns a reference element into a ``Reference`` :param reference: ``docutils.nodes.reference`` reference element :returns: ``Reference`` simpler object representing the reference ''' name, refuri = None, None for pair in reference.attlist(): if pair[0] == 'name': name = pair[1] if pair[0] == 'refuri': refuri = pair[1] return Reference(name, refuri) def version_and_date_from_title(title): ''' Splits a section title into version and date if possible. :param title: ``str`` raw section title to be processed :returns: ``(str, datetime.datetime)`` :raises: ``InvalidSectionTitle`` for non REP-0132 section titles ''' match = re.search(r'^([0-9]+\.[0-9]+\.[0-9]+)[ ]\((.+)\)$', title) if match is None: raise InvalidSectionTitle(title) version, date_str = match.groups() try: date = dateutil.parser.parse(date_str) except (ValueError, TypeError) as e: # Catch invalid dates log.debug("Error parsing date ({0}): '{1}'".format(date_str, e)) raise InvalidSectionTitle(title) return version, date class BulletList(object): '''Represents a bulleted list of text''' def __init__(self, bullets=None, bullet_type=None): ''' :param bullets: ``list(MixedText)`` list of text bullets :param bullet_type: ``str`` either 'bullet' or 'enumerated' ''' bullet_type = 'bullet' if bullet_type is None else bullet_type if bullet_type not in ['bullet', 'enumerated']: raise RuntimeError("Invalid bullet type: '{0}'".format(bullet_type)) self.bullets = bullets or [] self.bullet_type = bullet_type def __iter__(self): for bullet in self.bullets: yield bullet def __str__(self): value = self.__unicode__() if not _py3: value = value.encode('ascii', 'replace') return value def __unicode__(self): return self.as_txt() def as_rst(self): return self.as_txt(indent='', use_hyphen_bullet=True) def as_txt(self, indent='', use_hyphen_bullet=False): bullet = '*' if self.bullet_type == 'bullet' else '#' if use_hyphen_bullet and bullet == '*': bullet = '-' b = self.bullet_generator(bullet) i = indent n = '\n' + i + ' ' lines = [i + next(b) + _unicode(l).replace('\n', n) for l in self] return '\n'.join(lines) def bullet_generator(self, bullet): if '#' == bullet: bullets = [str(i) + '. ' for i in range(1, len(self.bullets) + 1)] else: bullets = [bullet + ' '] * len(self.bullets) for b in bullets: yield b class Changelog(object): ''' Represents a REP-0132 changelog ''' def __init__(self, package_name=None): self.__package_name = package_name self.__versions = [] self.__parsed_versions = [] self.__dates = {} self.__content = {} self.__rst = '' def __str__(self): value = self.__unicode__() if not _py3: value = value.encode('ascii', 'replace') return value def __unicode__(self): msg = [] if self.__package_name: msg.append("Changelog for package '{0}'".format(self.package_name)) for version, date, content in self.foreach_version(reverse=True): msg.append(' ' + version + ' ({0}):'.format(date)) for item in content: msg.extend([' ' + i for i in _unicode(item).splitlines()]) return '\n'.join(msg) @property def package_name(self): return self.__package_name @package_name.setter def package_name(self, package_name): self.__package_name = package_name @property def rst(self): return self.__rst @rst.setter def rst(self, rst): self.__rst = rst def add_version_section(self, version, date, contents): ''' Adds a version section :param version: ``str`` version as a string :param date: ``datetime.datetime`` version date :param contents: ``list(list([str|Reference]))``` contents as a list of lists which contain a combination of ``str`` and ``Reference`` objects :returns: None ''' if version in self.__versions: raise DuplicateVersionsException(version) self.__parsed_versions.append(pkg_resources.parse_version(version)) self.__parsed_versions = sorted(self.__parsed_versions) # Cannot go parsed -> str, so sorting must be done by comparison new_versions = [None] * len(self.__parsed_versions) for v in self.__versions + [version]: parsed_v = pkg_resources.parse_version(v) index = self.__parsed_versions.index(parsed_v) if index == -1: raise RuntimeError("Inconsistent internal version storage state") new_versions[index] = v self.__versions = new_versions self.__dates[version] = date self.__content[version] = contents def foreach_version(self, reverse=False): ''' Creates a generator for iterating over the versions, dates and content Versions are stored and iterated in order. :param reverse: ``bool`` if True then the iteration is reversed :returns: ``generator`` for iterating over versions, dates and content ''' for version in reversed(self.__versions) if reverse else self.__versions: yield version, self.__dates[version], self.__content[version] def get_date_of_version(self, version): '''Returns date of a given version as a ``datetime.datetime``''' if version not in self.__versions: raise KeyError("No date for version '{0}'".format(version)) return self.__dates[version] def get_content_of_version(self, version): ''' Returns changelog content for a given version :param version: ``str`` version :returns: ``list(list([str|Reference]))`` content expanded ''' if version not in self.__versions: raise KeyError("No content for version '{0}'".format(version)) return self.__content[version] class DuplicateVersionsException(Exception): '''Raised when more than one section per version is given''' def __init__(self, version): self.version = version Exception.__init__(self, "Version '{0}' is specified twice".format(version)) class InvalidSectionTitle(Exception): '''raised on non REP-0132 section titles''' def __init__(self, title): self.title = title msg = "Section title does not conform to REP-0132: '{0}'".format(title) Exception.__init__(self, msg) class MixedText(object): '''Represents text mixed with references and nested bullets''' def __init__(self, texts=[]): self.texts = list(texts) def __iter__(self): for text in self.texts: yield text def __str__(self): value = self.__unicode__() if not _py3: value = value.encode('ascii', 'replace') return value def __unicode__(self): return self.to_txt() def to_txt(self, bullet_indent=' '): lines = [] for t in self: if isinstance(t, BulletList): bullets = [bullet_indent + x for x in _unicode(t).splitlines()] bullets = ['', ''] + bullets + [''] lines.extend('\n'.join(bullets)) else: lines.append(_unicode(t)) return ''.join(lines) class Reference(object): ''' Represents a piece of text with an associated link ''' def __init__(self, text, link): self.text = text self.link = link def __str__(self): value = self.__unicode__() if not _py3: value = value.encode('ascii', 'replace') return value def __unicode__(self): return self.as_txt() def as_rst(self): '''Self as rst (unicode)''' if self.text is None: return _unicode(self.link) return "`{0} <{1}>`_".format(self.text, self.link) def as_txt(self): '''Self formatted for plain text (unicode)''' if self.text is None: return _unicode(self.link) return "{0} <{1}>".format(self.text, self.link) class Transition(object): '''Represents a trasition element from ReST''' def __str__(self): value = self.__unicode__() if not _py3: value = value.encode('ascii', 'replace') return value def __unicode__(self): return '-' * 20 def __iter__(self): yield self.unicode() def __test(): package_name = 'foo' changelog = Changelog(package_name) print(populate_changelog_from_rst(changelog, example_rst)) if __name__ == '__main__': logging.basicConfig() log.setLevel(logging.DEBUG) __test() catkin_pkg-0.3.9/src/catkin_pkg/changelog_generator.py000066400000000000000000000261551317611606000231170ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, 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 Open Source Robotics Foundation, 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. ''' Generate/update ROS changelog files. The Changelog format is described in REP-0132: http://ros.org/reps/rep-0132.html ''' import os import re from catkin_pkg.changelog import CHANGELOG_FILENAME from catkin_pkg.changelog_generator_vcs import Tag FORTHCOMING_LABEL = 'Forthcoming' def get_all_changes(vcs_client, skip_merges=False): tags = _get_version_tags(vcs_client) # query all log entries per tag range tag2log_entries = {} previous_tag = Tag(None) for tag in sorted_tags(tags): log_entries = vcs_client.get_log_entries( from_tag=previous_tag.name, to_tag=tag.name, skip_merges=skip_merges) tag2log_entries[previous_tag] = log_entries previous_tag = tag log_entries = vcs_client.get_log_entries( from_tag=previous_tag.name, to_tag=None, skip_merges=skip_merges) tag2log_entries[previous_tag] = log_entries return tag2log_entries def get_forthcoming_changes(vcs_client, skip_merges=False): tags = _get_version_tags(vcs_client) latest_tag_name = _get_latest_version_tag_name(vcs_client) # query log entries since latest tag only tag2log_entries = {} from_tag = Tag(None) to_tag = Tag(latest_tag_name) for tag in sorted_tags(tags): if to_tag.name is None: to_tag = tag # ignore non-forthcoming log entries but keep version to identify injection point of forthcoming tag2log_entries[tag] = None log_entries = vcs_client.get_log_entries( from_tag=from_tag.name, to_tag=to_tag.name, skip_merges=skip_merges) tag2log_entries[from_tag] = log_entries return tag2log_entries def _get_version_tags(vcs_client): # get all tags in descending order tags = vcs_client.get_tags() version_tags = [t for t in tags if re.match(r'^\d+\.\d+.\d+$', t.name)] return version_tags def _get_latest_version_tag_name(vcs_client): # get latest tag tag_name = vcs_client.get_latest_tag_name() version_tag_name = tag_name if re.match(r'^\d+\.\d+.\d+$', tag_name) else None return version_tag_name def generate_changelogs(base_path, packages, tag2log_entries, logger=None, vcs_client=None, skip_contributors=False): for pkg_path, package in packages.items(): changelog_path = os.path.join(base_path, pkg_path, CHANGELOG_FILENAME) if os.path.exists(changelog_path): continue # generate package specific changelog file if logger: logger.debug("- creating '%s'" % os.path.join(pkg_path, CHANGELOG_FILENAME)) pkg_tag2log_entries = filter_package_changes(tag2log_entries, pkg_path) data = generate_changelog_file(package.name, pkg_tag2log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors) with open(changelog_path, 'wb') as f: f.write(data.encode('utf-8')) def update_changelogs(base_path, packages, tag2log_entries, logger=None, vcs_client=None, skip_contributors=False): for pkg_path in packages.keys(): # update package specific changelog file if logger: logger.debug("- updating '%s'" % os.path.join(pkg_path, CHANGELOG_FILENAME)) pkg_tag2log_entries = filter_package_changes(tag2log_entries, pkg_path) changelog_path = os.path.join(base_path, pkg_path, CHANGELOG_FILENAME) with open(changelog_path, 'rb') as f: data = f.read().decode('utf-8') data = update_changelog_file(data, pkg_tag2log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors) with open(changelog_path, 'wb') as f: f.write(data.encode('utf-8')) def filter_package_changes(tag2log_entries, pkg_path): pkg_tag2log_entries = {} # collect all log entries relevant for this package for tag, log_entries in tag2log_entries.items(): if log_entries is None: pkg_log_entries = None else: pkg_log_entries = [] for log_entry in log_entries: if log_entry.affects_path(pkg_path): pkg_log_entries.append(log_entry) pkg_tag2log_entries[tag] = pkg_log_entries return pkg_tag2log_entries def generate_changelog_file(pkg_name, tag2log_entries, vcs_client=None, skip_contributors=False): blocks = [] blocks.append(generate_package_headline(pkg_name)) for tag in sorted_tags(tag2log_entries.keys()): log_entries = tag2log_entries[tag] if log_entries is not None: blocks.append(generate_version_block(tag.name, tag.timestamp, log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors)) return '\n'.join(blocks) def update_changelog_file(data, tag2log_entries, vcs_client=None, skip_contributors=False): tags = sorted_tags(tag2log_entries.keys()) for i, tag in enumerate(tags): log_entries = tag2log_entries[tag] if log_entries is None: continue content = generate_version_content(log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors) # check if version section exists match = get_version_section_match(data, tag.name) if match: # prepend content to existing section data = prepend_version_content(data, tag.name, content) assert data is not None else: # find injection point of earliest following version for next_tag in list(tags)[i:]: match = get_version_section_match(data, next_tag.name) if match: block = generate_version_block(tag.name, tag.timestamp, log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors) data = data[:match.start()] + block + '\n' + data[match.start():] break if not match: if tag.name is None: raise RuntimeError('Could not find section "%s"' % next_tag.name) else: raise RuntimeError('Could neither find section "%s" nor any other section' % tag.name) return data def get_version_section_match(data, version): pattern = get_version_section_pattern(version) matches = re.finditer(pattern, data, flags=re.MULTILINE) matches = list(matches) if len(matches) > 1: raise RuntimeError('Found multiple matching sections') return matches[0] if matches else None def get_version_section_pattern(version): valid_section_characters = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' headline = get_version_headline(version, None) pattern = '^(' + re.escape(headline) + '( \([0-9 \-:|+]+\))?)\n([' + re.escape(valid_section_characters) + ']+)\n?$' return pattern def prepend_version_content(data, version, content): pattern = get_version_section_pattern(version) def replace_section(match): headline = match.group(1) section = match.group(3) data = content.rstrip() if data: data += '\n' return headline + '\n' + section + '\n' + data data, count = re.subn(pattern, replace_section, data, flags=re.MULTILINE) if count > 1: raise RuntimeError('Found multiple matching sections') return data if count == 1 else None def sorted_tags(tags): # first return the forthcoming tag for tag in tags: if not tag.name: yield tag # then return the tags in descending order name_and_tag = [(t.name, t) for t in tags if t.name] name_and_tag.sort(key=lambda x: [int(y) for y in x[0].split('.')]) name_and_tag.reverse() for (_, tag) in name_and_tag: yield tag def generate_package_headline(pkg_name): headline = 'Changelog for package %s' % pkg_name section_marker = '^' * len(headline) return '%s\n%s\n%s\n' % (section_marker, headline, section_marker) def generate_version_block(version, timestamp, log_entries, vcs_client=None, skip_contributors=False): data = generate_version_headline(version, timestamp) data += generate_version_content(log_entries, vcs_client=vcs_client, skip_contributors=skip_contributors) return data def generate_version_headline(version, timestamp): headline = get_version_headline(version, timestamp) return '%s\n%s\n' % (headline, '-' * len(headline)) def get_version_headline(version, timestamp): if not version: return FORTHCOMING_LABEL headline = version if timestamp: headline += ' (%s)' % timestamp return headline def generate_version_content(log_entries, vcs_client=None, skip_contributors=False): data = '' all_authors = set() for entry in log_entries: msg = entry.msg lines = msg.splitlines() lines = [l.strip() for l in lines] lines = [l for l in lines if l] lines = [escape_trailing_underscores(l) for l in lines] data += '* %s\n' % (replace_repository_references(lines[0], vcs_client=vcs_client) if lines else '') for line in lines[1:]: data += ' %s\n' % replace_repository_references(line, vcs_client=vcs_client) all_authors.add(entry.author) if all_authors and not skip_contributors: data += '* Contributors: %s\n' % ', '.join(sorted(all_authors)) return data def escape_trailing_underscores(line): if line.endswith('_'): line = line[:-1] + '\_' # match words ending with an underscore which are not followed by another word # and insert a backslash before the underscore to escape it line = re.sub(r'(\w+)_([^\w])', '\\1\\_\\2', line) return line def replace_repository_references(line, vcs_client=None): if vcs_client: line = vcs_client.replace_repository_references(line) return line catkin_pkg-0.3.9/src/catkin_pkg/changelog_generator_vcs.py000066400000000000000000000346441317611606000237740ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, 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 Open Source Robotics Foundation, 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. ''' Extract log information from repositories. ''' import os import re import shutil import subprocess import tempfile class Tag(object): def __init__(self, name, timestamp=None): self.name = name self.timestamp = timestamp class LogEntry(object): def __init__(self, msg, affected_paths, author): self.msg = msg self.author = author self._affected_paths = [p for p in affected_paths if p] def affects_path(self, path): for apath in self._affected_paths: # if the path is the root of the repository # it is affected by all changes if path == '.': return True if apath.startswith(os.path.join(path, '')): return True return False class VcsClientBase(object): def __init__(self, path): self.path = path def get_tags(self): raise NotImplementedError() def get_latest_tag_name(self): raise NotImplementedError() def get_log_entries(self, from_tag, to_tag, skip_merges=False): raise NotImplementedError() def replace_repository_references(self, line): return line def _find_executable(self, file_name): for path in os.getenv('PATH').split(os.path.pathsep): file_path = os.path.join(path, file_name) if os.path.isfile(file_path): return file_path return None def _run_command(self, cmd, env=None): cwd = os.path.abspath(self.path) result = {'cmd': ' '.join(cmd), 'cwd': cwd} try: proc = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) output, _ = proc.communicate() result['output'] = output.rstrip().decode('utf-8') result['returncode'] = proc.returncode except subprocess.CalledProcessError as e: result['output'] = e.output result['returncode'] = e.returncode return result def _truncate_timestamps(self, tags): # truncate timestamps to shortest unique representation # - date only # - date including hours and minutes # - date include hours, minutes and seconds lengths = [10, 16, 19] for length in lengths: # filter tags which have not been truncated yet considered_tags = [t for t in tags if len(t.timestamp) > length] # count tags which timestamps have the same truncated representation grouped_by_timestamp = {} for t in considered_tags: truncated_timestamp = t.timestamp[:length] if truncated_timestamp not in grouped_by_timestamp: grouped_by_timestamp[truncated_timestamp] = [] grouped_by_timestamp[truncated_timestamp].append(t) # truncate timestamp of tags which are unique for truncated_timestamp, similar_tags in grouped_by_timestamp.items(): if len(similar_tags) == 1: similar_tags[0].timestamp = truncated_timestamp class GitClient(VcsClientBase): type = 'git' def __init__(self, path): super(GitClient, self).__init__(path) self._executable = self._find_executable('git') self._repo_hosting = None self._github_base_url = 'https://github.com/' self._github_path = None # query author def _get_author(self, hash_): cmd = [self._executable, 'log', hash_, '-n', '1', '--format=format:%an'] result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch author:\n%s' % result['output']) return result['output'] def get_tags(self): # Get a decorated log, use the refnames to find the ancestor tags cmd_tag = [self._executable, 'log', '--simplify-by-decoration', '--decorate', '--pretty=oneline'] result_tag = self._run_command(cmd_tag) if result_tag['returncode']: raise RuntimeError('Could not fetch tags:\n%s' % result_tag['output']) # Parse a comma-separated list of refname decorators out of the log decorations = ', '.join(re.findall('^[a-f0-9]+ \(([^)]*)\) .', result_tag['output'], re.MULTILINE)) + ',' # Extract only refnames that are tags tag_names = re.findall('tag: ([^,]+)[,]', decorations) tags = [] for tag_name in tag_names: cmd = [self._executable, 'log', tag_name, '-n', '1', '--format=format:%ai'] result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch timestamp:\n%s' % result['output']) tags.append(Tag(tag_name, result['output'])) self._truncate_timestamps(tags) return tags def get_latest_tag_name(self): cmd_describe = [self._executable, 'describe', '--abbrev=0', '--tags'] result_describe = self._run_command(cmd_describe) if result_describe['returncode']: raise RuntimeError('Could not fetch latest tag:\n%s' % result_describe['output']) tag_name = result_describe['output'] return tag_name def get_log_entries(self, from_tag, to_tag, skip_merges=False): # query all hashes in the range cmd = [self._executable, 'log'] if from_tag or to_tag: cmd.append('%s%s' % ('%s..' % to_tag if to_tag else '', from_tag if from_tag else '')) cmd.append('--format=format:%H') if skip_merges: cmd.append('--no-merges') result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch commit hashes:\n%s' % result['output']) log_entries = [] if result['output']: # query further information for each changeset hashes = result['output'].splitlines() for hash_ in hashes: # query commit message cmd = [self._executable, 'log', hash_, '-n', '1', '--format=format:%B'] result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch commit message:\n%s' % result['output']) if result['output'] == from_tag: continue msg = result['output'] # query affected paths cmd = [self._executable, 'show', '--first-parent', hash_, '--name-only', '--format=format:""'] result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch affected paths:\n%s' % result['output']) affected_paths = result['output'].splitlines() log_entries.append(LogEntry(msg, affected_paths, self._get_author(hash_))) return log_entries def replace_repository_references(self, line): if self._repo_hosting is None: self._repo_hosting = False try: self._determine_repo_hosting() except RuntimeError: pass if self._repo_hosting == 'github': line = self._replace_github_issue_references(line) return line def _determine_repo_hosting(self): cmd = [self._executable, 'config', '--get', 'remote.origin.url'] result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch remote url:\n%s' % result['output']) # detect github hosting prefixes = ['git@github.com:', 'https://github.com/', 'git://github.com/'] for prefix in prefixes: if result['output'].startswith(prefix): self._repo_hosting = 'github' path = result['output'][len(prefix):] if path.endswith('.git'): path = path[:-4] self._github_path = path break def _replace_github_issue_references(self, line): valid_name = '[\\w\._-]+' issue_pattern = '#(\\d+)' def replace_issue_number(match): issue_url = self._github_base_url if match.group(1): path = match.group(1) issue_url += path else: path = '' issue_url += self._github_path issue_number = match.group(2) issue_url += '/issues/' + issue_number return '`%s#%s <%s>`_' % (path, issue_number, issue_url) line = re.sub(('(%s/%s)?' % (valid_name, valid_name)) + issue_pattern, replace_issue_number, line) return line class HgClient(VcsClientBase): type = 'hg' def __init__(self, path): super(HgClient, self).__init__(path) self._executable = self._find_executable('hg') # query author def _get_author(self, hash_): cmd = [self._executable, 'log', '-r', hash_, '--template', '{author}'] result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch author:\n%s' % result['output']) return result['output'] def get_tags(self): cmd_tag = [self._executable, 'tags', '-q'] result_tag = self._run_command(cmd_tag) if result_tag['returncode']: raise RuntimeError('Could not fetch tags:\n%s' % result_tag['output']) tag_names = result_tag['output'].splitlines() tags = [] for tag_name in tag_names: cmd = [self._executable, 'log', '-r', tag_name, '--template', '{date|isodatesec}'] result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch timestamp:\n%s' % result['output']) tags.append(Tag(tag_name, result['output'])) self._truncate_timestamps(tags) return tags def get_latest_tag_name(self): cmd_log = [self._executable, 'log', '--rev', '.', '--template', '{latesttagdistance}'] result_log = self._run_command(cmd_log) if result_log['returncode']: raise RuntimeError('Could not fetch latest tag:\n%s' % result_log['output']) tag_name = result_log['output'] return tag_name def get_log_entries(self, from_tag, to_tag, skip_merges=False): # query all hashes in the range # ascending chronological order since than it is easier to handle empty tag names revrange = '%s:%s' % ((to_tag if to_tag else ''), (from_tag if from_tag else 'tip')) if to_tag: revrange += '-%s' % to_tag if from_tag: revrange += '-%s' % from_tag cmd = [self._executable, 'log', '-r', revrange, '--template', '{rev}\n'] result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch commit hashes:\n%s' % result['output']) tmp_base = tempfile.mkdtemp('-hg-style') try: style_file = os.path.join(tmp_base, 'hg-changeset-files-per-line.style') with open(style_file, 'w') as f: f.write("changeset = '{files}'\n") f.write("file = '{file}\\n'\n") log_entries = [] if result['output']: # query further information for each changeset revs = reversed(result['output'].splitlines()) for rev in revs: # query commit message cmd = [self._executable, 'log', '-r', rev, '-l', '1', '--template', '{desc}'] result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch commit message:\n%s' % result['output']) if result['output'] == from_tag: continue msg = result['output'] # query affected paths cmd = [self._executable, 'log', '-r', rev, '-l', '1', '--style', style_file] result = self._run_command(cmd) if result['returncode']: raise RuntimeError('Could not fetch affected paths:\n%s' % result['output']) affected_paths = result['output'].splitlines() log_entries.append(LogEntry(msg, affected_paths, self._get_author(rev))) finally: shutil.rmtree(tmp_base) return log_entries def get_vcs_client(base_path): vcs_clients = [] vcs_clients.append(GitClient) vcs_clients.append(HgClient) client_types = [c.type for c in vcs_clients] if len(client_types) != len(set(client_types)): raise RuntimeError('Multiple vcs clients share the same type: %s' % ', '.join(sorted(client_types))) for vcs_client in vcs_clients: if os.path.exists(os.path.join(base_path, '.%s' % vcs_client.type)): return vcs_client(base_path) raise RuntimeError('Could not detect repository type - currently supports: %s' % ', '.join([c.type for c in vcs_clients])) catkin_pkg-0.3.9/src/catkin_pkg/cli/000077500000000000000000000000001317611606000173065ustar00rootroot00000000000000catkin_pkg-0.3.9/src/catkin_pkg/cli/__init__.py000066400000000000000000000000001317611606000214050ustar00rootroot00000000000000catkin_pkg-0.3.9/src/catkin_pkg/cli/create_pkg.py000066400000000000000000000057461317611606000220000ustar00rootroot00000000000000"""This script creates the skeletton of a catkin package""" from __future__ import print_function import argparse import os import sys from catkin_pkg.package_templates import create_package_files, PackageTemplate def main(argv=sys.argv[1:], parent_path=os.getcwd()): parser = argparse.ArgumentParser( description='Creates a new catkin package') parser.add_argument('name', nargs=1, help='The name for the package') parser.add_argument('--meta', action='store_true', help='Creates meta-package files') parser.add_argument('dependencies', nargs='*', help='Catkin package Dependencies') parser.add_argument('-s', '--sys-deps', nargs='*', help='System Dependencies') parser.add_argument('-b', '--boost-comps', nargs='*', help='Boost Components') parser.add_argument('-V', '--pkg_version', action='store', help='Initial Package version') parser.add_argument('-D', '--description', action='store', help='Description') parser.add_argument('-l', '--license', action='append', help='Name for License, (e.g. BSD, MIT, GPLv3...)') parser.add_argument('-a', '--author', action='append', help='A single author, may be used multiple times') parser.add_argument('-m', '--maintainer', action='append', help='A single maintainer, may be used multiple times') rosdistro_name = os.environ['ROS_DISTRO'] if 'ROS_DISTRO' in os.environ else None parser.add_argument('--rosdistro', required=rosdistro_name is None, default=rosdistro_name, help='The ROS distro (default: environment variable ROS_DISTRO if defined)') args = parser.parse_args(argv) try: package_name = args.name[0] target_path = os.path.join(parent_path, package_name) package_template = PackageTemplate._create_package_template( package_name=package_name, description=args.description, licenses=args.license or [], maintainer_names=args.maintainer, author_names=args.author, version=args.pkg_version, catkin_deps=args.dependencies, system_deps=args.sys_deps, boost_comps=args.boost_comps) create_package_files(target_path=target_path, package_template=package_template, rosdistro=args.rosdistro, newfiles={}, meta=args.meta) print('Successfully created files in %s. Please adjust the values in package.xml.' % target_path) except ValueError as vae: parser.error(str(vae)) catkin_pkg-0.3.9/src/catkin_pkg/cli/find_pkg.py000066400000000000000000000015761317611606000214520ustar00rootroot00000000000000"""This script finds a catkin packages""" from __future__ import print_function import argparse import os import sys from catkin_pkg.packages import find_packages def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser(description='Find a catkin package') parser.add_argument('pkg', help='The name of the package') parser.add_argument('base_path', nargs='?', default=os.curdir, help='The base path to crawl for packages') args = parser.parse_args(argv) try: packages = find_packages(args.base_path) catkin_pkg = [path for path, p in packages.items() if p.name == args.pkg] if catkin_pkg: print(catkin_pkg[0]) else: print("Could not find package '%s'." % args.pkg, file=sys.stderr) sys.exit(2) except RuntimeError as e: print('ERROR: ' + str(e), file=sys.stderr) sys.exit(1) catkin_pkg-0.3.9/src/catkin_pkg/cli/generate_changelog.py000066400000000000000000000142371317611606000234700ustar00rootroot00000000000000"""This script generates REP-0132 CHANGELOG.rst files for git or hg repositories""" from __future__ import print_function import argparse import logging import os import sys from catkin_pkg.changelog import CHANGELOG_FILENAME from catkin_pkg.changelog_generator import generate_changelog_file, generate_changelogs, get_all_changes, get_forthcoming_changes, update_changelogs from catkin_pkg.changelog_generator_vcs import get_vcs_client from catkin_pkg.packages import find_packages try: raw_input except NameError: raw_input = input def prompt_continue(msg, default): """Prompt the user for continuation.""" if default: msg += ' [Y/n]?' else: msg += ' [y/N]?' while True: response = raw_input(msg) if not response: response = 'y' if default else 'n' else: response = response.lower() if response in ['y', 'n']: return response == 'y' print("Response '%s' was not recognized, please use one of the following options: y, Y, n, N" % response, file=sys.stderr) def main(sysargs=None): parser = argparse.ArgumentParser(description='Generate a REP-0132 %s' % CHANGELOG_FILENAME) parser.add_argument('-a', '--all', action='store_true', default=False, help='Generate changelog for all versions instead of only the forthcoming one (only supported when no changelog file exists yet)') parser.add_argument('--print-root', action='store_true', default=False, help='Output changelog content to the console as if there would be only one package in the root of the repository') parser.add_argument('--skip-contributors', action='store_true', default=False, help='Skip adding the list of contributors to the changelog') parser.add_argument('--skip-merges', action='store_true', default=False, help='Skip adding merge commits to the changelog') parser.add_argument('-y', '--non-interactive', action='store_true', default=False, help="Run without user interaction, confirming all questions with 'yes'") args = parser.parse_args(sysargs) base_path = '.' logging.basicConfig(format='%(message)s', level=logging.DEBUG) vcs_client = get_vcs_client(base_path) if args.print_root: # printing status messages to stderr to allow piping the changelog to a file if args.all: print('Querying all tags and commit information...', file=sys.stderr) tag2log_entries = get_all_changes(vcs_client, skip_merges=args.skip_merges) print('Generating changelog output with all versions...', file=sys.stderr) else: print('Querying commit information since latest tag...', file=sys.stderr) tag2log_entries = get_forthcoming_changes(vcs_client, skip_merges=args.skip_merges) print('Generating changelog files with forthcoming version...', file=sys.stderr) print('', file=sys.stderr) data = generate_changelog_file('repository-level', tag2log_entries, vcs_client=vcs_client) print(data) return 0 # find packages packages = find_packages(base_path) if not packages: raise RuntimeError('No packages found') print('Found packages: %s' % ', '.join(sorted([p.name for p in packages.values()]))) # check for missing changelogs missing_changelogs = [] for pkg_path, package in packages.items(): changelog_path = os.path.join(base_path, pkg_path, CHANGELOG_FILENAME) if not os.path.exists(changelog_path): missing_changelogs.append(package.name) if args.all and not missing_changelogs: raise RuntimeError('All packages already have a changelog. Either remove (some of) them before using --all or invoke the script without --all.') if args.all and len(missing_changelogs) != len(packages): ignored = set([p.name for p in packages.values()]) - set(missing_changelogs) print('The following packages already have a changelog file and will be ignored: %s' % ', '.join(sorted(ignored)), file=sys.stderr) # prompt to switch to --all if not args.all and missing_changelogs: print('Some of the packages have no changelog file: %s' % ', '.join(sorted(missing_changelogs))) print('You might consider to use --all to generate the changelogs for all versions (not only for the forthcoming version).') if not args.non_interactive and not prompt_continue('Continue without --all option', default=False): raise RuntimeError('Skipping generation, rerun the script with --all.') if args.all: print('Querying all tags and commit information...') tag2log_entries = get_all_changes(vcs_client, skip_merges=args.skip_merges) print('Generating changelog files with all versions...') generate_changelogs(base_path, packages, tag2log_entries, logger=logging, vcs_client=vcs_client, skip_contributors=args.skip_contributors) else: print('Querying commit information since latest tag...') tag2log_entries = get_forthcoming_changes(vcs_client, skip_merges=args.skip_merges) # separate packages with/without a changelog file packages_without = {pkg_path: package for pkg_path, package in packages.items() if package.name in missing_changelogs} if packages_without: print('Generating changelog files with forthcoming version...') generate_changelogs(base_path, packages_without, tag2log_entries, logger=logging, vcs_client=vcs_client, skip_contributors=args.skip_contributors) packages_with = {pkg_path: package for pkg_path, package in packages.items() if package.name not in missing_changelogs} if packages_with: print('Updating forthcoming section of changelog files...') update_changelogs(base_path, packages_with, tag2log_entries, logger=logging, vcs_client=vcs_client, skip_contributors=args.skip_contributors) print('Done.') print('Please review the extracted commit messages and consolidate the changelog entries before committing the files!') def main_catching_runtime_error(*args, **kwargs): try: main(*args, **kwargs) except RuntimeError as e: print('ERROR: ' + str(e), file=sys.stderr) sys.exit(1) catkin_pkg-0.3.9/src/catkin_pkg/cli/tag_changelog.py000066400000000000000000000115731317611606000224510ustar00rootroot00000000000000"""This script renames the forthcoming section in changelog files with the upcoming version and the current date""" from __future__ import print_function import argparse import datetime import docutils.core import os import re import sys from catkin_pkg.changelog import CHANGELOG_FILENAME, get_changelog_from_path from catkin_pkg.changelog_generator import FORTHCOMING_LABEL from catkin_pkg.package_version import bump_version from catkin_pkg.packages import find_packages, verify_equal_package_versions def get_forthcoming_label(rst): document = docutils.core.publish_doctree(rst) forthcoming_label = None for child in document.children: title = None if isinstance(child, docutils.nodes.subtitle): title = child elif isinstance(child, docutils.nodes.section): section = child 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 if FORTHCOMING_LABEL.lower() in title_text.lower(): if forthcoming_label: raise RuntimeError('Found multiple forthcoming sections') forthcoming_label = title_text return forthcoming_label def rename_section(data, old_label, new_label): valid_section_characters = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' def replace_section(match): section_char = match.group(2)[0] return new_label + '\n' + section_char * len(new_label) pattern = '^(' + re.escape(old_label) + ')\n([' + re.escape(valid_section_characters) + ']+)$' data, count = re.subn(pattern, replace_section, data, flags=re.MULTILINE) if count == 0: raise RuntimeError('Could not find section') if count > 1: raise RuntimeError('Found multiple matching sections') return data def main(sysargs=None): parser = argparse.ArgumentParser(description='Tag the forthcoming section in the changelog files with an upcoming version number') parser.add_argument('--bump', choices=('major', 'minor', 'patch'), default='patch', help='Which part of the version number to bump? (default: %(default)s)') args = parser.parse_args(sysargs) base_path = '.' # find packages packages = find_packages(base_path) if not packages: raise RuntimeError('No packages found') print('Found packages: %s' % ', '.join([p.name for p in packages.values()])) # fetch current version and verify that all packages have same version number old_version = verify_equal_package_versions(packages.values()) new_version = bump_version(old_version, args.bump) print('Tag version %s' % new_version) # check for changelog entries changelogs = [] missing_forthcoming = [] already_tagged = [] for pkg_path, package in packages.items(): changelog_path = os.path.join(base_path, pkg_path, CHANGELOG_FILENAME) if not os.path.exists(changelog_path): missing_forthcoming.append(package.name) continue changelog = get_changelog_from_path(changelog_path, package.name) if not changelog: missing_forthcoming.append(package.name) continue # check that forthcoming section exists forthcoming_label = get_forthcoming_label(changelog.rst) if not forthcoming_label: missing_forthcoming.append(package.name) continue # check that new_version section does not exist yet try: changelog.get_content_of_version(new_version) already_tagged.append(package.name) continue except KeyError: pass changelogs.append((package.name, changelog_path, changelog, forthcoming_label)) if missing_forthcoming: print('The following packages do not have a forthcoming section in their changelog file: %s' % ', '.join(sorted(missing_forthcoming)), file=sys.stderr) if already_tagged: print("The following packages do already have a section '%s' in their changelog file: %s" % (new_version, ', '.join(sorted(already_tagged))), file=sys.stderr) # rename forthcoming sections to new_version including current date new_changelog_data = [] new_label = '%s (%s)' % (new_version, datetime.date.today().isoformat()) for (pkg_name, changelog_path, changelog, forthcoming_label) in changelogs: print("Renaming section '%s' to '%s' in package '%s'..." % (forthcoming_label, new_label, pkg_name)) data = rename_section(changelog.rst, forthcoming_label, new_label) new_changelog_data.append((changelog_path, data)) print('Writing updated changelog files...') for (changelog_path, data) in new_changelog_data: with open(changelog_path, 'wb') as f: f.write(data.encode('utf-8')) catkin_pkg-0.3.9/src/catkin_pkg/cli/test_changelog.py000066400000000000000000000030321317611606000226440ustar00rootroot00000000000000"""This script tests REP-0132 changelog files""" from __future__ import print_function import argparse import logging import os import sys import catkin_pkg.changelog from catkin_pkg.changelog import Changelog, CHANGELOG_FILENAME from catkin_pkg.changelog import populate_changelog_from_rst def main(sysargs=None): parser = argparse.ArgumentParser( description="Tests a REP-0132 %s" % CHANGELOG_FILENAME) parser.add_argument( 'changelog_file', help="%s file to parse" % CHANGELOG_FILENAME, default='.', nargs='?') args = parser.parse_args(sysargs) if os.path.isdir(args.changelog_file): changelog_file = os.path.join(args.changelog_file, CHANGELOG_FILENAME) if not os.path.exists(changelog_file): print("No {0} file in given directory: '{1}'" .format(CHANGELOG_FILENAME, args.changelog_file), file=sys.stderr) return 1 else: changelog_file = args.changelog_file if not os.path.exists(changelog_file): print("{0} file given does not exist: '{1}'" .format(CHANGELOG_FILENAME, args.changelog_file), file=sys.stderr) return 1 if os.path.basename(changelog_file) != CHANGELOG_FILENAME: print("WARNING: changelog file name should be %s" % CHANGELOG_FILENAME) logging.basicConfig() catkin_pkg.changelog.log.setLevel(logging.DEBUG) changelog = Changelog() with open(changelog_file, 'r') as f: print(populate_changelog_from_rst(changelog, f.read())) catkin_pkg-0.3.9/src/catkin_pkg/cmake.py000066400000000000000000000060551317611606000201770ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, 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 Open Source Robotics Foundation, 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. from __future__ import print_function import os import re def get_metapackage_cmake_template_path(): ''' Returns the location of the metapackage CMakeLists.txt CMake template. :returns: ``str`` location of the metapackage CMakeLists.txt CMake template ''' rel_path = os.path.join('templates', 'metapackage.cmake.in') return os.path.join(os.path.dirname(__file__), rel_path) def configure_file(template_file, environment): ''' Evaluate a .in template file used in CMake with configure_file(). :param template_file: path to the template, ``str`` :param environment: dictionary of placeholders to substitute, ``dict`` :returns: string with evaluates template :raises: KeyError for placeholders in the template which are not in the environment ''' with open(template_file, 'r') as f: template = f.read() return configure_string(template, environment) def configure_string(template, environment): ''' Substitute variables enclosed by @ characters. :param template: the template, ``str`` :param environment: dictionary of placeholders to substitute, ``dict`` :returns: string with evaluates template :raises: KeyError for placeholders in the template which are not in the environment ''' def substitute(match): var = match.group(0)[1:-1] return environment[var] return re.sub('\@[a-zA-Z0-9_]+\@', substitute, template) catkin_pkg-0.3.9/src/catkin_pkg/metapackage.py000066400000000000000000000141531317611606000213570ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, 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 Open Source Robotics Foundation, 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. """ Checks metapackages for compliance with REP-0127: http://ros.org/reps/rep-0127.html#metapackage """ from __future__ import print_function import os from catkin_pkg.cmake import get_metapackage_cmake_template_path from catkin_pkg.cmake import configure_file __author__ = "William Woodall" __email__ = "william@osrfoundation.org" __maintainer__ = "William Woodall" DEFINITION_URL = 'http://ros.org/reps/rep-0127.html#metapackage' class InvalidMetapackage(Exception): def __init__(self, msg, path, package): self.path = path self.package = package Exception.__init__(self, "Metapackage '%s': %s" % (package.name, msg)) def get_expected_cmakelists_txt(metapackage_name): """ Returns the expected boilerplate CMakeLists.txt file for a metapackage :param metapackage_name: name of the metapackage :type metapackage_name: str :returns: expected CMakeLists.txt file :rtype: str """ env = { 'name': metapackage_name, 'metapackage_arguments': '' } return configure_file(get_metapackage_cmake_template_path(), env) def has_cmakelists_txt(path): """ Returns True if the given path contains a CMakeLists.txt, otherwise False :param path: path to folder potentially containing CMakeLists.txt :type path: str :returns: True if path contains CMakeLists.txt, else False :rtype: bool """ cmakelists_txt_path = os.path.join(path, 'CMakeLists.txt') return os.path.isfile(cmakelists_txt_path) def get_cmakelists_txt(path): """ Fetches the CMakeLists.txt from a given path :param path: path to the folder containing the CMakeLists.txt :type path: str :returns: contents of CMakeLists.txt file in given path :rtype: str :raises OSError: if there is no CMakeLists.txt in given path """ cmakelists_txt_path = os.path.join(path, 'CMakeLists.txt') with open(cmakelists_txt_path, 'r') as f: return f.read() def has_valid_cmakelists_txt(path, metapackage_name): """ Returns True if the given path contains a valid CMakeLists.txt, otherwise False A valid CMakeLists.txt for a metapackage is defined by REP-0127 :param path: path to folder containing CMakeLists.txt :type path: str :param metapackage_name: name of the metapackage being tested :type metapackage_name: str :returns: True if the path contains a valid CMakeLists.txt, else False :rtype: bool :raises OSError: if there is no CMakeLists.txt in given path """ cmakelists_txt = get_cmakelists_txt(path) expected = get_expected_cmakelists_txt(metapackage_name) return cmakelists_txt == expected def validate_metapackage(path, package): """ Validates the given package (catkin_pkg.package.Package) as a metapackage This validates the metapackage against the definition from REP-0127 :param path: directory of the package being checked :type path: str :param package: package to be validated :type package: :py:class:`catkin_pkg.package.Package` :raises InvalidMetapackage: if package is not a valid metapackage :raises OSError: if there is not package.xml at the given path """ # Is there actually a package at the given path, else raise # Cannot do package_exists_at from catkin_pkg.packages because of circular dep if not os.path.isdir(path) or not os.path.isfile(os.path.join(path, 'package.xml')): raise OSError("No package.xml found at path: '%s'" % path) # Is it a metapackage, else raise if not package.is_metapackage(): raise InvalidMetapackage('No tag in section of package.xml', path, package) # Is there a CMakeLists.txt, else raise if not has_cmakelists_txt(path): raise InvalidMetapackage('No CMakeLists.txt', path, package) # Is the CMakeLists.txt correct, else raise if not has_valid_cmakelists_txt(path, package.name): raise InvalidMetapackage("""\ Invalid CMakeLists.txt Expected: <<<%s>>> Got: <<<%s>>>""" % (get_expected_cmakelists_txt(package.name), get_cmakelists_txt(path)), path, package ) # Does it buildtool depend on catkin, else raise if not package.has_buildtool_depend_on_catkin(): raise InvalidMetapackage("No buildtool dependency on catkin", path, package) # Does it have only run depends, else raise if package.has_invalid_metapackage_dependencies(): raise InvalidMetapackage( "Has build, buildtool, and/or test depends, but only run depends are allowed (except buildtool catkin)", path, package) catkin_pkg-0.3.9/src/catkin_pkg/package.py000066400000000000000000000616231317611606000205140ustar00rootroot00000000000000# 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. """ Library for parsing package.xml and providing an object representation. """ from __future__ import print_function from copy import deepcopy import os import re import sys import xml.dom.minidom as dom PACKAGE_MANIFEST_FILENAME = 'package.xml' class Package(object): """ Object representation of a package manifest file """ __slots__ = [ 'package_format', 'name', 'version', 'version_abi', 'description', 'maintainers', 'licenses', 'urls', 'authors', 'build_depends', 'buildtool_depends', 'build_export_depends', 'buildtool_export_depends', 'exec_depends', 'test_depends', 'doc_depends', 'conflicts', 'replaces', 'exports', 'filename' ] def __init__(self, filename=None, **kwargs): """ :param filename: location of package.xml. Necessary if converting ``${prefix}`` in ```` values, ``str``. """ # initialize all slots ending with "s" with lists, all other with plain values for attr in self.__slots__: if attr.endswith('s'): value = list(kwargs[attr]) if attr in kwargs else [] setattr(self, attr, value) else: value = kwargs[attr] if attr in kwargs else None setattr(self, attr, value) if 'depends' in kwargs: for d in kwargs['depends']: for slot in [self.build_depends, self.build_export_depends, self.exec_depends]: if d not in slot: slot.append(deepcopy(d)) del kwargs['depends'] if 'run_depends' in kwargs: for d in kwargs['run_depends']: for slot in [self.build_export_depends, self.exec_depends]: if d not in slot: slot.append(deepcopy(d)) del kwargs['run_depends'] self.filename = filename # verify that no unknown keywords are passed unknown = set(kwargs.keys()).difference(self.__slots__) if unknown: raise TypeError('Unknown properties: %s' % ', '.join(unknown)) def __getattr__(self, name): if name == 'run_depends': # merge different dependencies if they are not exactly equal # potentially having the same dependency name multiple times with different attributes run_depends = [] [run_depends.append(deepcopy(d)) for d in self.exec_depends + self.build_export_depends if d not in run_depends] return run_depends raise AttributeError(name) def __getitem__(self, key): if key in self.__slots__ + ['run_depends']: return getattr(self, key) raise KeyError('Unknown key "%s"' % key) def __iter__(self): for slot in self.__slots__: yield slot def __str__(self): data = {} for attr in self.__slots__: data[attr] = getattr(self, attr) return str(data) def has_buildtool_depend_on_catkin(self): """ Returns True if this Package buildtool depends on catkin, otherwise False :returns: True if the given package buildtool depends on catkin :rtype: bool """ return 'catkin' in [d.name for d in self.buildtool_depends] def get_build_type(self): """ Return value of export/build_type element, or 'catkin' if unspecified. :returns: package build type :rtype: str :raises: :exc:`InvalidPackage` """ build_type_exports = [e.content for e in self.exports if e.tagname == 'build_type'] if not build_type_exports: return 'catkin' if len(build_type_exports) == 1: return build_type_exports[0] raise InvalidPackage('Only one element is permitted.') def has_invalid_metapackage_dependencies(self): """ Returns True if this package has invalid dependencies for a metapackage This is defined by REP-0127 as any non-run_depends dependencies other then a buildtool_depend on catkin. :returns: True if the given package has any invalid dependencies, otherwise False :rtype: bool """ buildtool_depends = [d.name for d in self.buildtool_depends if d.name != 'catkin'] return len(self.build_depends + buildtool_depends + self.test_depends) > 0 def is_metapackage(self): """ Returns True if this pacakge is a metapackage, otherwise False :returns: True if metapackage, else False :rtype: bool """ return 'metapackage' in [e.tagname for e in self.exports] def validate(self, warnings=None): """ makes sure all standards for packages are met :param package: Package to check :param warnings: Print warnings if None or return them in the given list :raises InvalidPackage: in case validation fails """ errors = [] new_warnings = [] if self.package_format: if not re.match('^[1-9][0-9]*$', str(self.package_format)): errors.append('The "format" attribute of the package must contain a positive integer if present') if not self.name: errors.append('Package name must not be empty') # accepting upper case letters and hyphens only for backward compatibility if not re.match('^[a-zA-Z0-9][a-zA-Z0-9_-]*$', self.name): errors.append('Package name "%s" does not follow naming conventions' % self.name) else: if self.get_build_type() == 'catkin': if not re.match('^[a-z][a-z0-9_]*$', self.name): new_warnings.append( 'Catkin package name "%s" does not follow the naming conventions. It should start with ' 'a lower case letter and only contain lower case letters, digits, and underscores.' % self.name) else: if not re.match('^[a-z][a-z0-9_-]*$', self.name): new_warnings.append( 'Non-catkin package name "%s" does not follow the naming conventions. It should start with' 'a lower case letter and only contain lower case letters, digits, underscores, and dashes.' % self.name) if not self.version: errors.append('Package version must not be empty') elif not re.match('^[0-9]+\.[0-9]+\.[0-9]+$', self.version): errors.append('Package version "%s" does not follow version conventions' % self.version) elif not re.match('^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$', self.version): new_warnings.append('Package "%s" does not follow the version conventions. It should not contain leading zeros (unless the number is 0).' % self.name) if not self.description: errors.append('Package description must not be empty') if not self.maintainers: errors.append("Package '{}' must declare at least one maintainer".format(self.name)) for maintainer in self.maintainers: try: maintainer.validate() except InvalidPackage as e: errors.append(str(e)) if not maintainer.email: errors.append('Maintainers must have an email address') if not self.licenses: errors.append('The package node must contain at least one "license" tag') if [l for l in self.licenses if not l.strip()]: errors.append('The license tag must neither be empty nor only contain whitespaces') if self.authors is not None: for author in self.authors: try: author.validate() except InvalidPackage as e: errors.append(str(e)) dep_types = { 'build': self.build_depends, 'buildtool': self.buildtool_depends, 'build_export': self.build_export_depends, 'buildtool_export': self.buildtool_export_depends, 'exec': self.exec_depends, 'test': self.test_depends, 'doc': self.doc_depends } for dep_type, depends in dep_types.items(): for depend in depends: if depend.name == self.name: errors.append('The package must not "%s_depend" on a package with the same name as this package' % dep_type) if self.is_metapackage(): if not self.has_buildtool_depend_on_catkin(): # TODO escalate to error in the future, or use metapackage.validate_metapackage new_warnings.append('Metapackage "%s" must buildtool_depend on catkin.' % self.name) if self.has_invalid_metapackage_dependencies(): new_warnings.append('Metapackage "%s" should not have other dependencies besides a ' 'buildtool_depend on catkin and run_depends.' % self.name) for warning in new_warnings: if warnings is None: print('WARNING: ' + warning, file=sys.stderr) elif warning not in warnings: warnings.append(warning) if errors: raise InvalidPackage('\n'.join(errors)) class Dependency(object): __slots__ = ['name', 'version_lt', 'version_lte', 'version_eq', 'version_gte', 'version_gt'] def __init__(self, name, **kwargs): for attr in self.__slots__: value = kwargs[attr] if attr in kwargs else None setattr(self, attr, value) self.name = name # verify that no unknown keywords are passed unknown = set(kwargs.keys()).difference(self.__slots__) if unknown: raise TypeError('Unknown properties: %s' % ', '.join(unknown)) def __eq__(self, other): if not isinstance(other, Dependency): return False return all([getattr(self, attr) == getattr(other, attr) for attr in self.__slots__]) def __hash__(self): return hash(tuple([getattr(self, slot) for slot in self.__slots__])) def __str__(self): return self.name class Export(object): __slots__ = ['tagname', 'attributes', 'content'] def __init__(self, tagname, content=None): self.tagname = tagname self.attributes = {} self.content = content def __str__(self): txt = '<%s' % self.tagname for key in sorted(self.attributes.keys()): txt += ' %s="%s"' % (key, self.attributes[key]) if self.content: txt += '>%s' % (self.content, self.tagname) else: txt += '/>' return txt class Person(object): __slots__ = ['name', 'email'] def __init__(self, name, email=None): self.name = name self.email = email def __str__(self): name = self.name if not isinstance(name, str): name = name.encode('utf-8') if self.email is not None: return '%s <%s>' % (name, self.email) else: return '%s' % name def validate(self): if self.email is None: return if not re.match('^[-a-zA-Z0-9_%+]+(\.[-a-zA-Z0-9_%+]+)*@[-a-zA-Z0-9%]+(\.[-a-zA-Z0-9%]+)*\.[a-zA-Z]{2,}$', self.email): raise InvalidPackage('Invalid email "%s" for person "%s"' % (self.email, self.name)) class Url(object): __slots__ = ['url', 'type'] def __init__(self, url, type_=None): self.url = url self.type = type_ def __str__(self): return self.url def parse_package_for_distutils(path=None): print('WARNING: %s/setup.py: catkin_pkg.package.parse_package_for_distutils() is deprecated. Please use catkin_pkg.python_setup.generate_distutils_setup(**kwargs) instead.' % os.path.basename(os.path.abspath('.'))) from .python_setup import generate_distutils_setup data = {} if path is not None: data['package_xml_path'] = path return generate_distutils_setup(**data) class InvalidPackage(Exception): pass def package_exists_at(path): """ Checks that a package exists at the given path :param path: path to a package :type path: str :returns: True if package exists in given path, else False :rtype: bool """ return os.path.isdir(path) and os.path.isfile(os.path.join(path, PACKAGE_MANIFEST_FILENAME)) def _get_package_xml(path): """ Get xml of package manifest. :param path: The path of the package.xml file, it may or may not include the filename :returns: a tuple with the xml as well as the path of the read file :raises: :exc:`IOError` """ if os.path.isfile(path): filename = path elif package_exists_at(path): filename = os.path.join(path, PACKAGE_MANIFEST_FILENAME) if not os.path.isfile(filename): raise IOError('Directory "%s" does not contain a "%s"' % (path, PACKAGE_MANIFEST_FILENAME)) else: raise IOError('Path "%s" is neither a directory containing a "%s" file nor a file' % (path, PACKAGE_MANIFEST_FILENAME)) # Force utf8 encoding for python3. # This way unicode files can still be processed on non-unicode locales. kwargs = {} if sys.version_info[0] >= 3: kwargs['encoding'] = 'utf8' with open(filename, 'r', **kwargs) as f: return f.read(), filename def parse_package(path, warnings=None): """ Parse package manifest. :param path: The path of the package.xml file, it may or may not include the filename :param warnings: Print warnings if None or return them in the given list :returns: return :class:`Package` instance, populated with parsed fields :raises: :exc:`InvalidPackage` :raises: :exc:`IOError` """ xml, filename = _get_package_xml(path) try: return parse_package_string(xml, filename, warnings=warnings) except InvalidPackage as e: e.args = ['Invalid package manifest "%s": %s' % (filename, e.message)] raise def _check_known_attributes(node, known): if node.hasAttributes(): attrs = map(str, node.attributes.keys()) # colon is the namespace separator in attributes, xmlns can be added to any tag unknown_attrs = [attr for attr in attrs if not (attr in known or attr == 'xmlns' or ':' in attr)] if unknown_attrs: return ['The "%s" tag must not have the following attributes: %s' % (node.tagName, ', '.join(unknown_attrs))] return [] def parse_package_string(data, filename=None, warnings=None): """ Parse package.xml string contents. :param data: package.xml contents, ``str`` :param filename: full file path for debugging, ``str`` :param warnings: Print warnings if None or return them in the given list :returns: return parsed :class:`Package` :raises: :exc:`InvalidPackage` """ try: root = dom.parseString(data) except Exception as ex: raise InvalidPackage('The manifest contains invalid XML:\n%s' % ex) pkg = Package(filename) # verify unique root node nodes = _get_nodes(root, 'package') if len(nodes) != 1: raise InvalidPackage('The manifest must contain a single "package" root tag') root = nodes[0] # format attribute value = _get_node_attr(root, 'format', default=1) pkg.package_format = int(value) assert pkg.package_format in [1, 2], "Unable to handle package.xml format version '%d', please update catkin_pkg (e.g. on Ubuntu/Debian use: sudo apt-get update && sudo apt-get install --only-upgrade python-catkin-pkg)" % pkg.package_format # name pkg.name = _get_node_value(_get_node(root, 'name')) # version and optional abi version_node = _get_node(root, 'version') pkg.version = _get_node_value(version_node) pkg.version_abi = _get_node_attr(version_node, 'abi', default=None) # description pkg.description = _get_node_value(_get_node(root, 'description'), allow_xml=True, apply_str=False) # at least one maintainer, all must have email maintainers = _get_nodes(root, 'maintainer') for node in maintainers: pkg.maintainers.append(Person( _get_node_value(node, apply_str=False), _get_node_attr(node, 'email') )) # urls with optional type urls = _get_nodes(root, 'url') for node in urls: pkg.urls.append(Url( _get_node_value(node), _get_node_attr(node, 'type', default='website') )) # authors with optional email authors = _get_nodes(root, 'author') for node in authors: pkg.authors.append(Person( _get_node_value(node, apply_str=False), _get_node_attr(node, 'email', default=None) )) # at least one license licenses = _get_nodes(root, 'license') for node in licenses: pkg.licenses.append(_get_node_value(node)) errors = [] # dependencies and relationships pkg.build_depends = _get_dependencies(root, 'build_depend') pkg.buildtool_depends = _get_dependencies(root, 'buildtool_depend') if pkg.package_format == 1: run_depends = _get_dependencies(root, 'run_depend') for d in run_depends: pkg.build_export_depends.append(deepcopy(d)) pkg.exec_depends.append(deepcopy(d)) if pkg.package_format == 2: pkg.build_export_depends = _get_dependencies(root, 'build_export_depend') pkg.buildtool_export_depends = _get_dependencies(root, 'buildtool_export_depend') pkg.exec_depends = _get_dependencies(root, 'exec_depend') depends = _get_dependencies(root, 'depend') for dep in depends: # check for collisions with specific dependencies same_build_depends = ['build_depend' for d in pkg.build_depends if d.name == dep.name] same_build_export_depends = ['build_export_depend' for d in pkg.build_export_depends if d.name == dep.name] same_exec_depends = ['exec_depend' for d in pkg.exec_depends if d.name == dep.name] if same_build_depends or same_build_export_depends or same_exec_depends: errors.append("The generic dependency on '%s' is redundant with: %s" % (dep.name, ', '.join(same_build_depends + same_build_export_depends + same_exec_depends))) # only append non-duplicates if not same_build_depends: pkg.build_depends.append(deepcopy(dep)) if not same_build_export_depends: pkg.build_export_depends.append(deepcopy(dep)) if not same_exec_depends: pkg.exec_depends.append(deepcopy(dep)) pkg.doc_depends = _get_dependencies(root, 'doc_depend') pkg.test_depends = _get_dependencies(root, 'test_depend') pkg.conflicts = _get_dependencies(root, 'conflict') pkg.replaces = _get_dependencies(root, 'replace') if pkg.package_format == 1: for test_depend in pkg.test_depends: same_build_depends = ['build_depend' for d in pkg.build_depends if d.name == test_depend.name] same_run_depends = ['run_depend' for d in pkg.run_depends if d.name == test_depend.name] if same_build_depends or same_run_depends: errors.append('The test dependency on "%s" is redundant with: %s' % (test_depend.name, ', '.join(same_build_depends + same_run_depends))) # exports export_node = _get_optional_node(root, 'export') if export_node is not None: exports = [] for node in [n for n in export_node.childNodes if n.nodeType == n.ELEMENT_NODE]: export = Export(str(node.tagName), _get_node_value(node, allow_xml=True)) for key, value in node.attributes.items(): export.attributes[str(key)] = str(value) exports.append(export) pkg.exports = exports # verify that no unsupported tags and attributes are present errors += _check_known_attributes(root, ['format']) depend_attributes = ['version_lt', 'version_lte', 'version_eq', 'version_gte', 'version_gt'] known = { 'name': [], 'version': ['abi'], 'description': [], 'maintainer': ['email'], 'license': [], 'url': ['type'], 'author': ['email'], 'build_depend': depend_attributes, 'buildtool_depend': depend_attributes, 'test_depend': depend_attributes, 'conflict': depend_attributes, 'replace': depend_attributes, 'export': [], } if pkg.package_format == 1: known.update({ 'run_depend': depend_attributes, }) if pkg.package_format == 2: known.update({ 'build_export_depend': depend_attributes, 'buildtool_export_depend': depend_attributes, 'depend': depend_attributes, 'exec_depend': depend_attributes, 'doc_depend': depend_attributes, }) nodes = [n for n in root.childNodes if n.nodeType == n.ELEMENT_NODE] unknown_tags = set([n.tagName for n in nodes if n.tagName not in known.keys()]) if unknown_tags: errors.append('The manifest (with format version %d) must not contain the following tags: %s' % (pkg.package_format, ', '.join(unknown_tags))) for node in [n for n in nodes if n.tagName in known.keys()]: errors += _check_known_attributes(node, known[node.tagName]) if node.tagName not in ['description', 'export']: subnodes = [n for n in node.childNodes if n.nodeType == n.ELEMENT_NODE] if subnodes: errors.append('The "%s" tag must not contain the following children: %s' % (node.tagName, ', '.join([n.tagName for n in subnodes]))) if errors: raise InvalidPackage('Error(s) in %s:%s' % (filename, ''.join(['\n- %s' % e for e in errors]))) pkg.validate(warnings=warnings) return pkg def _get_nodes(parent, tagname): return [n for n in parent.childNodes if n.nodeType == n.ELEMENT_NODE and n.tagName == tagname] def _get_node(parent, tagname): nodes = _get_nodes(parent, tagname) if len(nodes) != 1: raise InvalidPackage('The manifest must contain exactly one "%s" tags' % tagname) return nodes[0] def _get_optional_node(parent, tagname): nodes = _get_nodes(parent, tagname) if len(nodes) > 1: raise InvalidPackage('The manifest must not contain more than one "%s" tags' % tagname) return nodes[0] if nodes else None def _get_node_value(node, allow_xml=False, apply_str=True): if allow_xml: value = (''.join([n.toxml() for n in node.childNodes])).strip(' \n\r\t') else: value = (''.join([n.data for n in node.childNodes if n.nodeType == n.TEXT_NODE])).strip(' \n\r\t') if apply_str: value = str(value) return value def _get_optional_node_value(parent, tagname, default=None): node = _get_optional_node(parent, tagname) if node is None: return default return _get_node_value(node) def _get_node_attr(node, attr, default=False): """ :param default: False means value is required """ if node.hasAttribute(attr): return str(node.getAttribute(attr)) if default is False: raise InvalidPackage('The "%s" tag must have the attribute "%s"' % (node.tagName, attr)) return default def _get_dependencies(parent, tagname): depends = [] for node in _get_nodes(parent, tagname): depend = Dependency(_get_node_value(node)) for attr in ['version_lt', 'version_lte', 'version_eq', 'version_gte', 'version_gt']: setattr(depend, attr, _get_node_attr(node, attr, None)) depends.append(depend) return depends catkin_pkg-0.3.9/src/catkin_pkg/package_templates.py000066400000000000000000000435151317611606000225720ustar00rootroot00000000000000# 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. from __future__ import print_function import getpass import os import string import sys from catkin_pkg.cmake import configure_file from catkin_pkg.cmake import get_metapackage_cmake_template_path from catkin_pkg.package import Dependency from catkin_pkg.package import Package from catkin_pkg.package import PACKAGE_MANIFEST_FILENAME from catkin_pkg.package import Person class PackageTemplate(Package): def __init__(self, catkin_deps=None, system_deps=None, boost_comps=None, **kwargs): super(PackageTemplate, self).__init__(**kwargs) self.catkin_deps = catkin_deps or [] self.system_deps = system_deps or [] self.boost_comps = boost_comps or [] self.validate() @staticmethod def _create_package_template(package_name, description=None, licenses=None, maintainer_names=None, author_names=None, version=None, catkin_deps=None, system_deps=None, boost_comps=None): """ alternative factory method mapping CLI args to argument for Package class :param package_name: :param description: :param licenses: :param maintainer_names: :param authors: :param version: :param catkin_deps: """ # Sort so they are alphebetical licenses = list(licenses or ["TODO"]) licenses.sort() if not maintainer_names: maintainer_names = [getpass.getuser()] maintainer_names = list(maintainer_names or []) maintainer_names.sort() maintainers = [] for maintainer_name in maintainer_names: maintainers.append( Person(maintainer_name, '%s@todo.todo' % maintainer_name.split()[-1]) ) author_names = list(author_names or []) author_names.sort() authors = [] for author_name in author_names: authors.append(Person(author_name)) catkin_deps = list(catkin_deps or []) catkin_deps.sort() pkg_catkin_deps = [] depends = [] build_depends = [] exec_depends = [] buildtool_depends = [Dependency('catkin')] for dep in catkin_deps: if dep.lower() == 'catkin': catkin_deps.remove(dep) continue if dep.lower() == 'genmsg': sys.stderr.write('WARNING: Packages with messages or services should not depend on genmsg, but on message_generation and message_runtime\n') buildtool_depends.append(Dependency('genmsg')) continue if dep.lower() == 'message_generation': if not 'message_runtime' in catkin_deps: sys.stderr.write('WARNING: Packages with messages or services should depend on both message_generation and message_runtime\n') build_depends.append(Dependency('message_generation')) continue if dep.lower() == 'message_runtime': if not 'message_generation' in catkin_deps: sys.stderr.write('WARNING: Packages with messages or services should depend on both message_generation and message_runtime\n') exec_depends.append(Dependency('message_runtime')) continue pkg_catkin_deps.append(Dependency(dep)) for dep in pkg_catkin_deps: depends.append(dep) if boost_comps: if not system_deps: system_deps = ['boost'] elif not 'boost' in system_deps: system_deps.append('boost') for dep in system_deps or []: if not dep.lower().startswith('python-'): depends.append(Dependency(dep)) else: exec_depends.append(Dependency(dep)) package_temp = PackageTemplate( name=package_name, version=version or '0.0.0', description=description or 'The %s package' % package_name, buildtool_depends=buildtool_depends, build_depends=build_depends, depends=depends, exec_depends=exec_depends, catkin_deps=catkin_deps, system_deps=system_deps, boost_comps=boost_comps, licenses=licenses, authors=authors, maintainers=maintainers, urls=[]) return package_temp def read_template_file(filename, rosdistro): template_dir = os.path.join(os.path.dirname(__file__), 'templates') templates = [] templates.append(os.path.join(template_dir, rosdistro, '%s.in' % filename)) templates.append(os.path.join(template_dir, '%s.in' % filename)) for template in templates: if os.path.isfile(template): with open(template, 'r') as fhand: template_contents = fhand.read() return template_contents raise IOError( "Could not read template for ROS distro " "'{}' at '{}': ".format(rosdistro, ', '.join(templates)) + "no such file or directory" ) def _safe_write_files(newfiles, target_dir): """ writes file contents to target_dir/filepath for all entries of newfiles. Aborts early if files exist in places for new files or directories :param newfiles: a dict {filepath: contents} :param target_dir: a string """ # first check no filename conflict exists for filename in newfiles: target_file = os.path.join(target_dir, filename) if os.path.exists(target_file): raise ValueError('File exists: %s' % target_file) dirname = os.path.dirname(target_file) while(dirname != target_dir): if os.path.isfile(dirname): raise ValueError('Cannot create directory, file exists: %s' % dirname) dirname = os.path.dirname(dirname) for filename, content in newfiles.items(): target_file = os.path.join(target_dir, filename) dirname = os.path.dirname(target_file) if not os.path.exists(dirname): os.makedirs(dirname) # print(target_file, content) with open(target_file, 'ab') as fhand: fhand.write(content.encode()) print('Created file %s' % os.path.relpath(target_file, os.path.dirname(target_dir))) def create_package_files(target_path, package_template, rosdistro, newfiles=None, meta=False): """ creates several files from templates to start a new package. :param target_path: parent folder where to create the package :param package_template: contains the required information :param rosdistro: name of the distro to look up respective template :param newfiles: dict {filepath: contents} for additional files to write """ if newfiles is None: newfiles = {} # allow to replace default templates when path string is equal manifest_path = os.path.join(target_path, PACKAGE_MANIFEST_FILENAME) if manifest_path not in newfiles: newfiles[manifest_path] = \ create_package_xml(package_template, rosdistro, meta=meta) cmake_path = os.path.join(target_path, 'CMakeLists.txt') if not cmake_path in newfiles: newfiles[cmake_path] = create_cmakelists(package_template, rosdistro, meta=meta) _safe_write_files(newfiles, target_path) if 'roscpp' in package_template.catkin_deps: fname = os.path.join(target_path, 'include', package_template.name) os.makedirs(fname) print('Created folder %s' % os.path.relpath(fname, os.path.dirname(target_path))) if 'roscpp' in package_template.catkin_deps or \ 'rospy' in package_template.catkin_deps: fname = os.path.join(target_path, 'src') os.makedirs(fname) print('Created folder %s' % os.path.relpath(fname, os.path.dirname(target_path))) class CatkinTemplate(string.Template): """subclass to use @ instead of $ as markers""" delimiter = '@' escape = '@' def create_cmakelists(package_template, rosdistro, meta=False): """ :param package_template: contains the required information :returns: file contents as string """ if meta: template_path = get_metapackage_cmake_template_path() temp_dict = {'name': package_template.name, 'metapackage_arguments': '' } return configure_file(template_path, temp_dict) else: cmakelists_txt_template = read_template_file('CMakeLists.txt', rosdistro) ctemp = CatkinTemplate(cmakelists_txt_template) if package_template.catkin_deps == []: components = '' else: components = ' COMPONENTS\n %s\n' % '\n '.join(package_template.catkin_deps) boost_find_package = \ ('' if not package_template.boost_comps else ('find_package(Boost REQUIRED COMPONENTS %s)\n' % ' '.join(package_template.boost_comps))) system_find_package = '' for sysdep in package_template.system_deps: if sysdep == 'boost': continue if sysdep.startswith('python-'): system_find_package += '# ' system_find_package += 'find_package(%s REQUIRED)\n' % sysdep # provide dummy values catkin_depends = (' '.join(package_template.catkin_deps) if package_template.catkin_deps else 'other_catkin_pkg') system_depends = (' '.join(package_template.system_deps) if package_template.system_deps else 'system_lib') message_pkgs = [pkg for pkg in package_template.catkin_deps if pkg.endswith('_msgs')] if message_pkgs: message_depends = '# %s' % '# '.join(message_pkgs) else: message_depends = '# std_msgs # Or other packages containing msgs' temp_dict = {'name': package_template.name, 'components': components, 'include_directories': _create_include_macro(package_template), 'boost_find': boost_find_package, 'systems_find': system_find_package, 'catkin_depends': catkin_depends, 'system_depends': system_depends, 'target_libraries': _create_targetlib_args(package_template), 'message_dependencies': message_depends } return ctemp.substitute(temp_dict) def _create_targetlib_args(package_template): result = '# ${catkin_LIBRARIES}\n' if package_template.boost_comps: result += '# ${Boost_LIBRARIES}\n' if package_template.system_deps: result += (''.join(['# ${%s_LIBRARIES}\n' % sdep for sdep in package_template.system_deps])) return result def _create_include_macro(package_template): includes = ['# include'] includes.append((' ' if package_template.catkin_deps else '# ') + '${catkin_INCLUDE_DIRS}') if package_template.boost_comps: includes.append(' ${Boost_INCLUDE_DIRS}') if package_template.system_deps: deplist = [] for sysdep in package_template.system_deps: if not sysdep.startswith('python-'): deplist.append(sysdep) if deplist: todo_incl = '# TODO: Check names of system library include directories' includes.append(todo_incl + (' (%s)' % ', '.join(deplist))) includes.extend([' ${%s_INCLUDE_DIRS}' % sysdep for sysdep in deplist]) result = '' if includes: result += '\n'.join(includes) return result def _create_depend_tag(dep_type, name, version_eq=None, version_lt=None, version_lte=None, version_gt=None, version_gte=None): """ Helper to create xml snippet for package.xml """ version_string = [] for key, var in {'version_eq': version_eq, 'version_lt': version_lt, 'version_lte': version_lte, 'version_gt': version_gt, 'version_gte': version_gte}.items(): if var is not None: version_string.append(' %s="%s"' % (key, var)) result = ' <%s%s>%s\n' % (dep_type, ''.join(version_string), name, dep_type) return result def create_package_xml(package_template, rosdistro, meta=False): """ :param package_template: contains the required information :returns: file contents as string """ package_xml_template = \ read_template_file(PACKAGE_MANIFEST_FILENAME, rosdistro) ctemp = CatkinTemplate(package_xml_template) temp_dict = {} for key in package_template.__slots__: temp_dict[key] = getattr(package_template, key) if package_template.version_abi: temp_dict['version_abi'] = ' abi="%s"' % package_template.version_abi else: temp_dict['version_abi'] = '' if not package_template.description: temp_dict['description'] = 'The %s package ...' % package_template.name licenses = [] for plicense in package_template.licenses: licenses.append(' %s\n' % plicense) temp_dict['licenses'] = ''.join(licenses) def get_person_tag(tagname, person): email_string = ( "" if person.email is None else 'email="%s"' % person.email ) return ' <%s %s>%s\n' % (tagname, email_string, person.name, tagname) maintainers = [] for maintainer in package_template.maintainers: maintainers.append(get_person_tag('maintainer', maintainer)) temp_dict['maintainers'] = ''.join(maintainers) urls = [] for url in package_template.urls: type_string = ("" if url.type is None else 'type="%s"' % url.type) urls.append(' %s\n' % (type_string, url.url)) temp_dict['urls'] = ''.join(urls) authors = [] for author in package_template.authors: authors.append(get_person_tag('author', author)) temp_dict['authors'] = ''.join(authors) dependencies = [] dep_map = { 'build_depend': package_template.build_depends, 'build_export_depend': package_template.build_export_depends, 'buildtool_depend': package_template.buildtool_depends, 'exec_depend': package_template.exec_depends, 'test_depend': package_template.test_depends, 'conflict': package_template.conflicts, 'replace': package_template.replaces } for dep_type in ['buildtool_depend', 'build_depend', 'build_export_depend', 'exec_depend', 'test_depend', 'conflict', 'replace']: for dep in sorted(dep_map[dep_type], key=lambda x: x.name): if 'depend' in dep_type: dep_tag = _create_depend_tag( dep_type, dep.name, dep.version_eq, dep.version_lt, dep.version_lte, dep.version_gt, dep.version_gte ) dependencies.append(dep_tag) else: dependencies.append(_create_depend_tag(dep_type, dep.name)) temp_dict['dependencies'] = ''.join(dependencies) exports = [] if package_template.exports is not None: for export in package_template.exports: if export.content is not None: print('WARNING: Create package does not know how to ' 'serialize exports with content: ' '%s, %s, ' % (export.tagname, export.attributes) + '%s' % (export.content), file=sys.stderr) else: attribs = [' %s="%s"' % (k, v) for (k, v) in export.attributes.items()] line = ' <%s%s/>\n' % (export.tagname, ''.join(attribs)) exports.append(line) if meta: exports.append(' ') temp_dict['exports'] = ''.join(exports) temp_dict['components'] = package_template.catkin_deps return ctemp.substitute(temp_dict) catkin_pkg-0.3.9/src/catkin_pkg/package_version.py000066400000000000000000000050501317611606000222510ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2013, Open Source Robotics Foundation, 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 Open Source Robotics Foundation, 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. import re def bump_version(version, bump='patch'): """ Increases version number. :param version: str, must be in version format "int.int.int" :param bump: str, one of 'patch, minor, major' :returns: version with the given part increased, and all inferior parts reset to 0 :raises ValueError: if the version string is not in the format x.y.z """ # split the version number match = re.match('^(\d+)\.(\d+)\.(\d+)$', version) if match is None: raise ValueError('Invalid version string, must be int.int.int: "%s"' % version) new_version = match.groups() new_version = [int(x) for x in new_version] # find the desired index idx = dict(major=0, minor=1, patch=2)[bump] # increment the desired part new_version[idx] += 1 # reset all parts behind the bumped part new_version = new_version[:idx + 1] + [0 for x in new_version[idx + 1:]] return '%d.%d.%d' % tuple(new_version) catkin_pkg-0.3.9/src/catkin_pkg/packages.py000066400000000000000000000165161317611606000207000ustar00rootroot00000000000000# 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. """ Library to find packages in the filesystem. """ import multiprocessing import os from .package import _get_package_xml from .package import PACKAGE_MANIFEST_FILENAME from .package import parse_package_string def find_package_paths(basepath, exclude_paths=None, exclude_subspaces=False): """ Crawls the filesystem to find package manifest files. When a subfolder contains a file ``CATKIN_IGNORE`` it is ignored. :param basepath: The path to search in, ``str`` :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`` :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 'CATKIN_IGNORE' in filenames or \ os.path.realpath(dirpath) in real_exclude_paths or \ (exclude_subspaces and '.catkin' in filenames): del dirnames[:] continue elif PACKAGE_MANIFEST_FILENAME in filenames: paths.append(os.path.relpath(dirpath, basepath)) del dirnames[:] continue # filter out hidden directories in-place dirnames[:] = [d for d in dirnames if not d.startswith('.')] return paths def find_packages(basepath, exclude_paths=None, exclude_subspaces=False, warnings=None): """ Crawls the filesystem to find package manifest files and parses them. :param basepath: The path to search in, ``str`` :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 :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) package_paths_by_name = {} for path, package in packages.items(): if package.name not in package_paths_by_name: package_paths_by_name[package.name] = set([]) package_paths_by_name[package.name].add(path) duplicates = dict([(name, paths) for name, paths in package_paths_by_name.items() if len(paths) > 1]) if duplicates: duplicates = ['Multiple packages found with the same name "%s":%s' % (name, ''.join(['\n- %s' % path_ for path_ in sorted(duplicates[name])])) for name in sorted(duplicates.keys())] raise RuntimeError('\n'.join(duplicates)) return packages class _PackageParser(object): def __init__(self, capture_warnings): self.capture_warnings = capture_warnings def __call__(self, xml_and_path_and_filename): xml, path, filename = xml_and_path_and_filename warnings = [] if self.capture_warnings else None parsed_package = parse_package_string(xml, filename=filename, warnings=warnings) return (path, parsed_package), warnings def find_packages_allowing_duplicates(basepath, exclude_paths=None, exclude_subspaces=False, warnings=None): """ Crawls the filesystem to find package manifest files and parses them. :param basepath: The path to search in, ``str`` :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 :returns: A dict mapping relative paths to ``Package`` objects ``dict`` """ package_paths = find_package_paths(basepath, exclude_paths=exclude_paths, exclude_subspaces=exclude_subspaces) xmls = {} for path in package_paths: xmls[path] = _get_package_xml(os.path.join(basepath, path)) data = [(v[0], k, v[1]) for k, v in xmls.items()] if not data: return {} parallel = len(data) > 100 if parallel: try: pool = multiprocessing.Pool() except OSError: # On chroot environment, multiprocessing is not available # https://stackoverflow.com/questions/6033599/oserror-38-errno-38-with-multiprocessing parallel = False if not parallel: # use sequential loop parsed_packages = {} for xml, path, filename in data: parsed_package = parse_package_string( xml, filename=filename, warnings=warnings) parsed_packages[path] = parsed_package return parsed_packages # use multiprocessing pool parser = _PackageParser(warnings is not None) try: path_parsed_packages, warnings_lists = zip(*pool.map(parser, data)) finally: pool.close() pool.join() if parser.capture_warnings: map(warnings.extend, warnings_lists) return dict(path_parsed_packages) def verify_equal_package_versions(packages): """ Verifies that all packages have the same version number. :param packages: The list of ``Package`` objects, ``list`` :returns: The version number :raises: :exc:RuntimeError` If the version is not equal in all packages """ version = None for package in packages: if version is None: version = package.version elif package.version != version: raise RuntimeError('Two packages have different version numbers (%s != %s):\n- %s\n- %s' % (package.version, version, package.filename, packages[0].filename)) return version catkin_pkg-0.3.9/src/catkin_pkg/python_setup.py000066400000000000000000000135471317611606000216640ustar00rootroot00000000000000# 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. """ Library for providing the relevant information from the package manifest for the Python setup.py file. """ from __future__ import print_function import os import sys from .package import InvalidPackage, parse_package def generate_distutils_setup(package_xml_path=os.path.curdir, **kwargs): """ Extract the information relevant for distutils from the package manifest. The following keys will be set: The "name" and "version" are taken from the eponymous tags. A single maintainer will set the keys "maintainer" and "maintainer_email" while multiple maintainers are merged into the "maintainer" fields (including their emails). Authors are handled likewise. The first URL of type "website" (or without a type) is used for 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. All licenses are merged into the "license" field. :param kwargs: All keyword arguments are passed through. The above mentioned keys are verified to be identical if passed as a keyword argument :returns: return dict populated with parsed fields and passed keyword arguments :raises: :exc:`InvalidPackage` :raises: :exc:`IOError` """ package = parse_package(package_xml_path) data = {} data['name'] = package.name data['version'] = package.version # either set one author with one email or join all in a single field if len(package.authors) == 1 and package.authors[0].email is not None: data['author'] = package.authors[0].name data['author_email'] = package.authors[0].email else: data['author'] = ', '.join([('%s <%s>' % (a.name, a.email) if a.email is not None else a.name) for a in package.authors]) # either set one maintainer with one email or join all in a single field if len(package.maintainers) == 1: data['maintainer'] = package.maintainers[0].name data['maintainer_email'] = package.maintainers[0].email else: data['maintainer'] = ', '.join(['%s <%s>' % (m.name, m.email) for m in package.maintainers]) # either set the first URL with the type 'website' or the first URL of any type websites = [url.url for url in package.urls if url.type == 'website'] if websites: data['url'] = websites[0] 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 data['license'] = ', '.join(package.licenses) # pass keyword arguments and verify equality if generated and passed in for k, v in kwargs.items(): if k in data: if v != data[k]: raise InvalidPackage('The keyword argument "%s" does not match the information from package.xml: "%s" != "%s"' % (k, v, data[k])) else: data[k] = v return data def get_global_bin_destination(): return 'bin' def get_global_etc_destination(): return 'etc' def get_global_include_destination(): return 'include' def get_global_lib_destination(): return 'lib' def get_global_libexec_destination(): return 'lib' def get_global_python_destination(): dest = 'lib/python%u.%u/' % (sys.version_info[0], sys.version_info[1]) if '--install-layout=deb' not in sys.argv[1:]: dest += 'site-packages' else: dest += 'dist-packages' return dest def get_global_share_destination(): return 'share' def get_package_bin_destination(pkgname): return os.path.join(get_global_libexec_destination(), pkgname) def get_package_etc_destination(pkgname): return os.path.join(get_global_etc_destination(), pkgname) def get_package_include_destination(pkgname): return os.path.join(get_global_include_destination(), pkgname) def get_package_lib_destination(_pkgname): return get_global_lib_destination() def get_package_python_destination(pkgname): return os.path.join(get_global_python_destination(), pkgname) def get_package_share_destination(pkgname): return os.path.join(get_global_share_destination(), pkgname) catkin_pkg-0.3.9/src/catkin_pkg/rospack.py000066400000000000000000000036671317611606000205670ustar00rootroot00000000000000# 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. """ API provided for rospack to reorder include/library paths according to the chained workspaces """ from __future__ import print_function from .workspaces import get_spaces, order_paths def reorder_paths(paths): paths_to_order = paths.split(' ') if paths else [] ordered_paths = order_paths(paths_to_order, get_spaces()) return ' '.join(ordered_paths) catkin_pkg-0.3.9/src/catkin_pkg/templates/000077500000000000000000000000001317611606000205355ustar00rootroot00000000000000catkin_pkg-0.3.9/src/catkin_pkg/templates/CMakeLists.txt.in000066400000000000000000000151151317611606000237050ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.3) project(@name) ## Compile as C++11, supported in ROS Kinetic and newer # add_compile_options(-std=c++11) ## Find catkin macros and libraries ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) ## is used, also find other catkin packages find_package(catkin REQUIRED@components) ## System dependencies are found with CMake's conventions # find_package(Boost REQUIRED COMPONENTS system) @boost_find@systems_find ## Uncomment this if the package has a setup.py. This macro ensures ## modules and global scripts declared therein get installed ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html # catkin_python_setup() ################################################ ## Declare ROS messages, services and actions ## ################################################ ## To declare and build messages, services or actions from within this ## package, follow these steps: ## * Let MSG_DEP_SET be the set of packages whose message types you use in ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). ## * In the file package.xml: ## * add a build_depend tag for "message_generation" ## * add a build_depend and a run_depend tag for each package in MSG_DEP_SET ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in ## but can be declared for certainty nonetheless: ## * add a run_depend tag for "message_runtime" ## * In this file (CMakeLists.txt): ## * add "message_generation" and every package in MSG_DEP_SET to ## find_package(catkin REQUIRED COMPONENTS ...) ## * add "message_runtime" and every package in MSG_DEP_SET to ## catkin_package(CATKIN_DEPENDS ...) ## * uncomment the add_*_files sections below as needed ## and list every .msg/.srv/.action file to be processed ## * uncomment the generate_messages entry below ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) ## Generate messages in the 'msg' folder # add_message_files( # FILES # Message1.msg # Message2.msg # ) ## Generate services in the 'srv' folder # add_service_files( # FILES # Service1.srv # Service2.srv # ) ## Generate actions in the 'action' folder # add_action_files( # FILES # Action1.action # Action2.action # ) ## Generate added messages and services with any dependencies listed here # generate_messages( # DEPENDENCIES @message_dependencies # ) ################################################ ## Declare ROS dynamic reconfigure parameters ## ################################################ ## To declare and build dynamic reconfigure parameters within this ## package, follow these steps: ## * In the file package.xml: ## * add a build_depend and a run_depend tag for "dynamic_reconfigure" ## * In this file (CMakeLists.txt): ## * add "dynamic_reconfigure" to ## find_package(catkin REQUIRED COMPONENTS ...) ## * uncomment the "generate_dynamic_reconfigure_options" section below ## and list every .cfg file to be processed ## Generate dynamic reconfigure parameters in the 'cfg' folder # generate_dynamic_reconfigure_options( # cfg/DynReconf1.cfg # cfg/DynReconf2.cfg # ) ################################### ## catkin specific configuration ## ################################### ## The catkin_package macro generates cmake config files for your package ## Declare things to be passed to dependent projects ## INCLUDE_DIRS: uncomment this if your package contains header files ## LIBRARIES: libraries you create in this project that dependent projects also need ## CATKIN_DEPENDS: catkin_packages dependent projects also need ## DEPENDS: system dependencies of this project that dependent projects also need catkin_package( # INCLUDE_DIRS include # LIBRARIES @{name} # CATKIN_DEPENDS @catkin_depends # DEPENDS @system_depends ) ########### ## Build ## ########### ## Specify additional locations of header files ## Your package locations should be listed before other locations include_directories( @include_directories ) ## Declare a C++ library # add_library(${PROJECT_NAME} # src/${PROJECT_NAME}/@name.cpp # ) ## Add cmake target dependencies of the library ## as an example, code may need to be generated before libraries ## either from message generation or dynamic reconfigure # add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) ## Declare a C++ executable ## With catkin_make all packages are built within a single CMake context ## The recommended prefix ensures that target names across packages don't collide # add_executable(${PROJECT_NAME}_node src/@{name}_node.cpp) ## Rename C++ executable without prefix ## The above recommended prefix causes long target names, the following renames the ## target back to the shorter version for ease of user use ## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" # set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") ## Add cmake target dependencies of the executable ## same as for the library above # add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) ## Specify libraries to link a library or executable target against # target_link_libraries(${PROJECT_NAME}_node @target_libraries# ) ############# ## Install ## ############# # all install targets should use catkin DESTINATION variables # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html ## Mark executable scripts (Python etc.) for installation ## in contrast to setup.py, you can choose the destination # install(PROGRAMS # scripts/my_python_script # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # ) ## Mark executables and/or libraries for installation # install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # ) ## Mark cpp header files for installation # install(DIRECTORY include/${PROJECT_NAME}/ # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} # FILES_MATCHING PATTERN "*.h" # PATTERN ".svn" EXCLUDE # ) ## Mark other files for installation (e.g. launch and bag files, etc.) # install(FILES # # myfile1 # # myfile2 # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} # ) ############# ## Testing ## ############# ## Add gtest based cpp test target and link libraries # catkin_add_gtest(${PROJECT_NAME}-test test/test_@name.cpp) # if(TARGET ${PROJECT_NAME}-test) # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) # endif() ## Add folders to be run by python nosetests # catkin_add_nosetests(test) catkin_pkg-0.3.9/src/catkin_pkg/templates/metapackage.cmake.in000066400000000000000000000002001317611606000243760ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.3) project(@name@) find_package(catkin REQUIRED) catkin_metapackage(@metapackage_arguments@) catkin_pkg-0.3.9/src/catkin_pkg/templates/package.xml.in000066400000000000000000000045551317611606000232700ustar00rootroot00000000000000 @name @version @description @maintainers @licenses @urls @authors @dependencies @exports catkin_pkg-0.3.9/src/catkin_pkg/tool_detection.py000066400000000000000000000062071317611606000221310ustar00rootroot00000000000000# Software License Agreement (BSD License) # # Copyright (c) 2015, Open Source Robotics Foundation, 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. """ Common functions that can be used to mark spaces, e.g. build and devel, to indicate which tools previously built the space. This allows the tools to detect cross tool talk and avoid it where appropriate. """ from __future__ import print_function import os SPACE_BUILT_BY_MARKER_FILENAME = '.built_by' def get_previous_tool_used_on_the_space(space_path): """ Return the tool used to build the space at the given path, or None. Returns None if the path does not exist or if there is no built by file. :param str space_path: path to the space in question. :returns: str identifying the tool used to build the space or None. """ if os.path.isdir(space_path): marker_path = os.path.join(space_path, SPACE_BUILT_BY_MARKER_FILENAME) if os.path.isfile(marker_path): with open(marker_path, 'r') as f: return f.read().strip() return None def mark_space_as_built_by(space_path, tool_name): """ Place a marker file in the space at the given path, telling who built it. The path to the marker is created if necessary. :param str space_path: path to the space which should be marked. :param str tool_name: name of the tool doing the marking. :raises: OSError, others, when trying to create the folder. """ if not os.path.isdir(space_path): # Might fail if it's a file already or for permissions. os.makedirs(space_path) marker_path = os.path.join(space_path, SPACE_BUILT_BY_MARKER_FILENAME) with open(marker_path, 'w') as f: f.write(tool_name) catkin_pkg-0.3.9/src/catkin_pkg/topological_order.py000066400000000000000000000303741317611606000226270ustar00rootroot00000000000000# 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. from __future__ import print_function import copy import sys from .packages import find_packages from .workspaces import get_spaces class _PackageDecorator(object): def __init__(self, package, path): self.package = package self.path = path self.is_metapackage = 'metapackage' in [e.tagname for e in self.package.exports] message_generators = [e.content for e in self.package.exports if e.tagname == 'message_generator'] self.message_generator = message_generators[0] if message_generators else None # full includes direct build depends and recursive run_depends of these build_depends self.depends_for_topological_order = None def __getattr__(self, name): if name.startswith('__'): raise AttributeError(name) return getattr(self.package, name) def calculate_depends_for_topological_order(self, packages): """ Sets self.depends_for_topological_order to the recursive dependencies required for topological order. It contains all direct build- and buildtool dependencies and their recursive runtime dependencies. The set only contains packages which are in the passed packages dictionary. :param packages: dict of name to ``_PackageDecorator`` """ self.depends_for_topological_order = set([]) all_depends = self.package.build_depends + self.package.buildtool_depends + self.package.test_depends # skip external dependencies, meaning names that are not known packages unique_depend_names = set([d.name for d in all_depends if d.name in packages.keys()]) for name in unique_depend_names: if not self.is_metapackage and packages[name].is_metapackage: print('WARNING: package "%s" should not depend on metapackage "%s" but on its packages instead' % (self.name, name), file=sys.stderr) if name in self.depends_for_topological_order: # avoid function call to improve performance # check within the loop since the set changes every cycle continue packages[name]._add_recursive_run_depends(packages, self.depends_for_topological_order) def _add_recursive_run_depends(self, packages, depends_for_topological_order): """ Modifies depends_for_topological_order argument by adding run_depends of self recursively. Only packages which are in the passed packages are added and recursed into. :param packages: dict of name to ``_PackageDecorator`` :param depends_for_topological_order: set to be extended """ depends_for_topological_order.add(self.package.name) package_names = packages.keys() for name in [d.name for d in self.package.run_depends if d.name in package_names]: if name in depends_for_topological_order: # avoid function call to improve performance # check within the loop since the set changes every cycle continue packages[name]._add_recursive_run_depends(packages, depends_for_topological_order) def topological_order(root_dir, whitelisted=None, blacklisted=None, underlay_workspaces=None): ''' Crawls the filesystem to find packages and uses their dependencies to return a topologically order list. When a circular dependency is detected, the last item in the returned list is a tuple with None and a string giving a superset of the guilty packages. :param root_dir: The path to search in, ``str`` :param whitelisted: A list of whitelisted package names, ``list`` :param blacklisted: A list of blacklisted package names, ``list`` :param underlay_workspaces: A list of underlay workspaces of packages which might provide dependencies in case of partial workspaces, ``list`` :returns: A list of tuples containing the relative path and a ``Package`` object, ``list`` ''' packages = find_packages(root_dir) # find packages in underlayed workspaces underlay_packages = {} if underlay_workspaces: for workspace in reversed(underlay_workspaces): # since underlay workspace might be a devel space # consider spaces stored in the .catkin file spaces = get_spaces([workspace]) for space in spaces: for path, package in find_packages(space).items(): underlay_packages[package.name] = (path, package) return topological_order_packages(packages, whitelisted=whitelisted, blacklisted=blacklisted, underlay_packages=dict(underlay_packages.values())) def topological_order_packages(packages, whitelisted=None, blacklisted=None, underlay_packages=None): ''' Topologically orders packages. First returning packages which have message generators and then the rest based on direct build-/buildtool_depends and indirect recursive run_depends. When a circular dependency is detected, the last item in the returned list is a tuple with None and a string giving a superset of the guilty packages. :param packages: A dict mapping relative paths to ``Package`` objects ``dict`` :param whitelisted: A list of whitelisted package names, ``list`` :param blacklisted: A list of blacklisted package names, ``list`` :param underlay_packages: A dict mapping relative paths to ``Package`` objects ``dict`` :returns: A list of tuples containing the relative path and a ``Package`` object, ``list`` ''' decorators_by_name = {} for path, package in packages.items(): # skip non-whitelisted packages if whitelisted and package.name not in whitelisted: continue # skip blacklisted packages if blacklisted and package.name in blacklisted: continue if package.name in decorators_by_name: path_with_same_name = decorators_by_name[package.name].path raise RuntimeError('Two packages with the same name "%s" in the workspace:\n- %s\n- %s' % (package.name, path_with_same_name, path)) decorators_by_name[package.name] = _PackageDecorator(package, path) underlay_decorators_by_name = {} if underlay_packages: for path, package in underlay_packages.items(): # skip overlayed packages if package.name in decorators_by_name: continue underlay_decorators_by_name[package.name] = _PackageDecorator(package, path) decorators_by_name.update(underlay_decorators_by_name) # calculate transitive dependencies for decorator in decorators_by_name.values(): decorator.calculate_depends_for_topological_order(decorators_by_name) tuples = _sort_decorated_packages(decorators_by_name) # remove underlay packages from result return [(path, package) for path, package in tuples if path is None or package.name not in underlay_decorators_by_name] def _reduce_cycle_set(packages_orig): ''' This function iteratively removes some packages from a set that are definitely not part of any cycle. When there is a cycle in the package dependencies, _sort_decorated_packages only knows the set of packages containing the cycle. :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) packages = copy.copy(packages_orig) last_depended = None while len(packages) > 0: depended = set([]) for name, decorator in packages.items(): if decorator.depends_for_topological_order: depended = depended.union(decorator.depends_for_topological_order) for name in list(packages.keys()): if not name in depended: del packages[name] if last_depended: if last_depended == depended: return packages.keys() last_depended = depended def _sort_decorated_packages(packages_orig): ''' Sorts packages according to dependency ordering, first considering the message generators and their recursive dependencies and then the rest of the packages. When a circle is detected, a tuple with None and a string giving a superset of the guilty packages. :param packages: A dict mapping package name to ``_PackageDecorator`` objects ``dict`` :returns: A List of tuples containing the relative path and a ``Package`` object ``list`` ''' packages = copy.deepcopy(packages_orig) # mark all packages which are (recursively) dependent on by message generators dependency_names_to_follow = set([name for name, decorator in packages.items() if decorator.message_generator]) not_marked_package_names = set(packages.keys()) - dependency_names_to_follow while dependency_names_to_follow: pkg_name = dependency_names_to_follow.pop() for name in packages[pkg_name].depends_for_topological_order: if name in not_marked_package_names: # mark package packages[name].message_generator = True not_marked_package_names.remove(name) # queue for recursion dependency_names_to_follow.add(name) ordered_packages = [] while len(packages) > 0: # find all packages without build dependencies message_generators = [] non_message_generators = [] for name, decorator in packages.items(): if not decorator.depends_for_topological_order: if decorator.message_generator: message_generators.append(name) else: non_message_generators.append(name) # first choose message generators if message_generators: names = message_generators elif non_message_generators: names = non_message_generators else: # in case of a circular dependency pass a string with # the names list of remaining package names, with path # None to indicate cycle ordered_packages.append([None, ', '.join(sorted(_reduce_cycle_set(packages)))]) break # alphabetic order only for convenience names.sort() # add first candidates to ordered list # do not add all candidates since removing the depends from the first might affect the next candidates name = names[0] ordered_packages.append([packages[name].path, packages[name].package]) # remove package from further processing del packages[name] for package in packages.values(): if name in package.depends_for_topological_order: package.depends_for_topological_order.remove(name) return ordered_packages catkin_pkg-0.3.9/src/catkin_pkg/workspaces.py000066400000000000000000000101121317611606000212650ustar00rootroot00000000000000# 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. """ Library to provided logic for chained workspaces """ from __future__ import print_function import os CATKIN_WORKSPACE_MARKER_FILE = '.catkin_workspace' def get_spaces(paths=None): """ Return a list of spaces based on the CMAKE_PREFIX_PATH or passed in list of workspaces. It resolves the source space for each devel space and ignores non-catkin paths. :param paths_to_order: list of paths :param prefix_paths: list of prefixes, must not end with '/' """ if paths is None: if 'CMAKE_PREFIX_PATH' not in os.environ: raise RuntimeError('Neither the environment variable CMAKE_PREFIX_PATH is set nor was a list of paths passed.') paths = os.environ['CMAKE_PREFIX_PATH'].split(os.pathsep) if os.environ['CMAKE_PREFIX_PATH'] else [] spaces = [] for path in paths: marker = os.path.join(path, '.catkin') # ignore non catkin paths if not os.path.exists(marker): continue spaces.append(path) # append source spaces with open(marker, 'r') as f: data = f.read() if data: spaces += data.split(';') return spaces def order_paths(paths_to_order, prefix_paths): """ Return a list containing all items of paths_to_order ordered by list of prefix_paths, compared as strings :param paths_to_order: list of paths :param prefix_paths: list of prefixes, must not end with '/' """ # the ordered paths contains a list for each prefix plus one more which contains paths which do not match one of the prefix_paths ordered_paths = [[] for _ in range(len(prefix_paths) + 1)] for path in paths_to_order: # put each directory into the slot where it matches the prefix, or last otherwise index = 0 for prefix in prefix_paths: if path == prefix or path.startswith(prefix + os.sep) or (os.altsep and path.startswith(prefix + os.altsep)): break index += 1 ordered_paths[index].append(path) # flatten list of lists return [j for i in ordered_paths for j in i] def ensure_workspace_marker(base_path): """ creates workspace marker file at path if not existing :param path: target folder """ if not os.path.exists(os.path.join(base_path, CATKIN_WORKSPACE_MARKER_FILE)): with open(os.path.join(base_path, CATKIN_WORKSPACE_MARKER_FILE), 'a') as fhand: fhand.write('# This file currently only serves to mark the location of a catkin workspace for tool integration\n') catkin_pkg-0.3.9/stdeb.cfg000066400000000000000000000017051317611606000154230ustar00rootroot00000000000000[catkin_pkg] Debian-Version: 100 Depends: python-argparse, python-catkin-pkg-modules, python-dateutil, python-docutils Depends3: python3-catkin-pkg-modules, python3-dateutil, python3-docutils Conflicts: catkin, python3-catkin-pkg Conflicts3: catkin, python-catkin-pkg Suite: oneiric precise quantal raring saucy trusty utopic vivid wily xenial yakkety zesty artful wheezy jessie stretch X-Python3-Version: >= 3.2 Setup-Env-Vars: SKIP_PYTHON_MODULES=1 [catkin_pkg_modules] Source: catkin_pkg_modules Depends: python-argparse, python-dateutil, python-docutils Depends3: python3-dateutil, python3-docutils Conflicts: catkin, python-catkin-pkg (<< 0.3.0) Conflicts3: catkin, python3-catkin-pkg (<< 0.3.0) Replaces: python-catkin-pkg (<< 0.3.0) Replaces3: python3-catkin-pkg (<< 0.3.0) Suite: oneiric precise quantal raring saucy trusty utopic vivid wily xenial yakkety zesty artful wheezy jessie stretch X-Python3-Version: >= 3.2 Setup-Env-Vars: SKIP_PYTHON_SCRIPTS=1 catkin_pkg-0.3.9/test/000077500000000000000000000000001317611606000146155ustar00rootroot00000000000000catkin_pkg-0.3.9/test/__init__.py000066400000000000000000000001041317611606000167210ustar00rootroot00000000000000import os import sys sys.path.insert(0, os.path.join('..', 'src')) catkin_pkg-0.3.9/test/data/000077500000000000000000000000001317611606000155265ustar00rootroot00000000000000catkin_pkg-0.3.9/test/data/metapackages/000077500000000000000000000000001317611606000201535ustar00rootroot00000000000000catkin_pkg-0.3.9/test/data/metapackages/NonConformingName/000077500000000000000000000000001317611606000235305ustar00rootroot00000000000000catkin_pkg-0.3.9/test/data/metapackages/NonConformingName/CMakeLists.txt000066400000000000000000000001641317611606000262710ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.3) project(NonConformingName) find_package(catkin REQUIRED) catkin_metapackage() catkin_pkg-0.3.9/test/data/metapackages/NonConformingName/package.xml000066400000000000000000000006371317611606000256530ustar00rootroot00000000000000 NonConformingName 0.1.0 valid_metapackage user BSD catkin foo bar baz catkin_pkg-0.3.9/test/data/metapackages/invalid_cmake/000077500000000000000000000000001317611606000227415ustar00rootroot00000000000000catkin_pkg-0.3.9/test/data/metapackages/invalid_cmake/CMakeLists.txt000066400000000000000000000001541317611606000255010ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.3) project(invalid_cmake) find_package(catkin REQUIRED) catkin_package() catkin_pkg-0.3.9/test/data/metapackages/invalid_cmake/package.xml000066400000000000000000000006261317611606000250620ustar00rootroot00000000000000 invalid_cmake 0.1.0 invalid_cmake user BSD catkin foo bar baz catkin_pkg-0.3.9/test/data/metapackages/invalid_depends/000077500000000000000000000000001317611606000233035ustar00rootroot00000000000000catkin_pkg-0.3.9/test/data/metapackages/invalid_depends/CMakeLists.txt000066400000000000000000000001621317611606000260420ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.3) project(invalid_depends) find_package(catkin REQUIRED) catkin_metapackage() catkin_pkg-0.3.9/test/data/metapackages/invalid_depends/package.xml000066400000000000000000000006771317611606000254320ustar00rootroot00000000000000 invalid_depends 0.1.0 invalid_depends user BSD catkin foo bar baz ping catkin_pkg-0.3.9/test/data/metapackages/leftover_files/000077500000000000000000000000001317611606000231635ustar00rootroot00000000000000catkin_pkg-0.3.9/test/data/metapackages/leftover_files/CMakeLists.txt000066400000000000000000000001611317611606000257210ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.3) project(leftover_files) find_package(catkin REQUIRED) catkin_metapackage() catkin_pkg-0.3.9/test/data/metapackages/leftover_files/Makefile000066400000000000000000000000441317611606000246210ustar00rootroot00000000000000all: echo 'not supposed to be here' catkin_pkg-0.3.9/test/data/metapackages/leftover_files/package.xml000066400000000000000000000006301317611606000252770ustar00rootroot00000000000000 leftover_files 0.1.0 leftover_files user BSD catkin foo bar baz catkin_pkg-0.3.9/test/data/metapackages/no_buildtool_depend_catkin/000077500000000000000000000000001317611606000255145ustar00rootroot00000000000000catkin_pkg-0.3.9/test/data/metapackages/no_buildtool_depend_catkin/CMakeLists.txt000066400000000000000000000001751317611606000302570ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.3) project(no_buildtool_depend_catkin) find_package(catkin REQUIRED) catkin_metapackage() catkin_pkg-0.3.9/test/data/metapackages/no_buildtool_depend_catkin/package.xml000066400000000000000000000006021317611606000276270ustar00rootroot00000000000000 no_buildtool_depend_catkin 0.1.0 no_buildtool_depend_catkin user BSD foo bar baz catkin_pkg-0.3.9/test/data/metapackages/no_cmake/000077500000000000000000000000001317611606000217275ustar00rootroot00000000000000catkin_pkg-0.3.9/test/data/metapackages/no_cmake/package.xml000066400000000000000000000006141317611606000240450ustar00rootroot00000000000000 no_cmake 0.1.0 no_cmake user BSD catkin foo bar baz catkin_pkg-0.3.9/test/data/metapackages/no_metapackage_tag/000077500000000000000000000000001317611606000237445ustar00rootroot00000000000000catkin_pkg-0.3.9/test/data/metapackages/no_metapackage_tag/CMakeLists.txt000066400000000000000000000001651317611606000265060ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.3) project(no_metapackage_tag) find_package(catkin REQUIRED) catkin_metapackage() catkin_pkg-0.3.9/test/data/metapackages/no_metapackage_tag/package.xml000066400000000000000000000005651317611606000260670ustar00rootroot00000000000000 no_metapackage_tag 0.1.0 no_metapackage_tag user BSD catkin foo bar baz catkin_pkg-0.3.9/test/data/metapackages/valid_metapackage/000077500000000000000000000000001317611606000235745ustar00rootroot00000000000000catkin_pkg-0.3.9/test/data/metapackages/valid_metapackage/CMakeLists.txt000066400000000000000000000001641317611606000263350ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.3) project(valid_metapackage) find_package(catkin REQUIRED) catkin_metapackage() catkin_pkg-0.3.9/test/data/metapackages/valid_metapackage/package.xml000066400000000000000000000006361317611606000257160ustar00rootroot00000000000000 valid_metapackage 0.1.0 valid_metapackage user BSD catkin foo bar baz catkin_pkg-0.3.9/test/test_catkin_create_pkg.py000066400000000000000000000043521317611606000216670ustar00rootroot00000000000000import os import unittest import tempfile import shutil try: from catkin_pkg.package_templates import PackageTemplate except ImportError as impe: raise ImportError( 'Please adjust your pythonpath before running this test: %s' % str(impe)) from catkin_pkg.cli.create_pkg import main class CreatePkgTest(unittest.TestCase): def test_create_package_template(self): template = PackageTemplate._create_package_template('foopackage') self.assertEqual('foopackage', template.name) self.assertEqual('0.0.0', template.version) self.assertEqual('The foopackage package', template.description) self.assertEqual([], template.catkin_deps) self.assertEqual([], template.authors) self.assertEqual(1, len(template.maintainers)) self.assertIsNotNone(template.maintainers[0].email) self.assertEqual([], template.urls) # with args template = PackageTemplate._create_package_template( 'foopackage', description='foo_desc', licenses=['a', 'b'], maintainer_names=['John Doe', 'Jim Daniels'], author_names=['Harry Smith'], version='1.2.3', catkin_deps=['foobar', 'baz']) self.assertEqual('foopackage', template.name) self.assertEqual('1.2.3', template.version) self.assertEqual('foo_desc', template.description) self.assertEqual(['baz', 'foobar'], template.catkin_deps) self.assertEqual(1, len(template.authors)) self.assertEqual('Jim Daniels', template.maintainers[0].name) self.assertEqual('John Doe', template.maintainers[1].name) self.assertEqual('Harry Smith', template.authors[0].name) self.assertEqual(2, len(template.maintainers)) self.assertEqual([], template.urls) def test_main(self): try: root_dir = tempfile.mkdtemp() main(['--rosdistro', 'groovy', 'foo'], root_dir) self.assertTrue(os.path.isdir(os.path.join(root_dir, 'foo'))) self.assertTrue(os.path.isfile(os.path.join(root_dir, 'foo', 'CMakeLists.txt'))) self.assertTrue(os.path.isfile(os.path.join(root_dir, 'foo', 'package.xml'))) finally: shutil.rmtree(root_dir) catkin_pkg-0.3.9/test/test_changelog.py000066400000000000000000000070641317611606000201640ustar00rootroot00000000000000# coding=utf-8 import unittest from catkin_pkg.changelog import BulletList from catkin_pkg.changelog import Changelog from catkin_pkg.changelog import example_rst from catkin_pkg.changelog import InvalidSectionTitle from catkin_pkg.changelog import MixedText from catkin_pkg.changelog import populate_changelog_from_rst from catkin_pkg.changelog import Transition from catkin_pkg.changelog import version_and_date_from_title class TestSectionTitleParsing(unittest.TestCase): """Tests the section title parsing""" def test_version_and_date_from_title(self): title = '0.1.26 (2012-12-26)' assert '0.1.26' == version_and_date_from_title(title)[0] title = '0.1' self.assertRaises(InvalidSectionTitle, version_and_date_from_title, title) title = '0.1.27 (forthcoming)' self.assertRaises(InvalidSectionTitle, version_and_date_from_title, title) title = ' 0.1.26 (2012-12-26)' self.assertRaises(InvalidSectionTitle, version_and_date_from_title, title) title = '0.1.26 (2012-12-26) ' self.assertRaises(InvalidSectionTitle, version_and_date_from_title, title) # TODO: Add some more sofisticated Date entries def check_0_1_26(content): assert len(content) == 1 assert type(content[0]) == BulletList assert len(content[0].bullets) == 3 def check_0_1_25(content): assert len(content) == 3 assert type(content[0]) == BulletList assert len(content[0].bullets) == 5 mtext = content[0].bullets[3] assert type(mtext) == MixedText assert len(mtext.texts) == 2 assert type(content[1]) == Transition assert type(content[2]) == MixedText def check_0_1_0(content): assert len(content) == 1 assert type(content[0]) == MixedText assert len(content[0].texts) == 4 def check_0_0_1(content): assert len(content) == 1 assert type(content[0]) == BulletList assert content[0].bullet_type == 'enumerated' def test_Changelog(): # Example is from REP-0132 changelog = Changelog('foo') populate_changelog_from_rst(changelog, example_rst) expected_versions = ['0.1.26', '0.1.25', '0.1.0', '0.0.1'] versions = [] content_checks = { '0.1.26': check_0_1_26, '0.1.25': check_0_1_25, '0.1.0': check_0_1_0, '0.0.1': check_0_0_1 } for version, date, content in changelog.foreach_version(): versions.append(version) if version in content_checks: content_checks[version](content) assert sorted(expected_versions) == sorted(versions) single_version_rst = """\ 0.0.1 (2012-01-31) ------------------ * Initial release * Initial bugs * Contributors: Sömeöne with UTF-8 in their name """ def test_single_version_Changelog(): changelog = Changelog('foo') populate_changelog_from_rst(changelog, single_version_rst) expected_versions = ['0.0.1'] versions = [] for version, date, content in changelog.foreach_version(): versions.append(version) assert sorted(expected_versions) == sorted(versions) str(changelog) single_version_with_header_rst = """\ ^^^^^^^^^^^^^^^^^^^^^^^^^ Changelog for package foo ^^^^^^^^^^^^^^^^^^^^^^^^^ 0.0.1 (2012-01-31) ------------------ * Initial release * Initial bugs """ def test_single_version_with_header_Changelog(): changelog = Changelog('foo') populate_changelog_from_rst(changelog, single_version_with_header_rst) expected_versions = ['0.0.1'] versions = [] for version, date, content in changelog.foreach_version(): versions.append(version) assert sorted(expected_versions) == sorted(versions) catkin_pkg-0.3.9/test/test_metapackage.py000066400000000000000000000071731317611606000205000ustar00rootroot00000000000000from __future__ import print_function import contextlib import os import re try: from cStringIO import StringIO except ImportError: from io import StringIO import sys import unittest from catkin_pkg.metapackage import get_expected_cmakelists_txt from catkin_pkg.metapackage import InvalidMetapackage from catkin_pkg.metapackage import validate_metapackage from catkin_pkg.packages import find_packages test_data_dir = os.path.join(os.path.dirname(__file__), 'data', 'metapackages') test_expectations = { # Test name: [ExceptionType or None, ExceptionRegex or None, WarningRegex or None] 'invalid_cmake': [InvalidMetapackage, 'Invalid CMakeLists.txt', None], 'invalid_depends': [InvalidMetapackage, 'Has build, buildtool, and/or test depends', None], 'leftover_files': [None, None, None], 'no_buildtool_depend_catkin': [InvalidMetapackage, 'No buildtool dependency on catkin', None], 'no_cmake': [InvalidMetapackage, 'No CMakeLists.txt', None], 'no_metapackage_tag': [InvalidMetapackage, 'No tag in ', None], 'NonConformingName': [None, None, None], 'valid_metapackage': [None, None, None] } test_expected_warnings = [ 'Metapackage "invalid_depends" should not have other dependencies besides ' 'a buildtool_depend on catkin and run_depends.', 'Metapackage "no_buildtool_depend_catkin" must buildtool_depend on ' 'catkin.', 'Package name "NonConformingName" does not follow the naming conventions. ' 'It should start with a lower case letter and only contain lower case ' 'letters, digits and underscores.'] @contextlib.contextmanager def assert_warning(warnreg): orig_stdout = sys.stdout orig_stderr = sys.stderr try: out = StringIO() sys.stdout = out sys.stderr = sys.stdout yield finally: if warnreg is not None: out = out.getvalue() assert re.search(warnreg, out) is not None, "'%s' does not match warning '%s'" % (warnreg, out) else: print(out) sys.stdout = orig_stdout 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""" def test_validate_metapackage(self): pkgs_dict = find_packages(test_data_dir) for path, package in pkgs_dict.items(): path = os.path.join(test_data_dir, path) assert package.name in test_expectations, 'Unknown test %s' % package.name exc, excreg, warnreg = test_expectations[package.name] with assert_warning(warnreg): if exc is not None: if excreg is not None: with self.assertRaisesRegexp(exc, excreg): _validate_metapackage(path, package) else: with self.assertRaises(exc): _validate_metapackage(path, package) else: _validate_metapackage(path, package) def test_collect_warnings(self): """Tests warnings collection""" warnings = [] pkgs_dict = find_packages(test_data_dir, warnings=warnings) self.assertEqual(warnings.sort(), test_expected_warnings.sort()) def test_get_expected_cmakelists_txt(): expected = '''\ cmake_minimum_required(VERSION 2.8.3) project(example) find_package(catkin REQUIRED) catkin_metapackage() ''' assert expected == get_expected_cmakelists_txt('example') catkin_pkg-0.3.9/test/test_package.py000066400000000000000000000226011317611606000176220ustar00rootroot00000000000000import unittest # Redirect stderr to stdout to suppress output in tests import sys sys.stderr = sys.stdout from catkin_pkg.package import Dependency, Export, InvalidPackage, Package, Person, _check_known_attributes import xml.dom.minidom as dom from mock import Mock class PackageTest(unittest.TestCase): def get_maintainer(self): maint = Mock() maint.email = 'foo@bar.com' maint.name = 'John Foo' return maint def test_init(self): maint = self.get_maintainer() pack = Package(name='foo', version='0.0.0', maintainers=[maint], licenses=['BSD']) self.assertEqual(None, pack.filename) self.assertEqual([], pack.urls) self.assertEqual([], pack.authors) self.assertEqual([maint], pack.maintainers) self.assertEqual(['BSD'], pack.licenses) self.assertEqual([], pack.build_depends) self.assertEqual([], pack.buildtool_depends) self.assertEqual([], pack.run_depends) self.assertEqual([], pack.test_depends) self.assertEqual([], pack.conflicts) self.assertEqual([], pack.replaces) self.assertEqual([], pack.exports) pack = Package('foo', name='bar', version='0.0.0', licenses=['BSD'], maintainers=[self.get_maintainer()]) self.assertEqual('foo', pack.filename) self.assertRaises(TypeError, Package, unknownattribute=42) def test_init_dependency(self): dep = Dependency('foo', version_lt=1, version_lte=2, version_eq=3, version_gte=4, version_gt=5) self.assertEquals('foo', dep.name) self.assertEquals(1, dep.version_lt) self.assertEquals(2, dep.version_lte) self.assertEquals(3, dep.version_eq) self.assertEquals(4, dep.version_gte) self.assertEquals(5, dep.version_gt) self.assertRaises(TypeError, Dependency, 'foo', unknownattribute=42) d = {} d[dep] = None dep2 = Dependency('foo', version_lt=1, version_lte=2, version_eq=3, version_gte=4, version_gt=5) d[dep2] = None self.assertEquals(len(d), 1) dep3 = Dependency('foo', version_lt=1, version_lte=2, version_eq=3, version_gte=4, version_gt=6) d[dep3] = None self.assertEquals(len(d), 2) def test_init_kwargs_string(self): pack = Package('foo', name='bar', package_format='1', version='0.0.0', version_abi='pabi', description='pdesc', licenses=['BSD'], maintainers=[self.get_maintainer()]) self.assertEqual('foo', pack.filename) self.assertEqual('bar', pack.name) self.assertEqual('1', pack.package_format) self.assertEqual('pabi', pack.version_abi) self.assertEqual('0.0.0', pack.version) self.assertEqual('pdesc', pack.description) def test_init_kwargs_object(self): mmain = [self.get_maintainer(), self.get_maintainer()] mlis = ['MIT', 'BSD'] mauth = [self.get_maintainer(), self.get_maintainer()] murl = [Mock(), Mock()] mbuilddep = [Mock(), Mock()] mbuildtooldep = [Mock(), Mock()] mrundep = [Mock(), Mock()] mtestdep = [Mock(), Mock()] mconf = [Mock(), Mock()] mrepl = [Mock(), Mock()] mexp = [Mock(), Mock()] pack = Package(name='bar', version='0.0.0', maintainers=mmain, licenses=mlis, urls=murl, authors=mauth, build_depends=mbuilddep, buildtool_depends=mbuildtooldep, run_depends=mrundep, test_depends=mtestdep, conflicts=mconf, replaces=mrepl, exports=mexp) self.assertEqual(mmain, pack.maintainers) self.assertEqual(mlis, pack.licenses) self.assertEqual(murl, pack.urls) self.assertEqual(mauth, pack.authors) self.assertEqual(mbuilddep, pack.build_depends) self.assertEqual(mbuildtooldep, pack.buildtool_depends) # since run_depends are getting stores as build_export_depends as well as exec_depends # and the dependency objects are being cloned only the double count can be checked for self.assertEqual(2 * len(mrundep), len(pack.run_depends)) self.assertEqual(mtestdep, pack.test_depends) self.assertEqual(mconf, pack.conflicts) self.assertEqual(mrepl, pack.replaces) self.assertEqual(mexp, pack.exports) def test_validate_package(self): maint = self.get_maintainer() pack = Package('foo', name='bar_2go', package_format='1', version='0.0.0', version_abi='pabi', description='pdesc', licenses=['BSD'], maintainers=[maint]) pack.validate() # names that should error pack.name = 'bar bza' self.assertRaises(InvalidPackage, Package.validate, pack) pack.name = 'foo%' self.assertRaises(InvalidPackage, Package.validate, pack) # names that should throw warnings pack.name = '2bar' warnings = [] pack.validate(warnings=warnings) self.assertIn('naming conventions', warnings[0]) pack.name = 'bar-bza' warnings = [] pack.validate(warnings=warnings) self.assertIn('naming conventions', warnings[0]) pack.name = 'BAR' warnings = [] pack.validate(warnings=warnings) self.assertIn('naming conventions', warnings[0]) # dashes are permitted for a non-catkin package pack.exports.append(Export('build_type', 'other')) pack.name = 'bar-bza' warnings = [] pack.validate(warnings=warnings) self.assertEquals(warnings, []) pack.exports.pop() # check authors emails pack.name = 'bar' auth1 = Mock() auth2 = Mock() auth2.validate.side_effect = InvalidPackage('foo') pack.authors = [auth1, auth2] self.assertRaises(InvalidPackage, Package.validate, pack) pack.authors = [] pack.validate() # check maintainer required with email pack.maintainers = [] self.assertRaises(InvalidPackage, Package.validate, pack) pack.maintainers = [maint] maint.email = None self.assertRaises(InvalidPackage, Package.validate, pack) maint.email = 'foo@bar.com' for dep_type in [pack.build_depends, pack.buildtool_depends, pack.build_export_depends, pack.buildtool_export_depends, pack.exec_depends, pack.test_depends, pack.doc_depends]: pack.validate() depend = Dependency(pack.name) dep_type.append(depend) self.assertRaises(InvalidPackage, Package.validate, pack) dep_type.remove(depend) def test_validate_person(self): auth1 = Person('foo') auth1.email = 'foo@bar.com' auth1.validate() auth1.email = 'foo-bar@bar.com' auth1.validate() auth1.email = 'foo+bar@bar.com' auth1.validate() auth1.email = 'foo[at]bar.com' self.assertRaises(InvalidPackage, Person.validate, auth1) auth1.email = 'foo bar.com' self.assertRaises(InvalidPackage, Person.validate, auth1) auth1.email = 'foo