diff --git a/setup.py b/setup.py
index 8f94e343..edbcd5ef 100755
--- a/setup.py
+++ b/setup.py
@@ -12,6 +12,7 @@
version=__version__,
packages=['catkin_pkg'],
package_dir={'': 'src'},
+ package_data={'catkin_pkg': ['templates/*.in']},
scripts=[],
author='Dirk Thomas',
author_email='dthomas@willowgarage.com',
diff --git a/src/catkin_pkg/package_templates.py b/src/catkin_pkg/package_templates.py
new file mode 100644
index 00000000..87b70674
--- /dev/null
+++ b/src/catkin_pkg/package_templates.py
@@ -0,0 +1,205 @@
+# Software License Agreement (BSD License)
+#
+# Copyright (c) 2008, 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 sys
+import os
+from string import Template
+
+from catkin_pkg.package import Package
+
+
+class PackageTemplate(Package):
+
+ def __init__(self, components=None, **kwargs):
+ super(PackageTemplate, self).__init__(**kwargs)
+ self.components = components or []
+ self.validate()
+
+
+def create_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)
+
+
+def create_package_files(target_path, package_template, newfiles):
+ """
+ creates given newfiles and a package.xml from package template.
+
+ :param target_path: parent folder where to create the package
+ :param package_template: contains the required information
+ :param newfiles: dict {filepath: file_contents_str}
+ """
+ newfiles[os.path.join(target_path, 'package.xml')] = create_package_xml(package_template)
+ create_files(newfiles, target_path)
+
+
+class CatkinTemplate(Template):
+ """subclass to use @ instead of $ as markers"""
+ delimiter = '@'
+ escape = '@'
+
+
+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
+
+
+def create_package_xml(package_template):
+ """
+ :param package_template: contains the required information
+ :returns: file contents as string
+ """
+ with open(os.path.join(os.path.dirname(__file__), 'templates', 'package.xml.in'), 'r') as fhand:
+ package_xml_template = fhand.read()
+ 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%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 = []
+ for dep_type, dep_list in {'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}.items():
+ for dep in dep_list:
+ if 'depend' in dep_type:
+ dependencies.append(_create_depend_tag(dep_type,
+ dep.name,
+ dep.version_eq,
+ dep.version_lt,
+ dep.version_lte,
+ dep.version_gt,
+ dep.version_gte))
+ 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:
+ sys.stderr.write('WARNING: Create package does not know how to serialize exports with content: %s, %s, %s' % (export.tagname, export.attributes, export.content))
+ else:
+ attribs = ['%s="%s"' % (k, v) for (k, v) in export.attributes]
+ exports.append(' <%s%s/>\n' % (export.tagname, ''.join(attribs)))
+ temp_dict['exports'] = ''.join(exports)
+
+ temp_dict['components'] = package_template.components
+
+ return ctemp.substitute(temp_dict)
diff --git a/src/catkin_pkg/templates/package.xml.in b/src/catkin_pkg/templates/package.xml.in
new file mode 100644
index 00000000..2de48701
--- /dev/null
+++ b/src/catkin_pkg/templates/package.xml.in
@@ -0,0 +1,31 @@
+
+
+ @name
+ @version
+ @description
+
+
+
+@maintainers
+
+@licenses
+
+
+@urls
+
+
+@authors
+
+
+
+
+
+@dependencies
+
+
+
+
+@exports
+
+
\ No newline at end of file
diff --git a/test/test_templates.py b/test/test_templates.py
new file mode 100644
index 00000000..9e184a77
--- /dev/null
+++ b/test/test_templates.py
@@ -0,0 +1,195 @@
+import os
+import unittest
+import tempfile
+import shutil
+
+from mock import MagicMock, Mock
+
+from catkin_pkg.package_templates import create_files, create_package_files, \
+ create_package_xml, PackageTemplate
+from catkin_pkg.package import parse_package_for_distutils, parse_package, \
+ Dependency, Export, Url
+
+
+class TemplateTest(unittest.TestCase):
+
+ def get_maintainer(self):
+ maint = Mock()
+ maint.email = 'foo@bar.com'
+ maint.name = 'John Foo'
+ return maint
+
+ def test_create_files(self):
+ file1 = os.path.join('foo', 'bar')
+ file2 = os.path.join('foo', 'baz')
+ newfiles = {file1: 'foobar', file2: 'barfoo'}
+ try:
+ rootdir = tempfile.mkdtemp()
+ create_files(newfiles, rootdir)
+ self.assertTrue(os.path.isfile(os.path.join(rootdir, file1)))
+ self.assertTrue(os.path.isfile(os.path.join(rootdir, file2)))
+ self.assertRaises(ValueError, create_files, newfiles, rootdir)
+ finally:
+ shutil.rmtree(rootdir)
+
+
+ def test_create_package_xml(self):
+ maint = self.get_maintainer()
+ pack = PackageTemplate(name='foo',
+ description='foo',
+ version='0.0.0',
+ maintainers=[maint],
+ licenses=['BSD'])
+
+ result = create_package_xml(pack)
+ self.assertTrue('foo' in result, result)
+
+ def test_create_package(self):
+ maint = self.get_maintainer()
+ pack = PackageTemplate(name='bar',
+ description='bar',
+ package_format='1',
+ version='0.0.0',
+ version_abi='pabi',
+ maintainers=[maint],
+ licenses=['BSD'])
+ try:
+ rootdir = tempfile.mkdtemp()
+ file1 = os.path.join(rootdir, 'CMakeLists.txt')
+ file2 = os.path.join(rootdir, 'package.xml')
+ create_package_files(rootdir, pack, {file1: ''})
+ self.assertTrue(os.path.isfile(file1))
+ self.assertTrue(os.path.isfile(file2))
+ finally:
+ shutil.rmtree(rootdir)
+
+ def test_parse_generated(self):
+ maint = self.get_maintainer()
+ pack = PackageTemplate(name='bar',
+ package_format=1,
+ version='0.0.0',
+ version_abi='pabi',
+ urls=[Url('foo')],
+ description='pdesc',
+ maintainers=[maint],
+ licenses=['BSD'])
+ try:
+ rootdir = tempfile.mkdtemp()
+ file2 = os.path.join(rootdir, 'package.xml')
+ create_package_files(rootdir, pack, {})
+ self.assertTrue(os.path.isfile(file2))
+
+ pack_result = parse_package(file2)
+ self.assertEqual(pack.name, pack_result.name)
+ self.assertEqual(pack.package_format, pack_result.package_format)
+ self.assertEqual(pack.version, pack_result.version)
+ self.assertEqual(pack.version_abi, pack_result.version_abi)
+ self.assertEqual(pack.description, pack_result.description)
+ self.assertEqual(pack.maintainers[0].name, pack_result.maintainers[0].name)
+ self.assertEqual(pack.maintainers[0].email, pack_result.maintainers[0].email)
+ self.assertEqual(pack.authors, pack_result.authors)
+ self.assertEqual(pack.urls[0].url, pack_result.urls[0].url)
+ self.assertEqual('website', pack_result.urls[0].type)
+ self.assertEqual(pack.licenses, pack_result.licenses)
+ self.assertEqual(pack.build_depends, pack_result.build_depends)
+ self.assertEqual(pack.buildtool_depends, pack_result.buildtool_depends)
+ self.assertEqual(pack.run_depends, pack_result.run_depends)
+ self.assertEqual(pack.test_depends, pack_result.test_depends)
+ self.assertEqual(pack.conflicts, pack_result.conflicts)
+ self.assertEqual(pack.replaces, pack_result.replaces)
+ self.assertEqual(pack.exports, pack_result.exports)
+
+ rdict = parse_package_for_distutils(file2)
+ self.assertEqual({'name': 'bar',
+ 'maintainer': u'John Foo',
+ 'maintainer_email': 'foo@bar.com',
+ 'description': 'pdesc',
+ 'license': 'BSD',
+ 'version': '0.0.0',
+ 'author': '',
+ 'url': 'foo',
+ 'keywords': ['ROS']}, rdict)
+ finally:
+ shutil.rmtree(rootdir)
+
+ def test_parse_generated_multi(self):
+ # test with multiple attributes filled
+ maint = self.get_maintainer()
+ pack = PackageTemplate(name='bar',
+ package_format=1,
+ version='0.0.0',
+ version_abi='pabi',
+ description='pdesc',
+ maintainers=[maint, maint],
+ authors=[maint, maint],
+ licenses=['BSD', 'MIT'],
+ urls=[Url('foo', 'bugtracker'), Url('bar')],
+ build_depends=[Dependency('dep1')],
+ buildtool_depends=[Dependency('dep2'),
+ Dependency('dep3')],
+ run_depends=[Dependency('dep4', version_lt='4')],
+ test_depends=[Dependency('dep5',
+ version_gt='4',
+ version_lt='4')],
+ conflicts=[Dependency('dep6')],
+ replaces=[Dependency('dep7'),
+ Dependency('dep8')],
+ exports=[Export('architecture_independent'),
+ Export('meta_package')])
+
+ def assertEqualDependencies(deplist1, deplist2):
+ if len(deplist1) != len(deplist1):
+ return False
+ for depx, depy in zip(deplist1, deplist2):
+ for attr in ['name', 'version_lt', 'version_lte',
+ 'version_eq', 'version_gte', 'version_gt']:
+ if getattr(depx, attr) != getattr(depy, attr):
+ return False
+ return True
+
+ try:
+ rootdir = tempfile.mkdtemp()
+ file2 = os.path.join(rootdir, 'package.xml')
+ create_package_files(rootdir, pack, {})
+ self.assertTrue(os.path.isfile(file2))
+
+ pack_result = parse_package(file2)
+ self.assertEqual(pack.name, pack_result.name)
+ self.assertEqual(pack.package_format, pack_result.package_format)
+ self.assertEqual(pack.version, pack_result.version)
+ self.assertEqual(pack.version_abi, pack_result.version_abi)
+ self.assertEqual(pack.description, pack_result.description)
+ self.assertEqual(len(pack.maintainers), len(pack_result.maintainers))
+ self.assertEqual(len(pack.authors), len(pack_result.authors))
+ self.assertEqual(len(pack.urls), len(pack_result.urls))
+ self.assertEqual(pack.urls[0].url, pack_result.urls[0].url)
+ self.assertEqual(pack.urls[0].type, pack_result.urls[0].type)
+ self.assertEqual(pack.licenses, pack_result.licenses)
+ self.assertTrue(assertEqualDependencies(pack.build_depends,
+ pack_result.build_depends))
+ self.assertTrue(assertEqualDependencies(pack.build_depends,
+ pack_result.build_depends))
+ self.assertTrue(assertEqualDependencies(pack.buildtool_depends,
+ pack_result.buildtool_depends))
+ self.assertTrue(assertEqualDependencies(pack.run_depends,
+ pack_result.run_depends))
+ self.assertTrue(assertEqualDependencies(pack.test_depends,
+ pack_result.test_depends))
+ self.assertTrue(assertEqualDependencies(pack.conflicts,
+ pack_result.conflicts))
+ self.assertTrue(assertEqualDependencies(pack.replaces,
+ pack_result.replaces))
+ self.assertEqual(pack.exports[0].tagname, pack_result.exports[0].tagname)
+ self.assertEqual(pack.exports[1].tagname, pack_result.exports[1].tagname)
+
+ rdict = parse_package_for_distutils(file2)
+ self.assertEqual({'name': 'bar',
+ 'maintainer': u'John Foo , John Foo ',
+ 'description': 'pdesc',
+ 'license': 'BSD, MIT',
+ 'version': '0.0.0',
+ 'author': u'John Foo , John Foo ',
+ 'url': 'bar',
+ 'keywords': ['ROS']}, rdict)
+ finally:
+ shutil.rmtree(rootdir)