# 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)