1 # Software License Agreement (BSD License)
3 # Copyright (c) 2012, Willow Garage, Inc.
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
33 from __future__ import print_function
44 from argparse import ArgumentParser
47 def _get_locations(pkgs, package_dir):
49 based on setuptools logic and the package_dir dict, builds a dict
50 of location roots for each pkg in pkgs.
51 See http://docs.python.org/distutils/setupscript.html
53 :returns: a dict {pkgname: root} for each pkgname in pkgs (and each of their parents)
55 # package_dir contains a dict {package_name: relativepath}
56 # Example {'': 'src', 'foo': 'lib', 'bar': 'lib2'}
58 # '' means where to look for any package unless a parent package
59 # is listed so package bar.pot is expected at lib2/bar/pot,
60 # whereas package sup.dee is expected at src/sup/dee
62 # if package_dir does not state anything about a package,
63 # setuptool expects the package folder to be in the root of the
66 allprefix = package_dir.get('', '')
68 parent_location = None
69 splits = pkg.split('.')
70 # we iterate over compound name from parent to child
71 # so once we found parent, children just append to their parent
72 for key_len in range(len(splits)):
73 key = '.'.join(splits[:key_len + 1])
74 if key not in locations:
75 if key in package_dir:
76 locations[key] = package_dir[key]
77 elif parent_location is not None:
78 locations[key] = os.path.join(parent_location, splits[key_len])
80 locations[key] = os.path.join(allprefix, key)
81 parent_location = locations[key]
85 def generate_cmake_file(package_name, version, scripts, package_dir, pkgs, modules):
87 Generates lines to add to a cmake file which will set variables
89 :param version: str, format 'int.int.int'
90 :param scripts: [list of str]: relative paths to scripts
91 :param package_dir: {modulename: path}
92 :pkgs: [list of str] python_packages declared in catkin package
93 :modules: [list of str] python modules
95 prefix = '%s_SETUP_PY' % package_name
97 result.append(r'set(%s_VERSION "%s")' % (prefix, version))
98 result.append(r'set(%s_SCRIPTS "%s")' % (prefix, ';'.join(scripts)))
100 # Remove packages with '.' separators.
102 # setuptools allows specifying submodules in other folders than
105 # The symlink approach of catkin does not work with such submodules.
106 # In the common case, this does not matter as the submodule is
107 # within the containing module. We verify this assumption, and if
108 # it passes, we remove submodule packages.
109 locations = _get_locations(pkgs, package_dir)
110 for pkgname, location in locations.items():
111 if not '.' in pkgname:
113 splits = pkgname.split('.')
114 # hack: ignore write-combining setup.py files for msg and srv files
115 if splits[1] in ['msg', 'srv']:
117 # check every child has the same root folder as its parent
118 root_name = splits[0]
119 root_location = location
120 for _ in range(len(splits) - 1):
121 root_location = os.path.dirname(root_location)
122 if root_location != locations[root_name]:
124 "catkin_export_python does not support setup.py files that combine across multiple directories: %s in %s, %s in %s" % (pkgname, location, root_name, locations[root_name]))
126 # If checks pass, remove all submodules
127 pkgs = [p for p in pkgs if '.' not in p]
131 resolved_pkgs += [locations[pkg]]
133 result.append(r'set(%s_PACKAGES "%s")' % (prefix, ';'.join(pkgs)))
134 result.append(r'set(%s_PACKAGE_DIRS "%s")' % (prefix, ';'.join(resolved_pkgs).replace("\\", "/")))
136 # skip modules which collide with package names
137 filtered_modules = []
138 for modname in modules:
139 splits = modname.split('.')
140 # check all parents too
141 equals_package = [('.'.join(splits[:-i]) in locations) for i in range(len(splits))]
142 if any(equals_package):
144 filtered_modules.append(modname)
145 module_locations = _get_locations(filtered_modules, package_dir)
147 result.append(r'set(%s_MODULES "%s")' % (prefix, ';'.join(['%s.py' % m.replace('.', '/') for m in filtered_modules])))
148 result.append(r'set(%s_MODULE_DIRS "%s")' % (prefix, ';'.join([module_locations[m] for m in filtered_modules]).replace("\\", "/")))
153 def _create_mock_setup_function(package_name, outfile):
155 Creates a function to call instead of distutils.core.setup or
156 setuptools.setup, which just captures some args and writes them
157 into a file that can be used from cmake
159 :param package_name: name of the package
160 :param outfile: filename that cmake will use afterwards
161 :returns: a function to replace disutils.core.setup and setuptools.setup
164 def setup(*args, **kwargs):
166 Checks kwargs and writes a scriptfile
168 if 'version' not in kwargs:
169 sys.stderr.write("\n*** Unable to find 'version' in setup.py of %s\n" % package_name)
170 raise RuntimeError("version not found in setup.py")
171 version = kwargs['version']
172 package_dir = kwargs.get('package_dir', {})
174 pkgs = kwargs.get('packages', [])
175 scripts = kwargs.get('scripts', [])
176 modules = kwargs.get('py_modules', [])
180 'exclude_package_data',
183 'include_package_data',
184 'namespace_packages',
188 used_unsupported_args = [arg for arg in unsupported_args if arg in kwargs]
189 if used_unsupported_args:
190 sys.stderr.write("*** Arguments %s to setup() not supported in catkin devel space in setup.py of %s\n" % (used_unsupported_args, package_name))
192 result = generate_cmake_file(package_name=package_name,
195 package_dir=package_dir,
198 with open(outfile, 'w') as out:
199 out.write('\n'.join(result))
206 Script main, parses arguments and invokes Dummy.setup indirectly.
208 parser = ArgumentParser(description='Utility to read setup.py values from cmake macros. Creates a file with CMake set commands setting variables.')
209 parser.add_argument('package_name', help='Name of catkin package')
210 parser.add_argument('setupfile_path', help='Full path to setup.py')
211 parser.add_argument('outfile', help='Where to write result to')
213 args = parser.parse_args()
215 # print("%s" % sys.argv)
216 # PACKAGE_NAME = sys.argv[1]
217 # OUTFILE = sys.argv[3]
218 # print("Interrogating setup.py for package %s into %s " % (PACKAGE_NAME, OUTFILE),
221 # print("executing %s" % args.setupfile_path)
223 # be sure you're in the directory containing
224 # setup.py so the sys.path manipulation works,
225 # so the import of __version__ works
226 os.chdir(os.path.dirname(os.path.abspath(args.setupfile_path)))
228 # patch setup() function of distutils and setuptools for the
229 # context of evaluating setup.py
231 fake_setup = _create_mock_setup_function(package_name=args.package_name,
232 outfile=args.outfile)
234 distutils_backup = distutils.core.setup
235 distutils.core.setup = fake_setup
237 setuptools_backup = setuptools.setup
238 setuptools.setup = fake_setup
242 runpy.run_path(args.setupfile_path)
244 distutils.core.setup = distutils_backup
246 setuptools.setup = setuptools_backup
250 if __name__ == '__main__':