256 lines
10 KiB
Plaintext
256 lines
10 KiB
Plaintext
# 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 os
|
|
import runpy
|
|
import sys
|
|
from argparse import ArgumentParser
|
|
|
|
setup_modules = []
|
|
|
|
try:
|
|
import distutils.core
|
|
setup_modules.append(distutils.core)
|
|
except ImportError:
|
|
pass
|
|
|
|
try:
|
|
import setuptools
|
|
setup_modules.append(setuptools)
|
|
except ImportError:
|
|
pass
|
|
|
|
assert setup_modules, 'Must have distutils or setuptools installed'
|
|
|
|
|
|
def _get_locations(pkgs, package_dir):
|
|
"""
|
|
Based on setuptools logic and the package_dir dict, builds a dict of location roots for each pkg in pkgs.
|
|
|
|
See http://docs.python.org/distutils/setupscript.html
|
|
|
|
:returns: a dict {pkgname: root} for each pkgname in pkgs (and each of their parents)
|
|
"""
|
|
# package_dir contains a dict {package_name: relativepath}
|
|
# Example {'': 'src', 'foo': 'lib', 'bar': 'lib2'}
|
|
#
|
|
# '' means where to look for any package unless a parent package
|
|
# is listed so package bar.pot is expected at lib2/bar/pot,
|
|
# whereas package sup.dee is expected at src/sup/dee
|
|
#
|
|
# if package_dir does not state anything about a package,
|
|
# setuptool expects the package folder to be in the root of the
|
|
# project
|
|
locations = {}
|
|
allprefix = package_dir.get('', '')
|
|
for pkg in pkgs:
|
|
parent_location = None
|
|
splits = pkg.split('.')
|
|
# we iterate over compound name from parent to child
|
|
# so once we found parent, children just append to their parent
|
|
for key_len in range(len(splits)):
|
|
key = '.'.join(splits[:key_len + 1])
|
|
if key not in locations:
|
|
if key in package_dir:
|
|
locations[key] = package_dir[key]
|
|
elif parent_location is not None:
|
|
locations[key] = os.path.join(parent_location, splits[key_len])
|
|
else:
|
|
locations[key] = os.path.join(allprefix, key)
|
|
parent_location = locations[key]
|
|
return locations
|
|
|
|
|
|
def generate_cmake_file(package_name, version, scripts, package_dir, pkgs, modules, setup_module=None):
|
|
"""
|
|
Generate lines to add to a cmake file which will set variables.
|
|
|
|
:param version: str, format 'int.int.int'
|
|
:param scripts: [list of str]: relative paths to scripts
|
|
:param package_dir: {modulename: path}
|
|
:param pkgs: [list of str] python_packages declared in catkin package
|
|
:param modules: [list of str] python modules
|
|
:param setup_module: str, setuptools or distutils
|
|
"""
|
|
prefix = '%s_SETUP_PY' % package_name
|
|
result = []
|
|
if setup_module:
|
|
result.append(r'set(%s_SETUP_MODULE "%s")' % (prefix, setup_module))
|
|
result.append(r'set(%s_VERSION "%s")' % (prefix, version))
|
|
result.append(r'set(%s_SCRIPTS "%s")' % (prefix, ';'.join(scripts)))
|
|
|
|
# Remove packages with '.' separators.
|
|
#
|
|
# setuptools allows specifying submodules in other folders than
|
|
# their parent
|
|
#
|
|
# The symlink approach of catkin does not work with such submodules.
|
|
# In the common case, this does not matter as the submodule is
|
|
# within the containing module. We verify this assumption, and if
|
|
# it passes, we remove submodule packages.
|
|
locations = _get_locations(pkgs, package_dir)
|
|
for pkgname, location in locations.items():
|
|
if '.' not in pkgname:
|
|
continue
|
|
splits = pkgname.split('.')
|
|
# hack: ignore write-combining setup.py files for msg and srv files
|
|
if splits[1] in ['msg', 'srv']:
|
|
continue
|
|
# check every child has the same root folder as its parent
|
|
root_name = splits[0]
|
|
root_location = location
|
|
for _ in range(len(splits) - 1):
|
|
root_location = os.path.dirname(root_location)
|
|
if root_location != locations[root_name]:
|
|
raise RuntimeError(
|
|
'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]))
|
|
|
|
# If checks pass, remove all submodules
|
|
pkgs = [p for p in pkgs if '.' not in p]
|
|
|
|
resolved_pkgs = []
|
|
for pkg in pkgs:
|
|
resolved_pkgs += [locations[pkg]]
|
|
|
|
result.append(r'set(%s_PACKAGES "%s")' % (prefix, ';'.join(pkgs)))
|
|
result.append(r'set(%s_PACKAGE_DIRS "%s")' % (prefix, ';'.join(resolved_pkgs).replace('\\', '/')))
|
|
|
|
# skip modules which collide with package names
|
|
filtered_modules = []
|
|
for modname in modules:
|
|
splits = modname.split('.')
|
|
# check all parents too
|
|
equals_package = [('.'.join(splits[:-i]) in locations) for i in range(len(splits))]
|
|
if any(equals_package):
|
|
continue
|
|
filtered_modules.append(modname)
|
|
module_locations = _get_locations(filtered_modules, package_dir)
|
|
|
|
result.append(r'set(%s_MODULES "%s")' % (prefix, ';'.join(['%s.py' % m.replace('.', '/') for m in filtered_modules])))
|
|
result.append(r'set(%s_MODULE_DIRS "%s")' % (prefix, ';'.join([module_locations[m] for m in filtered_modules]).replace('\\', '/')))
|
|
|
|
return result
|
|
|
|
|
|
def _create_mock_setup_function(setup_module, package_name, outfile):
|
|
"""
|
|
Create a function to call instead of distutils.core.setup or setuptools.setup.
|
|
|
|
It just captures some args and writes them into a file that can be used from cmake.
|
|
|
|
:param package_name: name of the package
|
|
:param outfile: filename that cmake will use afterwards
|
|
:returns: a function to replace disutils.core.setup and setuptools.setup
|
|
"""
|
|
|
|
def setup(*args, **kwargs):
|
|
"""Check kwargs and write a scriptfile."""
|
|
if 'version' not in kwargs:
|
|
sys.stderr.write("\n*** Unable to find 'version' in setup.py of %s\n" % package_name)
|
|
raise RuntimeError('version not found in setup.py')
|
|
version = kwargs['version']
|
|
package_dir = kwargs.get('package_dir', {})
|
|
|
|
pkgs = kwargs.get('packages', [])
|
|
scripts = kwargs.get('scripts', [])
|
|
modules = kwargs.get('py_modules', [])
|
|
|
|
unsupported_args = [
|
|
'entry_points',
|
|
'exclude_package_data',
|
|
'ext_modules ',
|
|
'ext_package',
|
|
'include_package_data',
|
|
'namespace_packages',
|
|
'setup_requires',
|
|
'use_2to3',
|
|
'zip_safe']
|
|
used_unsupported_args = [arg for arg in unsupported_args if arg in kwargs]
|
|
if used_unsupported_args:
|
|
sys.stderr.write('*** Arguments %s to setup() not supported in catkin devel space in setup.py of %s\n' % (used_unsupported_args, package_name))
|
|
|
|
result = generate_cmake_file(package_name=package_name,
|
|
version=version,
|
|
scripts=scripts,
|
|
package_dir=package_dir,
|
|
pkgs=pkgs,
|
|
modules=modules,
|
|
setup_module=setup_module)
|
|
with open(outfile, 'w') as out:
|
|
out.write('\n'.join(result))
|
|
|
|
return setup
|
|
|
|
|
|
def main():
|
|
"""Script main, parses arguments and invokes Dummy.setup indirectly."""
|
|
parser = ArgumentParser(description='Utility to read setup.py values from cmake macros. Creates a file with CMake set commands setting variables.')
|
|
parser.add_argument('package_name', help='Name of catkin package')
|
|
parser.add_argument('setupfile_path', help='Full path to setup.py')
|
|
parser.add_argument('outfile', help='Where to write result to')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# print("%s" % sys.argv)
|
|
# PACKAGE_NAME = sys.argv[1]
|
|
# OUTFILE = sys.argv[3]
|
|
# print("Interrogating setup.py for package %s into %s " % (PACKAGE_NAME, OUTFILE),
|
|
# file=sys.stderr)
|
|
|
|
# print("executing %s" % args.setupfile_path)
|
|
|
|
# be sure you're in the directory containing
|
|
# setup.py so the sys.path manipulation works,
|
|
# so the import of __version__ works
|
|
os.chdir(os.path.dirname(os.path.abspath(args.setupfile_path)))
|
|
|
|
# patch setup() function of distutils and setuptools for the
|
|
# context of evaluating setup.py
|
|
backup_modules = {}
|
|
try:
|
|
|
|
for module in setup_modules:
|
|
backup_modules[id(module)] = module.setup
|
|
module.setup = _create_mock_setup_function(
|
|
setup_module=module.__name__, package_name=args.package_name, outfile=args.outfile)
|
|
|
|
runpy.run_path(args.setupfile_path)
|
|
finally:
|
|
for module in setup_modules:
|
|
module.setup = backup_modules[id(module)]
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|