python distutils
distutils可以用来在Python环境中构建和安装额外的模块。新的模块可以是纯python的,也可以是用C/C++写的扩展模块,或者可以是Python包,包中包含了由C和Python编写的模块。
对于模块开发者以及需要安装模块的使用者来说,distutils的使用都很简单,作为一个开发者,除了编写源码之外,还需要:
- 编写setup脚本(一般是setup.py);
- 编写一个setup配置文件(可选);
- 创建一个源码发布;
- 创建一个或多个构建(二进制)发布(可选);
一个setup.py的简单例子,
from distutils.core import setup
setup(name='Distutils',
version='1.0',
description='Python Distribution Utilities',
author='Greg Ward',
author_email='gward@python.net',
url='https://www.python.org/sigs/distutils-sig/',
packages=['distutils', 'distutils.command'],
)
关于distutils的具体用法,可以参考https://docs.python.org/2/distutils/setupscript.html
。
下面是roslaunch的setup.py,
from distutils.core import setup
from catkin_pkg.python_setup import generate_distutils_setup
#参数收集,返回到d,dict
d = generate_distutils_setup(
packages=['roslaunch'],
package_dir={'': 'src'},
scripts=['scripts/roscore',
'scripts/roslaunch',
'scripts/roslaunch-complete',
'scripts/roslaunch-deps',
'scripts/roslaunch-logs'],
requires=['genmsg', 'genpy', 'roslib', 'rospkg']
)
#将字典反转为参数值
setup(**d)
而其中的catkin_pkg,其git地址为https://github.com/ros-infrastructure/catkin_pkg.git
,
功能介绍如下,
catkin_pkg
Standalone Python library for the Catkin package system.
下面是generate_distutils_setup()
函数的实现,这里用到了**在函数定义时的参数收集功能(dict),
其核心功能就是将package.xml文件中的内容解析放到一个字典中,然后返回。(而且还要加上输入参数kwargs,输入参数kwargs中收集的key如果在package.xml中有,则值必须一样,如果没有,则添加到返回值中)
#catkin_pkg\src\catkin_pkg\python_setup.py
from .package import InvalidPackage, parse_package
def generate_distutils_setup(package_xml_path=os.path.curdir, **kwargs):
"""
Extract the information relevant for distutils from the package
manifest. The following keys will be set:
The "name" and "version" are taken from the eponymous tags.
A single maintainer will set the keys "maintainer" and
"maintainer_email" while multiple maintainers are merged into the
"maintainer" fields (including their emails). Authors are handled
likewise.
The first URL of type "website" (or without a type) is used for
the "url" field.
The "description" is taken from the eponymous tag if it does not
exceed 200 characters. If it does "description" contains the
truncated text while "description_long" contains the complete.
All licenses are merged into the "license" field.
:param kwargs: All keyword arguments are passed through. The above
mentioned keys are verified to be identical if passed as a
keyword argument
:returns: return dict populated with parsed fields and passed
keyword arguments
:raises: :exc:`InvalidPackage`
:raises: :exc:`IOError`
"""
package = parse_package(package_xml_path)
data = {}
data['name'] = package.name
data['version'] = package.version
# either set one author with one email or join all in a single field
if len(package.authors) == 1 and package.authors[0].email is not None:
data['author'] = package.authors[0].name
data['author_email'] = package.authors[0].email
else:
data['author'] = ', '.join([('%s <%s>' % (a.name, a.email) if a.email is not None else a.name) for a in package.authors])
# either set one maintainer with one email or join all in a single field
if len(package.maintainers) == 1:
data['maintainer'] = package.maintainers[0].name
data['maintainer_email'] = package.maintainers[0].email
else:
data['maintainer'] = ', '.join(['%s <%s>' % (m.name, m.email) for m in package.maintainers])
# either set the first URL with the type 'website' or the first URL of any type
websites = [url.url for url in package.urls if url.type == 'website']
if websites:
data['url'] = websites[0]
elif package.urls:
data['url'] = package.urls[0].url
if len(package.description) <= 200:
data['description'] = package.description
else:
data['description'] = package.description[:197] + '...'
data['long_description'] = package.description
data['license'] = ', '.join(package.licenses)
#输入参数kwargs中收集的key如果在package.xml中有,则值必须一样;
#如果没有,则添加到返回值中。
# pass keyword arguments and verify equality if generated and passed in
for k, v in kwargs.items():
if k in data:
if v != data[k]:
raise InvalidPackage('The keyword argument "%s" does not match the information from package.xml: "%s" != "%s"' % (k, v, data[k]))
else:
data[k] = v
return data
而,package.xml中都是一些distutils中setup()函数执行时需要的一些参数,用xml进行可配置化。
<package>
<name>roslaunch</name>
<version>1.13.0</version>
<description>
roslaunch is a tool for easily launching multiple ROS <a
href="http://ros.org/wiki/Nodes">nodes</a> locally and remotely
via SSH, as well as setting parameters on the <a
href="http://ros.org/wiki/Parameter Server">Parameter
Server</a>. It includes options to automatically respawn processes
that have already died. roslaunch takes in one or more XML
configuration files (with the <tt>.launch</tt> extension) that
specify the parameters to set and nodes to launch, as well as the
machines that they should be run on.
</description>
<maintainer email="dthomas@osrfoundation.org">Dirk Thomas</maintainer>
<license>BSD</license>
<url>http://ros.org/wiki/roslaunch</url>
<author>Ken Conley</author>
<buildtool_depend version_gte="0.5.78">catkin</buildtool_depend>
<run_depend>python-paramiko</run_depend>
<run_depend version_gte="1.0.37">python-rospkg</run_depend>
<run_depend>python-yaml</run_depend>
<run_depend>rosclean</run_depend>
<run_depend>rosgraph_msgs</run_depend>
<run_depend>roslib</run_depend>
<run_depend version_gte="1.11.16">rosmaster</run_depend>
<run_depend>rosout</run_depend>
<run_depend>rosparam</run_depend>
<run_depend version_gte="1.13.3">rosunit</run_depend>
<test_depend>rosbuild</test_depend>
<export>
<rosdoc config="rosdoc.yaml"/>
<architecture_independent/>
</export>
</package>
setup()函数的输入参数中,scripts的解释如下,
So far we have been dealing with pure and non-pure Python modules, which are usually not run by themselves but imported by scripts.
Scripts are files containing Python source code, intended to be started from the command line. Scripts don’t require Distutils to do anything very complicated.
所以,python 模块主要是用来被其他模块去import,而script是为了直接在命令行执行,类似于应用程序。
而roscore就是这样一个需要在命令行执行的脚本(程序),
scripts=['scripts/roscore',
'scripts/roslaunch',
'scripts/roslaunch-complete',
'scripts/roslaunch-deps',
'scripts/roslaunch-logs']
而roscore最终会去import roslaunch package,去调用其中的main函数。
#ros_comm\tools\roslaunch\scripts\roscore
import roslaunch
roslaunch.main(['roscore', '--core'] + sys.argv[1:])
后续将对roslaunch package源代码进行分析,但是由于其又依赖于ros_comm
的其他tools,例如rosmaster,rosgraph,所以后续将逐步对ros_comm中的tools从简到难进行分析。