Source code for catkin_pkg.package_templates

# 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


[docs]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 = [] build_depends = [] run_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') run_depends.append(Dependency('message_runtime')) continue pkg_catkin_deps.append(Dependency(dep)) for dep in pkg_catkin_deps: build_depends.append(dep) run_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-'): build_depends.append(Dependency(dep)) run_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, run_depends=run_depends, catkin_deps=catkin_deps, system_deps=system_deps, boost_comps=boost_comps, licenses=licenses, authors=authors, maintainers=maintainers, urls=[]) return package_temp
[docs]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)))
[docs]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)))
[docs]class CatkinTemplate(string.Template): """subclass to use @ instead of $ as markers""" delimiter = '@' escape = '@'
[docs]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): result = '# include_directories(include)' includes = [] if package_template.catkin_deps: includes.append('${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) includes.append('${%s_INCLUDE_DIRS}' % sysdep) if deplist: result += '\n# TODO: Check names of system library include directories (%s)' % ', '.join(deplist) if includes: result += '\ninclude_directories(\n %s\n)' % '\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</%s>\n' % (dep_type, ''.join(version_string), name, dep_type) return result
[docs]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(' <license>%s</license>\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</%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(' <url %s >%s</url>\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, 'buildtool_depend': package_template.buildtool_depends, 'run_depend': package_template.run_depends, 'test_depend': package_template.test_depends, 'conflict': package_template.conflicts, 'replace': package_template.replaces } for dep_type in ['buildtool_depend', 'build_depend', 'run_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(' <metapackage/>') temp_dict['exports'] = ''.join(exports) temp_dict['components'] = package_template.catkin_deps return ctemp.substitute(temp_dict)