Imported Upstream version 1.9.11
Thomas Moulard
10 years ago
0 | # Lines that start with '#' are comments. | |
1 | # ignore objects and libraries | |
2 | *.o | |
3 | *.a | |
4 | *.d | |
5 | *.so | |
6 | *.lo | |
7 | *.la | |
8 | # ignore python compiled files: | |
9 | *.pyc | |
10 | # ignore gcov files | |
11 | *.gcda | |
12 | *.gcno | |
13 | *.gcov | |
14 | # ignore tag files | |
15 | tags | |
16 | # we don't care about vi swp files | |
17 | *.swp | |
18 | # we also don't care about emacs swap files | |
19 | *~ | |
20 | .backup-* | |
21 | # nothing in the root directory should be checked in. | |
22 | root | |
23 | # setup.bash is a generated file. | |
24 | setup.bash | |
25 | config.mk | |
26 | # gcov files | |
27 | *.gcno | |
28 | *.gcov | |
29 | # Testing output files | |
30 | *.out | |
31 | .build-version | |
32 | .rospack_cache | |
33 | *.tar.gz | |
34 | 3rdparty/cmake/build | |
35 | 3rdparty/cmake/cmake | |
36 | 3rdparty/cmake/cmake-2.6.0 | |
37 | 3rdparty/numpy/build | |
38 | 3rdparty/numpy/numpy | |
39 | 3rdparty/numpy/src | |
40 | 3rdparty/tinyxml/build | |
41 | 3rdparty/xmlrpc++/build | |
42 | bin/rosinstall | |
43 | bin/roslaunch | |
44 | bin/rosmap | |
45 | bin/rosmaster | |
46 | bin/rospack | |
47 | bin/rosplay | |
48 | bin/rosprereq | |
49 | bin/rosrecord | |
50 | bin/rostest | |
51 | bin/svnmerge | |
52 | core/experimental/roslisp/build | |
53 | core/experimental/roslisp/lib | |
54 | core/genmsg_cpp/build | |
55 | core/genmsg_cpp/genmsg | |
56 | core/genmsg_cpp/genmsg_lisp | |
57 | core/genmsg_cpp/genmsgtest | |
58 | core/genmsg_cpp/gensrv | |
59 | core/genmsg_cpp/gensrv_lisp | |
60 | core/genmsg_cpp/submsgs | |
61 | core/rosconsole/build | |
62 | core/rosconsole/examples/example | |
63 | core/roscpp/build | |
64 | core/roscpp/msg/cpp | |
65 | core/roscpp/msg/lisp | |
66 | core/roscpp/src/roscpp | |
67 | core/roscpp/srv/cpp | |
68 | core/roscpp/srv/lisp | |
69 | core/rosout/build | |
70 | core/rosout/rosout | |
71 | core/rospack_test/build | |
72 | core/rospy/build | |
73 | core/rostime/build | |
74 | core/rostools/build | |
75 | core/rostools/msg/cpp | |
76 | core/rostools/msg/lisp | |
77 | core/rostools/src/rostools/msg | |
78 | core/roslib/build | |
79 | core/roslib/msg/cpp | |
80 | core/roslib/msg/lisp | |
81 | core/roslib/src/rostools/msg | |
82 | demos/roscpp_demo/bin | |
83 | demos/roscpp_demo/build | |
84 | demos/rospy_demo/build | |
85 | demos/rospy_demo/msg/cpp | |
86 | demos/rospy_demo/msg/lisp | |
87 | demos/rospy_demo/src/rospy_demo/msg | |
88 | param/rosparam/build | |
89 | param/shparam/build | |
90 | param/shparam/shparam | |
91 | param/xmlparam/build | |
92 | param/xmlparam/xmlparam | |
93 | std_msgs/build | |
94 | std_msgs/msg/cpp | |
95 | std_msgs/msg/lisp | |
96 | std_msgs/src | |
97 | std_srvs/build | |
98 | std_srvs/src | |
99 | std_srvs/srv/cpp | |
100 | std_srvs/srv/lisp | |
101 | test/gtest/build | |
102 | test/gtest/gtest | |
103 | test/gtest/gtest-1.0.1 | |
104 | test/param_setget/depend | |
105 | test/param_setget/param_setget | |
106 | test/performance/roscpp_msg_serdes/build | |
107 | test/performance/roscpp_msg_serdes/pointcloud_serdes | |
108 | test/rostest/build | |
109 | test/test_ros/build | |
110 | test/test_roscpp/bin | |
111 | test/test_roscpp/build | |
112 | test/test_roslaunch/build | |
113 | test/test_ros/msg/cpp | |
114 | test/test_ros/msg/lisp | |
115 | test/test_rospy/build | |
116 | test/test_rospy/msg/cpp | |
117 | test/test_rospy/msg/lisp | |
118 | test/test_rospy/src/test_rospy/msg | |
119 | test/test_rospy/src/test_rospy/srv | |
120 | test/test_rospy/srv/cpp | |
121 | test/test_rospy/srv/lisp | |
122 | test/test_ros/src/test_ros/msg | |
123 | tools/rosinstall/build | |
124 | tools/rosinstall/rosinstall | |
125 | tools/roslaunch/build | |
126 | tools/rosmap/depend | |
127 | tools/rospack/depend | |
128 | tools/rosprereq/depend | |
129 | tools/rosrecord/bin | |
130 | tools/rosrecord/build | |
131 | tools/vizdeps/deps.gv | |
132 | tools/vizdeps/deps.pdf | |
133 | tutorials/roscpp_tutorials/bin | |
134 | tutorials/roscpp_tutorials/build | |
135 | tutorials/roscpp_tutorials/src | |
136 | tutorials/roscpp_tutorials/srv/cpp | |
137 | tutorials/roscpp_tutorials/srv/lisp | |
138 | tutorials/rospy_tutorials/build | |
139 | tutorials/rospy_tutorials/src | |
140 | tutorials/rospy_tutorials/srv/cpp | |
141 | tutorials/rospy_tutorials/srv/lisp | |
142 | util/rosthread/build | |
143 | .build_version | |
144 | .rosgcov_files | |
145 | 3rdparty/gtest/gtest | |
146 | /core/genmsg_cpp/genmsg_java | |
147 | /core/genmsg_cpp/genmsg_oct | |
148 | /core/genmsg_cpp/gensrv_java | |
149 | /core/genmsg_cpp/gensrv_oct | |
150 | /core/roslib/src/roslib |
0 | Morgan Quigley <mquigley@cs.stanford.edu> | |
1 | ||
2 | Eric Berger <berger@willowgarage.com> | |
3 | Ken Conley <kwc@willowgarage.com> | |
4 | Rosen Diankov <rdiankov@cs.cmu.edu> | |
5 | Josh Faust <jfaust@willowgarage.com> | |
6 | Tully Foote <tfoote@willowgarage.com> | |
7 | Brian Gerkey <gerkey@willowgarage.com> | |
8 | Jeremy Leibs <leibs@willowgarage.com> | |
9 | Bhaskara Marthi <bhaskara@willowgarage.com> | |
10 | Troy Straszheim <straszheim@willowgarage.com> | |
11 | Rob Wheeler <wheeler@willowgarage.com> |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(ros) | |
2 | find_package(catkin REQUIRED) | |
3 | catkin_stack() | |
4 | catkin_project(ros) | |
5 | find_package(Boost REQUIRED COMPONENTS thread) | |
6 | ||
7 | # Must call catkin_python_setup() early, to ensure that things like | |
8 | # rosunit are available in build-space, and can be found by things like | |
9 | # catkin_add_gtest(), which is called from within lower-level CMakeLists.txts. | |
10 | catkin_python_setup() | |
11 | ||
12 | catkin_add_env_hooks(10.ros SHELLS bat sh DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/env-hooks) | |
13 | ||
14 | foreach(subdir | |
15 | core/roslang | |
16 | core/roslib | |
17 | tools/rosboost_cfg | |
18 | tools/rosbash | |
19 | tools/rosclean | |
20 | tools/roscreate | |
21 | tools/rosmake | |
22 | tools/rosunit | |
23 | ) | |
24 | add_subdirectory(${subdir}) | |
25 | endforeach() | |
26 | ||
27 | # INSTALLATION | |
28 | install(FILES config/rosconsole.config | |
29 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/config) | |
30 | ||
31 | # setup our backwards-compatibility ROS_ROOT | |
32 | install(DIRECTORY core/rosbuild/ | |
33 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/core/rosbuild | |
34 | PATTERN ".svn" EXCLUDE | |
35 | PATTERN "bin" EXCLUDE | |
36 | ) | |
37 | install(PROGRAMS | |
38 | core/rosbuild/bin/check_same_directories.py | |
39 | core/rosbuild/bin/download_checkmd5.py | |
40 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/core/rosbuild/bin) | |
41 | install(DIRECTORY core/mk/ | |
42 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/core/mk | |
43 | PATTERN ".svn" EXCLUDE | |
44 | ) | |
45 | ||
46 | install(DIRECTORY tools/roscreate/templates/ | |
47 | DESTINATION share/roscreate/templates | |
48 | PATTERN ".svn" EXCLUDE) |
0 | Check the manifest.xml in each package directory for license information. |
0 | Copyright (C) 2008, Morgan Quigley | |
1 | ||
2 | Redistribution and use in source and binary forms, with or without | |
3 | modification, are permitted provided that the following conditions are met: | |
4 | * Redistributions of source code must retain the above copyright notice, | |
5 | this list of conditions and the following disclaimer. | |
6 | * Redistributions in binary form must reproduce the above copyright | |
7 | notice, this list of conditions and the following disclaimer in the | |
8 | documentation and/or other materials provided with the distribution. | |
9 | * Neither the name of Stanford University nor the names of its | |
10 | contributors may be used to endorse or promote products derived from | |
11 | this software without specific prior written permission. | |
12 | ||
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
16 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
17 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
18 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
19 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
20 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
21 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
22 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
23 | POSSIBILITY OF SUCH DAMAGE. | |
24 |
0 | Copyright (c) 2009, Willow Garage, Inc. | |
1 | All rights reserved. | |
2 | ||
3 | Redistribution and use in source and binary forms, with or without | |
4 | modification, are permitted provided that the following conditions are met: | |
5 | ||
6 | * Redistributions of source code must retain the above copyright | |
7 | notice, this list of conditions and the following disclaimer. | |
8 | * Redistributions in binary form must reproduce the above copyright | |
9 | notice, this list of conditions and the following disclaimer in the | |
10 | documentation and/or other materials provided with the distribution. | |
11 | * Neither the name of Willow Garage, Inc. nor the names of its | |
12 | contributors may be used to endorse or promote products derived from | |
13 | this software without specific prior written permission. | |
14 | ||
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
25 | POSSIBILITY OF SUCH DAMAGE. |
0 | Copyright (C) 2008, Morgan Quigley and Willow Garage, Inc. | |
1 | ||
2 | Redistribution and use in source and binary forms, with or without | |
3 | modification, are permitted provided that the following conditions are met: | |
4 | * Redistributions of source code must retain the above copyright notice, | |
5 | this list of conditions and the following disclaimer. | |
6 | * Redistributions in binary form must reproduce the above copyright | |
7 | notice, this list of conditions and the following disclaimer in the | |
8 | documentation and/or other materials provided with the distribution. | |
9 | * Neither the names of Stanford University or Willow Garage, Inc. nor the names of its | |
10 | contributors may be used to endorse or promote products derived from | |
11 | this software without specific prior written permission. | |
12 | ||
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
16 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
17 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
18 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
19 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
20 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
21 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
22 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
23 | POSSIBILITY OF SUCH DAMAGE. |
0 | Copyright (c) 2008, Willow Garage, Inc. | |
1 | All rights reserved. | |
2 | ||
3 | Redistribution and use in source and binary forms, with or without | |
4 | modification, are permitted provided that the following conditions are met: | |
5 | ||
6 | * Redistributions of source code must retain the above copyright | |
7 | notice, this list of conditions and the following disclaimer. | |
8 | * Redistributions in binary form must reproduce the above copyright | |
9 | notice, this list of conditions and the following disclaimer in the | |
10 | documentation and/or other materials provided with the distribution. | |
11 | * Neither the name of Willow Garage, Inc. nor the names of its | |
12 | contributors may be used to endorse or promote products derived from | |
13 | this software without specific prior written permission. | |
14 | ||
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
25 | POSSIBILITY OF SUCH DAMAGE. |
0 | Robot Operating System (ROS) | |
1 | =============================================================================== | |
2 | ||
3 | ROS is a meta-operating system for your robot. It provides | |
4 | language-independent and network-transparent communication for a | |
5 | distributed robot control system. | |
6 | ||
7 | Installation Notes | |
8 | ------------------ | |
9 | ||
10 | For full installation instructions, including system prerequisites and | |
11 | platform-specific help, see: | |
12 | ||
13 | http://ros.org/wiki/ROS/Installation |
0 | # | |
1 | # rosconsole will find this file by default at $ROS_ROOT/config/rosconsole.config | |
2 | # | |
3 | # You can define your own by e.g. copying this file and setting | |
4 | # ROSCONSOLE_CONFIG_FILE (in your environment) to point to the new file | |
5 | # | |
6 | log4j.logger.ros=INFO | |
7 | log4j.logger.ros.roscpp.superdebug=WARN |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(mk) | |
2 | find_package(catkin REQUIRED) | |
3 | catkin_package() | |
4 | catkin_package_export() | |
5 | ||
6 | install(DIRECTORY . | |
7 | DESTINATION ${CATKIN_GLOBAL_SHARE_DESTINATION}/ros/core/mk | |
8 | PATTERN ".svn" EXCLUDE) |
0 | # A target to test building of a package, all of its dependencies, and all | |
1 | # of the things that depend on it | |
2 | # | |
3 | # For internal use only. | |
4 | ||
5 | # HACK: assume that the package name is the name of the directory we're | |
6 | # sitting in | |
7 | pkg = $(shell basename $(PWD)) | |
8 | ||
9 | rosalldeps = $(shell rospack find rospack)/rosalldeps | |
10 | ||
11 | deps = $(shell $(rosalldeps) -H 1 $(pkg)) | |
12 | rules = $(foreach d, $(deps), cd $(shell rospack find $(d)) && make &&) true; | |
13 | .PHONY: build | |
14 | build: | |
15 | $(rules) | |
16 | rules_test = $(foreach d, $(deps), cd $(shell rospack find $(d)) && make test &&) true; | |
17 | .PHONY: build-test | |
18 | build-test: | |
19 | $(rules_test) | |
20 | ||
21 | deps_all = $(shell $(rosalldeps) $(pkg)) | |
22 | rules_all = $(foreach d, $(deps_all), cd $(shell rospack find $(d)) && make &&) true; | |
23 | .PHONY: build-all | |
24 | build-all: | |
25 | $(rules_all) | |
26 | rules_all_test = $(foreach d, $(deps_all), cd $(shell rospack find $(d)) && make test &&) true; | |
27 | .PHONY: build-test-all | |
28 | build-test-all: | |
29 | $(rules_all_test) |
0 | # This file can be used to automate cloning and patching of 3rd | |
1 | # party software. | |
2 | # | |
3 | # Before including this file, e.g.: | |
4 | # include $(shell rospack find mk)/bzr_checkout.mk | |
5 | # define the following make variables: | |
6 | # BZR_DIR: name of directory into which you want to clone to | |
7 | # BZR_URL: full URL to download | |
8 | # BZR_PATCH: your (list of) patch file(s) to patch the downloaded software | |
9 | # BZR_REVISION: a bzr revisionspec string | |
10 | ||
11 | ifneq ($(BZR_REVISION),) | |
12 | BZR_REV=-r $(BZR_REVISION) | |
13 | else | |
14 | BZR_REV= | |
15 | endif | |
16 | ||
17 | $(BZR_DIR): | |
18 | bzr branch $(BZR_URL) $(BZR_REV) $(BZR_DIR) | |
19 | touch rospack_nosubdirs | |
20 | ||
21 | patched: $(BZR_DIR) $(BZR_PATCH) Makefile | |
22 | cd $(BZR_DIR) && bzr revert $(BZR_REV) | |
23 | ifneq ($(strip $(BZR_PATCH)),) | |
24 | $(foreach PATCH, $(BZR_PATCH), patch -d $(BZR_DIR) -p1 < $(PATCH) && ) echo patched | |
25 | touch rospack_nosubdirs | |
26 | touch patched | |
27 | endif | |
28 | ||
29 | download: $(BZR_DIR) patched |
0 | # set EXTRA_CMAKE_FLAGS in the including Makefile in order to add tweaks | |
1 | CMAKE_FLAGS= -Wdev -DCMAKE_TOOLCHAIN_FILE=`rospack find rosbuild`/rostoolchain.cmake $(EXTRA_CMAKE_FLAGS) | |
2 | ||
3 | # The all target does the heavy lifting, creating the build directory and | |
4 | # invoking CMake | |
5 | all: | |
6 | @mkdir -p build | |
7 | -mkdir -p bin | |
8 | @rm -rf msg/cpp srv/cpp # make sure there are no msg/cpp or srv/cpp directories | |
9 | cd build && cmake $(CMAKE_FLAGS) .. | |
10 | ifneq ($(MAKE),) | |
11 | cd build && $(MAKE) $(ROS_PARALLEL_JOBS) | |
12 | else | |
13 | cd build && make $(ROS_PARALLEL_JOBS) | |
14 | endif | |
15 | ||
16 | PACKAGE_NAME=$(shell basename $(PWD)) | |
17 | ||
18 | # The clean target blows everything away | |
19 | # It also removes auto-generated message/service code directories, | |
20 | # to handle the case where the original .msg/.srv file has been removed, | |
21 | # and thus CMake no longer knows about it. | |
22 | clean: | |
23 | -cd build && make clean | |
24 | rm -rf build | |
25 | ||
26 | # All other targets are just passed through | |
27 | test: all | |
28 | if cd build && make -k $@; then make test-results; else make test-results && exit 1; fi | |
29 | test-nobuild: | |
30 | @mkdir -p build | |
31 | cd build && cmake $(CMAKE_FLAGS) -Drosbuild_test_nobuild=1 .. | |
32 | if cd build && make rosbuild_clean-test-results && make -k test; then make test-results; else make test-results && exit 1; fi | |
33 | tests: all | |
34 | cd build && make $@ | |
35 | test-future: all | |
36 | cd build && make -k $@ | |
37 | gcoverage: all | |
38 | cd build && make $@ | |
39 | ||
40 | eclipse-project: | |
41 | mv Makefile Makefile.ros | |
42 | if ! (cmake -G"Eclipse CDT4 - Unix Makefiles" -Wno-dev . && rm Makefile && rm CMakeCache.txt && rm -rf CMakeFiles); then mv Makefile.ros Makefile && echo "**ERROR building Eclipse project!**" && false; fi | |
43 | mv Makefile.ros Makefile | |
44 | mv .project .project-cmake | |
45 | awk -f $(shell rospack find mk)/eclipse.awk .project-cmake > .project | |
46 | rm .project-cmake | |
47 | python $(shell rospack find mk)/make_pydev_project.py | |
48 | ||
49 | ||
50 | include $(shell rospack find mk)/buildtest.mk |
0 | # set EXTRA_CMAKE_FLAGS in the including Makefile in order to add tweaks | |
1 | CMAKE_FLAGS= -Wdev -DCMAKE_TOOLCHAIN_FILE=`rospack find rosbuild`/rostoolchain.cmake $(EXTRA_CMAKE_FLAGS) | |
2 | ||
3 | # The all target does the heavy lifting, creating the build directory and | |
4 | # invoking CMake | |
5 | all: | |
6 | @mkdir -p build | |
7 | cd build && cmake $(CMAKE_FLAGS) .. | |
8 | #cd build && make $(ROS_PARALLEL_JOBS) | |
9 | ||
10 | # The clean target blows everything away | |
11 | # It also removes auto-generated message/service code directories, | |
12 | # to handle the case where the original .msg/.srv file has been removed, | |
13 | # and thus CMake no longer knows about it. | |
14 | clean: | |
15 | -cd build && make clean | |
16 | #rm -rf msg/cpp msg/lisp msg/oct msg/java srv/cpp srv/lisp srv/oct srv/java src/$(PACKAGE_NAME)/msg src/$(PACKAGE_NAME)/srv | |
17 | rm -rf build | |
18 | ||
19 | # Build a source package. Assumes that you're in a clean tree. | |
20 | package_source: all | |
21 | $(shell rospack find rosbuild)/bin/package_source.py $(CURDIR) | |
22 | ||
23 | # All other targets are just passed through | |
24 | #test: all | |
25 | # if cd build && make -k $@; then make test-results; else make test-results && exit 1; fi | |
26 | #tests: all | |
27 | # cd build && make $@ | |
28 | #test-future: all | |
29 | # cd build && make -k $@ | |
30 | #gcoverage: all | |
31 | # cd build && make $@ | |
32 | ||
33 | #include $(shell rospack find mk)/buildtest.mk | |
34 | ||
35 |
0 | # This file can be used to automate downloading and unpacking of source | |
1 | # distributions | |
2 | # | |
3 | # Before including this file, define the following make variables: | |
4 | # | |
5 | # TARBALL: name of source distribution to be downloaded | |
6 | # TARBALL_URL: full URL (including the file itself) to download | |
7 | # SOURCE_DIR: name of directory into which TARBALL will unpack | |
8 | # UNPACK_CMD: command to use when unpacking (e.g., tar xzf) | |
9 | # TARBALL_PATCH: patch files to apply to SOURCE_DIR | |
10 | # INITIAL_DIR: set this if the tarball unpacks to a different dir than SOURCE_DIR, | |
11 | # and you want the directory moved | |
12 | # | |
13 | # Optional variables: | |
14 | # MD5SUM_FILE: name of md5sum file to check before unpacking | |
15 | # | |
16 | # Because this file declares targets, you almost certainly want to declare | |
17 | # your own 'all' target before including this file. Otherwise, the first | |
18 | # target declared here will become the default. | |
19 | ||
20 | # This target is pretty much vestigial, and is only here to support the | |
21 | # download target. The intended use it to depend on the unpacked file, | |
22 | # below, which repeats the download and check logic. | |
23 | $(TARBALL): | |
24 | -mkdir -p build | |
25 | ifneq ($(strip $(MD5SUM_FILE)),) | |
26 | if [ ! -f $(MD5SUM_FILE) ]; then echo "Error: Couldn't find md5sum file $(MD5SUM_FILE)" && false; fi | |
27 | `rospack find rosbuild`/bin/download_checkmd5.py $(TARBALL_URL) $(TARBALL) `awk {'print $$1'} $(MD5SUM_FILE)` | |
28 | else | |
29 | `rospack find rosbuild`/bin/download_checkmd5.py $(TARBALL_URL) $(TARBALL) | |
30 | endif | |
31 | touch -c $(TARBALL) | |
32 | ||
33 | download: $(TARBALL) | |
34 | ||
35 | $(SOURCE_DIR)/unpacked: $(TARBALL_PATCH) | |
36 | -mkdir -p build | |
37 | ifneq ($(strip $(MD5SUM_FILE)),) | |
38 | if [ ! -f $(MD5SUM_FILE) ]; then echo "Error: Couldn't find md5sum file $(MD5SUM_FILE)" && false; fi | |
39 | `rospack find rosbuild`/bin/download_checkmd5.py $(TARBALL_URL) $(TARBALL) `awk {'print $$1'} $(MD5SUM_FILE)` | |
40 | else | |
41 | `rospack find rosbuild`/bin/download_checkmd5.py $(TARBALL_URL) $(TARBALL) | |
42 | endif | |
43 | touch -c $(TARBALL) | |
44 | rm -rf $(SOURCE_DIR) $(INITIAL_DIR) | |
45 | ifneq ($(strip $(UNPACK_CMD)),) | |
46 | cd build; $(UNPACK_CMD) ../$(TARBALL) | |
47 | else | |
48 | cd build; tar xzf ../$(TARBALL) | |
49 | endif | |
50 | ifneq ($(strip $(INITIAL_DIR)),) | |
51 | mv $(INITIAL_DIR) $(SOURCE_DIR) | |
52 | endif | |
53 | ifneq ($(strip $(TARBALL_PATCH)),) | |
54 | $(foreach patch,$(TARBALL_PATCH), patch -d $(SOURCE_DIR) -p0 < $(patch);) | |
55 | endif | |
56 | touch $(SOURCE_DIR)/unpacked | |
57 |
0 | //{ | |
1 | if(index($0,"VERBOSE=1")>0) { | |
2 | printf "\t\t\t\t\t<value>VERBOSE=1|" | |
3 | printf "ROS_ROOT="ENVIRON["ROS_ROOT"]"|" | |
4 | printf "ROS_PACKAGE_PATH="ENVIRON["ROS_PACKAGE_PATH"]"|" | |
5 | printf "PYTHONPATH="ENVIRON["PYTHONPATH"]"|" | |
6 | printf "PATH="ENVIRON["PATH"]"|" | |
7 | print "</value>" | |
8 | } else { | |
9 | print $0 | |
10 | } | |
11 | } | |
12 |
0 | # This file can be used to automate cloning and patching of 3rd | |
1 | # party software. | |
2 | # | |
3 | # Before including this file, e.g.: | |
4 | # include $(shell rospack find mk)/git_checkout.mk | |
5 | # define the following make variables: | |
6 | # GIT_DIR: name of directory into which you want to clone to | |
7 | # GIT_URL: full URL to download | |
8 | # GIT_PATCH: your (list of) patch file(s) to patch the downloaded software | |
9 | # GIT_REVISION: - | |
10 | $(GIT_DIR): | |
11 | git clone $(GIT_URL) $(GIT_DIR) | |
12 | cd $(GIT_DIR) && git checkout $(GIT_REVISION) | |
13 | touch rospack_nosubdirs | |
14 | ||
15 | patched: $(GIT_DIR) $(GIT_PATCH) Makefile | |
16 | cd $(GIT_DIR) && git reset --hard | |
17 | ifneq ($(strip $(GIT_PATCH)),) | |
18 | $(foreach PATCH, $(GIT_PATCH), patch -d $(GIT_DIR) -p1 < $(PATCH) && ) echo patched | |
19 | touch rospack_nosubdirs | |
20 | touch patched | |
21 | endif | |
22 | ||
23 | download: $(GIT_DIR) patched |
0 | # This file can be used to automate cloning and patching of 3rd | |
1 | # party software. | |
2 | # | |
3 | # Before including this file, e.g.: | |
4 | # include $(shell rospack find mk)/hg_checkout.mk | |
5 | # define the following make variables: | |
6 | # HG_DIR: name of directory into which you want to clone to | |
7 | # HG_URL: full URL to download | |
8 | # HG_PATCH: your (list of) patch file(s) to patch the downloaded software | |
9 | # HG_BRANCH: the branch of the repository to update | |
10 | # HG_REVISION: the revision to update | |
11 | ||
12 | $(HG_DIR): | |
13 | mkdir -p $(HG_DIR) | |
14 | hg clone $(HG_URL) $(HG_DIR) | |
15 | cd $(HG_DIR) && hg update $(HG_BRANCH) && hg update $(HG_REVISION) | |
16 | touch rospack_nosubdirs | |
17 | ||
18 | patched: $(HG_PATCH) Makefile | |
19 | ifneq ($(strip $(HG_PATCH)),) | |
20 | cd $(HG_DIR) && hg revert --all | |
21 | $(foreach PATCH, $(HG_PATCH), patch -d $(HG_DIR) -p1 < $(PATCH) && ) echo patched | |
22 | touch rospack_nosubdirs | |
23 | touch patched | |
24 | endif | |
25 | ||
26 | download: $(HG_DIR) patched |
0 | #!/usr/bin/python | |
1 | ||
2 | import sys | |
3 | import os | |
4 | PKG = os.path.split(os.getcwd())[1] | |
5 | print "Creating pydev project for package '%s'"%PKG | |
6 | import roslib; roslib.load_manifest(PKG) | |
7 | ||
8 | pathlist = "\n".join(["<path>%s</path>"%path for path in sys.path if os.path.exists(path)]) | |
9 | ||
10 | pydev_project= '''<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
11 | <?eclipse-pydev version="1.0"?> | |
12 | ||
13 | <pydev_project> | |
14 | <pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property> | |
15 | <pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property> | |
16 | <pydev_pathproperty name="org.python.pydev.PROJECT_EXTERNAL_SOURCE_PATH"> | |
17 | %s | |
18 | </pydev_pathproperty> | |
19 | </pydev_project> | |
20 | '''%pathlist | |
21 | ||
22 | print "Writing .pydevproject, adding %d modules"%len(sys.path) | |
23 | f = open(".pydevproject","w") | |
24 | f.write(pydev_project) | |
25 | f.close() |
0 | <package> | |
1 | <description brief="ROS Makefile Includes">A collection of .mk include files for building ROS architectural elements. Most package authors should use cmake .mk, which calls CMake for the build of the package. The other files in this package are intended for use in exotic situations that mostly arise when importing 3rdparty code.</description> | |
2 | <author>Morgan Quigley, Brian Gerkey</author> | |
3 | <license>BSD</license> | |
4 | <review status="Doc reviewed" notes="2010/01/12"/> | |
5 | </package> |
0 | <package> | |
1 | <name>mk</name> | |
2 | <version>1.9.11</version> | |
3 | <description> | |
4 | A collection of .mk include files for building ROS architectural elements. Most package authors should use cmake .mk, which calls CMake for the build of the package. The other files in this package are intended for use in exotic situations that mostly arise when importing 3rdparty code. | |
5 | </description> | |
6 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
7 | <license>BSD</license> | |
8 | ||
9 | <url>http://www.ros.org/wiki/ROS</url> | |
10 | <author>Morgan Quigley</author> | |
11 | <author>Brian Gerkey</author> | |
12 | ||
13 | <build_depend>catkin</build_depend> | |
14 | </package> |
0 | ||
1 | ifeq ($(strip $(SVN_CMDLINE)),) | |
2 | SVN_CMDLINE = svn | |
3 | endif | |
4 | ||
5 | $(SVN_DIR): | |
6 | $(SVN_CMDLINE) co $(SVN_REVISION) $(SVN_URL) $(SVN_DIR) | |
7 | ifneq ($(strip $(SVN_PATCH)),) | |
8 | $(foreach patch,$(SVN_PATCH), patch -d $(SVN_DIR) -p0 < $(patch);) | |
9 | endif | |
10 | -if test -z "$(SVN_REVISION)" -o "x$(SVN_REVISION)" != "x-r `svn info $(SVN_DIR) | grep Revision | cut -d " " -f 2,2`"; then \ | |
11 | cd $(SVN_DIR) && $(SVN_CMDLINE) up $(SVN_REVISION); \ | |
12 | fi | |
13 | touch rospack_nosubdirs | |
14 | touch patched | |
15 | ||
16 | SVN_UP: $(SVN_DIR) | |
17 | ||
18 | # Note that 'svn revert' can't use the --non-interactive option, so we | |
19 | # invoke 'svn' directly, instead of calling $(SVN_CMDLINE) | |
20 | patched: $(SVN_PATCH) Makefile | |
21 | ifneq ($(strip $(SVN_PATCH)),) | |
22 | svn revert -R $(SVN_DIR) | |
23 | endif | |
24 | -cd $(SVN_DIR) && $(SVN_CMDLINE) up $(SVN_REVISION) | |
25 | $(foreach PATCH, $(SVN_PATCH), patch -d $(SVN_DIR) -p0 < $(PATCH) && ) echo patched | |
26 | touch rospack_nosubdirs | |
27 | touch patched | |
28 | ||
29 | SVN_UP_REVERT_PATCH: $(SVN_DIR) patched | |
30 | ||
31 | download: SVN_UP |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(rosbuild) | |
2 | find_package(catkin REQUIRED) | |
3 | catkin_package() | |
4 | catkin_package_export() | |
5 | ||
6 | install(DIRECTORY . | |
7 | DESTINATION ${CATKIN_GLOBAL_SHARE_DESTINATION}/ros/core/rosbuild | |
8 | PATTERN ".svn" EXCLUDE | |
9 | PATTERN "bin" EXCLUDE | |
10 | ) | |
11 | install(PROGRAMS | |
12 | bin/check_same_directories.py | |
13 | bin/download_checkmd5.py | |
14 | DESTINATION ${CATKIN_GLOBAL_SHARE_DESTINATION}/ros/core/rosbuild/bin) |
0 | # - a pkg-config module for CMake | |
1 | # | |
2 | # Usage: | |
3 | # pkg_check_modules(<PREFIX> [REQUIRED] <MODULE> [<MODULE>]*) | |
4 | # checks for all the given modules | |
5 | # | |
6 | # pkg_search_module(<PREFIX> [REQUIRED] <MODULE> [<MODULE>]*) | |
7 | # checks for given modules and uses the first working one | |
8 | # | |
9 | # When the 'REQUIRED' argument was set, macros will fail with an error | |
10 | # when module(s) could not be found | |
11 | # | |
12 | # It sets the following variables: | |
13 | # PKG_CONFIG_FOUND ... true iff pkg-config works on the system | |
14 | # PKG_CONFIG_EXECUTABLE ... pathname of the pkg-config program | |
15 | # <PREFIX>_FOUND ... set to 1 iff module(s) exist | |
16 | # | |
17 | # For the following variables two sets of values exist; first one is the | |
18 | # common one and has the given PREFIX. The second set contains flags | |
19 | # which are given out when pkgconfig was called with the '--static' | |
20 | # option. | |
21 | # <XPREFIX>_LIBRARIES ... only the libraries (w/o the '-l') | |
22 | # <XPREFIX>_LIBRARY_DIRS ... the paths of the libraries (w/o the '-L') | |
23 | # <XPREFIX>_LDFLAGS ... all required linker flags | |
24 | # <XPREFIX>_LDFLAGS_OTHER ... all other linker flags | |
25 | # <XPREFIX>_INCLUDE_DIRS ... the '-I' preprocessor flags (w/o the '-I') | |
26 | # <XPREFIX>_CFLAGS ... all required cflags | |
27 | # <XPREFIX>_CFLAGS_OTHER ... the other compiler flags | |
28 | # | |
29 | # <XPREFIX> = <PREFIX> for common case | |
30 | # <XPREFIX> = <PREFIX>_STATIC for static linking | |
31 | # | |
32 | # There are some special variables whose prefix depends on the count | |
33 | # of given modules. When there is only one module, <PREFIX> stays | |
34 | # unchanged. When there are multiple modules, the prefix will be | |
35 | # changed to <PREFIX>_<MODNAME>: | |
36 | # <XPREFIX>_VERSION ... version of the module | |
37 | # <XPREFIX>_PREFIX ... prefix-directory of the module | |
38 | # <XPREFIX>_INCLUDEDIR ... include-dir of the module | |
39 | # <XPREFIX>_LIBDIR ... lib-dir of the module | |
40 | # | |
41 | # <XPREFIX> = <PREFIX> when |MODULES| == 1, else | |
42 | # <XPREFIX> = <PREFIX>_<MODNAME> | |
43 | # | |
44 | # A <MODULE> parameter can have the following formats: | |
45 | # {MODNAME} ... matches any version | |
46 | # {MODNAME}>={VERSION} ... at least version <VERSION> is required | |
47 | # {MODNAME}={VERSION} ... exactly version <VERSION> is required | |
48 | # {MODNAME}<={VERSION} ... modules must not be newer than <VERSION> | |
49 | # | |
50 | # Examples | |
51 | # pkg_check_modules (GLIB2 glib-2.0) | |
52 | # | |
53 | # pkg_check_modules (GLIB2 glib-2.0>=2.10) | |
54 | # requires at least version 2.10 of glib2 and defines e.g. | |
55 | # GLIB2_VERSION=2.10.3 | |
56 | # | |
57 | # pkg_check_modules (FOO glib-2.0>=2.10 gtk+-2.0) | |
58 | # requires both glib2 and gtk2, and defines e.g. | |
59 | # FOO_glib-2.0_VERSION=2.10.3 | |
60 | # FOO_gtk+-2.0_VERSION=2.8.20 | |
61 | # | |
62 | # pkg_check_modules (XRENDER REQUIRED xrender) | |
63 | # defines e.g.: | |
64 | # XRENDER_LIBRARIES=Xrender;X11 | |
65 | # XRENDER_STATIC_LIBRARIES=Xrender;X11;pthread;Xau;Xdmcp | |
66 | # | |
67 | # pkg_search_module (BAR libxml-2.0 libxml2 libxml>=2) | |
68 | ||
69 | ||
70 | # Copyright (C) 2006 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de> | |
71 | # | |
72 | # Redistribution and use, with or without modification, are permitted | |
73 | # provided that the following conditions are met: | |
74 | # | |
75 | # 1. Redistributions must retain the above copyright notice, this | |
76 | # list of conditions and the following disclaimer. | |
77 | # 2. The name of the author may not be used to endorse or promote | |
78 | # products derived from this software without specific prior | |
79 | # written permission. | |
80 | # | |
81 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
82 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
83 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
84 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | |
85 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
86 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE | |
87 | # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
88 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER | |
89 | # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | |
90 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN | |
91 | # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
92 | ||
93 | ||
94 | ### Common stuff #### | |
95 | set(PKG_CONFIG_VERSION 1) | |
96 | set(PKG_CONFIG_FOUND 0) | |
97 | ||
98 | find_program(PKG_CONFIG_EXECUTABLE NAMES pkg-config DOC "pkg-config executable") | |
99 | mark_as_advanced(PKG_CONFIG_EXECUTABLE) | |
100 | ||
101 | if(PKG_CONFIG_EXECUTABLE) | |
102 | set(PKG_CONFIG_FOUND 1) | |
103 | endif(PKG_CONFIG_EXECUTABLE) | |
104 | ||
105 | ||
106 | # Unsets the given variables | |
107 | macro(_pkgconfig_unset var) | |
108 | set(${var} "" CACHE INTERNAL "") | |
109 | endmacro(_pkgconfig_unset) | |
110 | ||
111 | macro(_pkgconfig_set var value) | |
112 | set(${var} ${value} CACHE INTERNAL "") | |
113 | endmacro(_pkgconfig_set) | |
114 | ||
115 | # Invokes pkgconfig, cleans up the result and sets variables | |
116 | macro(_pkgconfig_invoke _pkglist _prefix _varname _regexp) | |
117 | set(_pkgconfig_invoke_result) | |
118 | ||
119 | execute_process( | |
120 | COMMAND ${PKG_CONFIG_EXECUTABLE} ${ARGN} ${_pkglist} | |
121 | OUTPUT_VARIABLE _pkgconfig_invoke_result | |
122 | RESULT_VARIABLE _pkgconfig_failed) | |
123 | ||
124 | if (_pkgconfig_failed) | |
125 | set(_pkgconfig_${_varname} "") | |
126 | _pkgconfig_unset(${_prefix}_${_varname}) | |
127 | else(_pkgconfig_failed) | |
128 | string(REGEX REPLACE "[\r\n]" " " _pkgconfig_invoke_result "${_pkgconfig_invoke_result}") | |
129 | string(REGEX REPLACE " +$" "" _pkgconfig_invoke_result "${_pkgconfig_invoke_result}") | |
130 | ||
131 | if (NOT ${_regexp} STREQUAL "") | |
132 | string(REGEX REPLACE "${_regexp}" " " _pkgconfig_invoke_result "${_pkgconfig_invoke_result}") | |
133 | endif(NOT ${_regexp} STREQUAL "") | |
134 | ||
135 | separate_arguments(_pkgconfig_invoke_result) | |
136 | ||
137 | #message(STATUS " ${_varname} ... ${_pkgconfig_invoke_result}") | |
138 | set(_pkgconfig_${_varname} ${_pkgconfig_invoke_result}) | |
139 | _pkgconfig_set(${_prefix}_${_varname} "${_pkgconfig_invoke_result}") | |
140 | endif(_pkgconfig_failed) | |
141 | endmacro(_pkgconfig_invoke) | |
142 | ||
143 | # Invokes pkgconfig two times; once without '--static' and once with | |
144 | # '--static' | |
145 | macro(_pkgconfig_invoke_dyn _pkglist _prefix _varname cleanup_regexp) | |
146 | _pkgconfig_invoke("${_pkglist}" ${_prefix} ${_varname} "${cleanup_regexp}" ${ARGN}) | |
147 | _pkgconfig_invoke("${_pkglist}" ${_prefix} STATIC_${_varname} "${cleanup_regexp}" --static ${ARGN}) | |
148 | endmacro(_pkgconfig_invoke_dyn) | |
149 | ||
150 | # Splits given arguments into options and a package list | |
151 | macro(_pkgconfig_parse_options _result _is_req) | |
152 | set(${_is_req} 0) | |
153 | ||
154 | foreach(_pkg ${ARGN}) | |
155 | if (_pkg STREQUAL "REQUIRED") | |
156 | set(${_is_req} 1) | |
157 | endif (_pkg STREQUAL "REQUIRED") | |
158 | endforeach(_pkg ${ARGN}) | |
159 | ||
160 | set(${_result} ${ARGN}) | |
161 | list(REMOVE_ITEM ${_result} "REQUIRED") | |
162 | endmacro(_pkgconfig_parse_options) | |
163 | ||
164 | ### | |
165 | macro(_pkg_check_modules_internal _is_required _is_silent _prefix) | |
166 | _pkgconfig_unset(${_prefix}_FOUND) | |
167 | _pkgconfig_unset(${_prefix}_VERSION) | |
168 | _pkgconfig_unset(${_prefix}_PREFIX) | |
169 | _pkgconfig_unset(${_prefix}_INCLUDEDIR) | |
170 | _pkgconfig_unset(${_prefix}_LIBDIR) | |
171 | _pkgconfig_unset(${_prefix}_LIBS) | |
172 | _pkgconfig_unset(${_prefix}_LIBS_L) | |
173 | _pkgconfig_unset(${_prefix}_LIBS_PATHS) | |
174 | _pkgconfig_unset(${_prefix}_LIBS_OTHER) | |
175 | _pkgconfig_unset(${_prefix}_CFLAGS) | |
176 | _pkgconfig_unset(${_prefix}_CFLAGS_I) | |
177 | _pkgconfig_unset(${_prefix}_CFLAGS_OTHER) | |
178 | _pkgconfig_unset(${_prefix}_STATIC_LIBDIR) | |
179 | _pkgconfig_unset(${_prefix}_STATIC_LIBS) | |
180 | _pkgconfig_unset(${_prefix}_STATIC_LIBS_L) | |
181 | _pkgconfig_unset(${_prefix}_STATIC_LIBS_PATHS) | |
182 | _pkgconfig_unset(${_prefix}_STATIC_LIBS_OTHER) | |
183 | _pkgconfig_unset(${_prefix}_STATIC_CFLAGS) | |
184 | _pkgconfig_unset(${_prefix}_STATIC_CFLAGS_I) | |
185 | _pkgconfig_unset(${_prefix}_STATIC_CFLAGS_OTHER) | |
186 | ||
187 | # create a better addressable variable of the modules and calculate its size | |
188 | set(_pkg_check_modules_list ${ARGN}) | |
189 | list(LENGTH _pkg_check_modules_list _pkg_check_modules_cnt) | |
190 | ||
191 | if(PKG_CONFIG_EXECUTABLE) | |
192 | # give out status message telling checked module | |
193 | if (NOT ${_is_silent}) | |
194 | if (_pkg_check_modules_cnt EQUAL 1) | |
195 | message(STATUS "checking for module '${_pkg_check_modules_list}'") | |
196 | else(_pkg_check_modules_cnt EQUAL 1) | |
197 | message(STATUS "checking for modules '${_pkg_check_modules_list}'") | |
198 | endif(_pkg_check_modules_cnt EQUAL 1) | |
199 | endif(NOT ${_is_silent}) | |
200 | ||
201 | set(_pkg_check_modules_packages) | |
202 | set(_pkg_check_modules_failed) | |
203 | ||
204 | # iterate through module list and check whether they exist and match the required version | |
205 | foreach (_pkg_check_modules_pkg ${_pkg_check_modules_list}) | |
206 | set(_pkg_check_modules_exist_query) | |
207 | ||
208 | # check whether version is given | |
209 | if (_pkg_check_modules_pkg MATCHES ".*(>=|=|<=).*") | |
210 | string(REGEX REPLACE "(.*[^><])(>=|=|<=)(.*)" "\\1" _pkg_check_modules_pkg_name "${_pkg_check_modules_pkg}") | |
211 | string(REGEX REPLACE "(.*[^><])(>=|=|<=)(.*)" "\\2" _pkg_check_modules_pkg_op "${_pkg_check_modules_pkg}") | |
212 | string(REGEX REPLACE "(.*[^><])(>=|=|<=)(.*)" "\\3" _pkg_check_modules_pkg_ver "${_pkg_check_modules_pkg}") | |
213 | else(_pkg_check_modules_pkg MATCHES ".*(>=|=|<=).*") | |
214 | set(_pkg_check_modules_pkg_name "${_pkg_check_modules_pkg}") | |
215 | set(_pkg_check_modules_pkg_op) | |
216 | set(_pkg_check_modules_pkg_ver) | |
217 | endif(_pkg_check_modules_pkg MATCHES ".*(>=|=|<=).*") | |
218 | ||
219 | # handle the operands | |
220 | if (_pkg_check_modules_pkg_op STREQUAL ">=") | |
221 | list(APPEND _pkg_check_modules_exist_query --atleast-version) | |
222 | endif(_pkg_check_modules_pkg_op STREQUAL ">=") | |
223 | ||
224 | if (_pkg_check_modules_pkg_op STREQUAL "=") | |
225 | list(APPEND _pkg_check_modules_exist_query --exact-version) | |
226 | endif(_pkg_check_modules_pkg_op STREQUAL "=") | |
227 | ||
228 | if (_pkg_check_modules_pkg_op STREQUAL "<=") | |
229 | list(APPEND _pkg_check_modules_exist_query --max-version) | |
230 | endif(_pkg_check_modules_pkg_op STREQUAL "<=") | |
231 | ||
232 | # create the final query which is of the format: | |
233 | # * --atleast-version <version> <pkg-name> | |
234 | # * --exact-version <version> <pkg-name> | |
235 | # * --max-version <version> <pkg-name> | |
236 | # * --exists <pkg-name> | |
237 | if (_pkg_check_modules_pkg_op) | |
238 | list(APPEND _pkg_check_modules_exist_query "${_pkg_check_modules_pkg_ver}") | |
239 | else(_pkg_check_modules_pkg_op) | |
240 | list(APPEND _pkg_check_modules_exist_query --exists) | |
241 | endif(_pkg_check_modules_pkg_op) | |
242 | ||
243 | _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_VERSION) | |
244 | _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_PREFIX) | |
245 | _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_INCLUDEDIR) | |
246 | _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_LIBDIR) | |
247 | ||
248 | list(APPEND _pkg_check_modules_exist_query "${_pkg_check_modules_pkg_name}") | |
249 | list(APPEND _pkg_check_modules_packages "${_pkg_check_modules_pkg_name}") | |
250 | ||
251 | # execute the query | |
252 | execute_process( | |
253 | COMMAND ${PKG_CONFIG_EXECUTABLE} ${_pkg_check_modules_exist_query} | |
254 | RESULT_VARIABLE _pkgconfig_retval) | |
255 | ||
256 | # evaluate result and tell failures | |
257 | if (_pkgconfig_retval) | |
258 | if(NOT ${_is_silent}) | |
259 | message(STATUS " package '${_pkg_check_modules_pkg}' not found") | |
260 | endif(NOT ${_is_silent}) | |
261 | ||
262 | set(_pkg_check_modules_failed 1) | |
263 | endif(_pkgconfig_retval) | |
264 | endforeach(_pkg_check_modules_pkg) | |
265 | ||
266 | if(_pkg_check_modules_failed) | |
267 | # fail when requested | |
268 | if (${_is_required}) | |
269 | message(SEND_ERROR "A required package was not found") | |
270 | endif (${_is_required}) | |
271 | else(_pkg_check_modules_failed) | |
272 | # when we are here, we checked whether requested modules | |
273 | # exist. Now, go through them and set variables | |
274 | ||
275 | _pkgconfig_set(${_prefix}_FOUND 1) | |
276 | list(LENGTH _pkg_check_modules_packages pkg_count) | |
277 | ||
278 | # iterate through all modules again and set individual variables | |
279 | foreach (_pkg_check_modules_pkg ${_pkg_check_modules_packages}) | |
280 | # handle case when there is only one package required | |
281 | if (pkg_count EQUAL 1) | |
282 | set(_pkg_check_prefix "${_prefix}") | |
283 | else(pkg_count EQUAL 1) | |
284 | set(_pkg_check_prefix "${_prefix}_${_pkg_check_modules_pkg}") | |
285 | endif(pkg_count EQUAL 1) | |
286 | ||
287 | _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" VERSION "" --modversion ) | |
288 | _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" PREFIX "" --variable=prefix ) | |
289 | _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" INCLUDEDIR "" --variable=includedir ) | |
290 | _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" LIBDIR "" --variable=libdir ) | |
291 | ||
292 | message(STATUS " found ${_pkg_check_modules_pkg}, version ${_pkgconfig_VERSION}") | |
293 | endforeach(_pkg_check_modules_pkg) | |
294 | ||
295 | # set variables which are combined for multiple modules | |
296 | _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LIBRARIES "(^| )-l" --libs-only-l ) | |
297 | _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LIBRARY_DIRS "(^| )-L" --libs-only-L ) | |
298 | _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LDFLAGS "" --libs ) | |
299 | _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LDFLAGS_OTHER "" --libs-only-other ) | |
300 | ||
301 | _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" INCLUDE_DIRS "(^| )-I" --cflags-only-I ) | |
302 | _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" CFLAGS "" --cflags ) | |
303 | _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" CFLAGS_OTHER "" --cflags-only-other ) | |
304 | endif(_pkg_check_modules_failed) | |
305 | else(PKG_CONFIG_EXECUTABLE) | |
306 | if (${_is_required}) | |
307 | message(SEND_ERROR "pkg-config tool not found") | |
308 | endif (${_is_required}) | |
309 | endif(PKG_CONFIG_EXECUTABLE) | |
310 | endmacro(_pkg_check_modules_internal) | |
311 | ||
312 | ### | |
313 | ### User visible macros start here | |
314 | ### | |
315 | ||
316 | ### | |
317 | macro(pkg_check_modules _prefix _module0) | |
318 | # check cached value | |
319 | if (NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION}) | |
320 | _pkgconfig_parse_options (_pkg_modules _pkg_is_required "${_module0}" ${ARGN}) | |
321 | _pkg_check_modules_internal("${_pkg_is_required}" 0 "${_prefix}" ${_pkg_modules}) | |
322 | ||
323 | _pkgconfig_set(__pkg_config_checked_${_prefix} ${PKG_CONFIG_VERSION}) | |
324 | endif(NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION}) | |
325 | endmacro(pkg_check_modules) | |
326 | ||
327 | ### | |
328 | macro(pkg_search_module _prefix _module0) | |
329 | # check cached value | |
330 | if (NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION} OR NOT ${_prefix}_FOUND) | |
331 | set(_pkg_modules_found 0) | |
332 | _pkgconfig_parse_options(_pkg_modules_alt _pkg_is_required "${_module0}" ${ARGN}) | |
333 | ||
334 | message(STATUS "checking for one of the modules '${_pkg_modules_alt}'") | |
335 | ||
336 | # iterate through all modules and stop at the first working one. | |
337 | foreach(_pkg_alt ${_pkg_modules_alt}) | |
338 | if(NOT _pkg_modules_found) | |
339 | _pkg_check_modules_internal(0 1 "${_prefix}" "${_pkg_alt}") | |
340 | endif(NOT _pkg_modules_found) | |
341 | ||
342 | if (${_prefix}_FOUND) | |
343 | set(_pkg_modules_found 1) | |
344 | endif(${_prefix}_FOUND) | |
345 | endforeach(_pkg_alt) | |
346 | ||
347 | if (NOT ${_prefix}_FOUND) | |
348 | if(${_pkg_is_required}) | |
349 | message(SEND_ERROR "None of the required '${_pkg_modules_alt}' found") | |
350 | endif(${_pkg_is_required}) | |
351 | endif(NOT ${_prefix}_FOUND) | |
352 | ||
353 | _pkgconfig_set(__pkg_config_checked_${_prefix} ${PKG_CONFIG_VERSION}) | |
354 | endif(NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION} OR NOT ${_prefix}_FOUND) | |
355 | endmacro(pkg_search_module) | |
356 | ||
357 | ### Local Variables: | |
358 | ### mode: cmake | |
359 | ### End: |
0 | # - ProcessorCount(var) | |
1 | # Determine the number of processors/cores and save value in ${var} | |
2 | # | |
3 | # Sets the variable named ${var} to the number of physical cores available on | |
4 | # the machine if the information can be determined. Otherwise it is set to 0. | |
5 | # Currently this functionality is only implemented for Windows, Mac OS X and | |
6 | # Unix systems providing getconf or the /proc/cpuinfo interface (e.g. Linux). | |
7 | ||
8 | # A more reliable way might be to compile a small C program that uses the CPUID | |
9 | # instruction, but that again requires compiler support or compiling assembler | |
10 | # code. | |
11 | ||
12 | #============================================================================= | |
13 | # Copyright 2010 Kitware, Inc. | |
14 | # | |
15 | # Distributed under the OSI-approved BSD License (the "License"); | |
16 | # see accompanying file Copyright.txt for details. | |
17 | # | |
18 | # This software is distributed WITHOUT ANY WARRANTY; without even the | |
19 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
20 | # See the License for more information. | |
21 | #============================================================================= | |
22 | # (To distribute this file outside of CMake, substitute the full | |
23 | # License text for the above reference.) | |
24 | ||
25 | function(ProcessorCount var) | |
26 | # Unknown: | |
27 | set(count 0) | |
28 | ||
29 | if(WIN32) | |
30 | # Windows: | |
31 | set(count "$ENV{NUMBER_OF_PROCESSORS}") | |
32 | #message("ProcessorCount: using environment variable") | |
33 | elseif(APPLE) | |
34 | # Mac: | |
35 | find_program(ProcessorCount_cmd_sysctl sysctl | |
36 | PATHS /usr/sbin) | |
37 | if(ProcessorCount_cmd_sysctl) | |
38 | execute_process(COMMAND ${ProcessorCount_cmd_sysctl} -n hw.ncpu | |
39 | OUTPUT_STRIP_TRAILING_WHITESPACE | |
40 | OUTPUT_VARIABLE count) | |
41 | #message("ProcessorCount: using sysctl '${ProcessorCount_cmd_sysctl}'") | |
42 | endif() | |
43 | else() | |
44 | # Linux (and other systems with getconf): | |
45 | find_program(ProcessorCount_cmd_getconf getconf) | |
46 | if(ProcessorCount_cmd_getconf) | |
47 | execute_process(COMMAND ${ProcessorCount_cmd_getconf} _NPROCESSORS_ONLN | |
48 | OUTPUT_STRIP_TRAILING_WHITESPACE | |
49 | OUTPUT_VARIABLE count) | |
50 | #message("ProcessorCount: using getconf '${ProcessorCount_cmd_getconf}'") | |
51 | endif() | |
52 | endif() | |
53 | ||
54 | # Execute this code when there is no 'sysctl' or 'getconf' or | |
55 | # when previously executed methods return empty output: | |
56 | # | |
57 | if(NOT count) | |
58 | # Systems with /proc/cpuinfo: | |
59 | set(cpuinfo_file /proc/cpuinfo) | |
60 | if(EXISTS "${cpuinfo_file}") | |
61 | file(STRINGS "${cpuinfo_file}" procs REGEX "^processor.: [0-9]+$") | |
62 | list(LENGTH procs count) | |
63 | #message("ProcessorCount: using cpuinfo '${cpuinfo_file}'") | |
64 | endif() | |
65 | endif() | |
66 | ||
67 | set(${var} ${count} PARENT_SCOPE) | |
68 | endfunction() | |
69 |
0 | #!/usr/bin/env python | |
1 | ||
2 | # Simple script to check whether two directories are the same. I'm doing | |
3 | # it in this script because the following command-line invocation produces | |
4 | # a syntax error for reasons that I don't understand: | |
5 | # | |
6 | # python -c 'import os; if os.path.realpath("/u/gerkey/code/ros/ros/core/rosconsole") != os.path.realpath("/u/gerkey/code/ros/ros/core/rosconsole"): raise Exception' | |
7 | ||
8 | import sys, os | |
9 | ||
10 | if __name__ == '__main__': | |
11 | if len(sys.argv) != 3: | |
12 | raise Exception | |
13 | if os.path.realpath(sys.argv[1]) != os.path.realpath(sys.argv[2]): | |
14 | raise Exception |
0 | #!/usr/bin/env python | |
1 | ||
2 | NAME="download_checkmd5.py" | |
3 | ||
4 | import urllib, hashlib, os, sys | |
5 | from optparse import OptionParser | |
6 | ||
7 | def main(): | |
8 | parser = OptionParser(usage="usage: %prog URI dest [md5sum]", prog=NAME) | |
9 | options, args = parser.parse_args() | |
10 | md5sum = None | |
11 | if len(args) == 2: | |
12 | uri, dest = args | |
13 | elif len(args) == 3: | |
14 | uri, dest, md5sum = args | |
15 | else: | |
16 | parser.error("wrong number of arguments") | |
17 | ||
18 | # Create intermediate directories as necessary, #2970 | |
19 | d = os.path.dirname(dest) | |
20 | if len(d) and not os.path.exists(d): | |
21 | os.makedirs(d) | |
22 | ||
23 | fresh = False | |
24 | if not os.path.exists(dest): | |
25 | sys.stdout.write('[rosbuild] Downloading %s to %s...'%(uri, dest)) | |
26 | sys.stdout.flush() | |
27 | urllib.urlretrieve(uri, dest) | |
28 | sys.stdout.write('Done\n') | |
29 | fresh = True | |
30 | ||
31 | if md5sum: | |
32 | m = hashlib.md5(open(dest).read()) | |
33 | d = m.hexdigest() | |
34 | ||
35 | print '[rosbuild] Checking md5sum on %s'%(dest) | |
36 | ||
37 | if d != md5sum: | |
38 | if not fresh: | |
39 | print '[rosbuild] WARNING: md5sum mismatch (%s != %s); re-downloading file %s'%(d, md5sum, dest) | |
40 | os.remove(dest) | |
41 | ||
42 | # Try one more time | |
43 | urllib.urlretrieve(uri, dest) | |
44 | m = hashlib.md5(open(dest).read()) | |
45 | d = m.hexdigest() | |
46 | ||
47 | if d != md5sum: | |
48 | print '[rosbuild] ERROR: md5sum mismatch (%s != %s) on %s; aborting'%(d, md5sum, dest) | |
49 | return 1 | |
50 | ||
51 | return 0 | |
52 | ||
53 | ||
54 | if __name__ == '__main__': | |
55 | sys.exit(main()) |
0 | #!/bin/sh | |
1 | ||
2 | # A helper script to invoke gcov from CMake, which seems to have problems | |
3 | # with pipes inside an add_custom_target() | |
4 | ||
5 | src=$1 | |
6 | p=$2 | |
7 | for f in `find $p -name \`basename $src\`.o`; do | |
8 | gcov -b -o $f $src 1>/dev/null 2>&1 | |
9 | done | |
10 | true |
0 | #!/usr/bin/env python | |
1 | ||
2 | import sys | |
3 | import fileinput | |
4 | import re | |
5 | import os.path | |
6 | ||
7 | USAGE = "USAGE: rosgcov_summarize <package_dir> <rosgcov_file>" | |
8 | ||
9 | if len(sys.argv) != 3: | |
10 | print USAGE | |
11 | sys.exit(-1) | |
12 | ||
13 | pkg = sys.argv[1] | |
14 | fname = sys.argv[2] | |
15 | ||
16 | if not os.path.exists(fname): | |
17 | print '[rosgcov] %s : %.2f%% (no coverage results)' % (os.path.split(pkg)[1],0.0) | |
18 | sys.exit(0) | |
19 | ||
20 | re_hit = re.compile('^ *[0-9]*:.*') | |
21 | re_miss = re.compile('^ *#####:.*') | |
22 | ||
23 | re_branch_hit = re.compile('^branch *[0-9] *taken [0-9]*.*') | |
24 | re_branch_miss = re.compile('^branch *[0-9] *never executed.*') | |
25 | ||
26 | ||
27 | files = [] | |
28 | finput = fileinput.input(fname) | |
29 | for l in finput: | |
30 | ls = l.strip().split(' ') | |
31 | f = os.path.join(ls[0],os.path.split(ls[1])[1]) | |
32 | files.append(f.strip()) | |
33 | ||
34 | total = 0 | |
35 | hits = 0 | |
36 | misses = 0 | |
37 | branch_total = 0 | |
38 | branch_hits = 0 | |
39 | branch_misses = 0 | |
40 | print '-------------------------------------------------------' | |
41 | print 'Coverage summary: ' | |
42 | print '-------------------------------------------------------' | |
43 | for f in files: | |
44 | prefix = os.path.commonprefix([pkg, f]) | |
45 | display_name = f[len(prefix):] | |
46 | if display_name[0] == '/': | |
47 | display_name = display_name[1:] | |
48 | print ' ' + display_name + ': ' | |
49 | gcov_fname = f + '.gcov' | |
50 | if not os.path.exists(gcov_fname): | |
51 | print 'WARNING: no coverage results for %s' % (display_name) | |
52 | continue | |
53 | gcovf = fileinput.input(gcov_fname) | |
54 | local_total = 0 | |
55 | local_hits = 0 | |
56 | local_misses = 0 | |
57 | local_branch_total = 0 | |
58 | local_branch_hits = 0 | |
59 | local_branch_misses = 0 | |
60 | for s in gcovf: | |
61 | if re_hit.match(s): | |
62 | local_hits += 1 | |
63 | local_total += 1 | |
64 | elif re_miss.match(s): | |
65 | local_misses += 1 | |
66 | local_total += 1 | |
67 | if re_branch_hit.match(s): | |
68 | local_branch_hits += 1 | |
69 | local_branch_total += 1 | |
70 | elif re_branch_miss.match(s): | |
71 | local_branch_misses += 1 | |
72 | local_branch_total += 1 | |
73 | ||
74 | print ' line: %.2f%% (%d / %d)' % ((100.0 * local_hits / max(local_total,1)), local_hits, local_total) | |
75 | hits += local_hits | |
76 | misses += local_misses | |
77 | total += local_total | |
78 | ||
79 | print ' branch: %.2f%% (%d / %d)' % ((100.0 * local_branch_hits / max(local_branch_total,1)), local_branch_hits, local_branch_total) | |
80 | branch_hits += local_branch_hits | |
81 | branch_misses += local_branch_misses | |
82 | branch_total += local_branch_total | |
83 | ||
84 | print '-------------------------------------------------------' | |
85 | print '[rosgcov] %s : %.2f%% (%d / %d)' % (os.path.split(pkg)[1],(100.0 * hits / max(total,1)), hits, total) | |
86 | print '[rosgcov] %s : branch %.2f%% (%d / %d)' % (os.path.split(pkg)[1],(100.0 * branch_hits / max(branch_total,1)), branch_hits, branch_total) | |
87 | print '-------------------------------------------------------' |
0 | <package> | |
1 | <description brief="ROS Build System"> | |
2 | ||
3 | rosbuild contains scripts for managing the CMake-based build system for ROS. | |
4 | ||
5 | </description> | |
6 | <author>Brian Gerkey, Troy Straszheim, Morgan Quigley</author> | |
7 | <license>BSD</license> | |
8 | <review status="Doc reviewed" notes="2010/01/20"/> | |
9 | <url>http://ros.org/wiki/rosbuild</url> | |
10 | <rosdep name="bzip2"/> | |
11 | </package> |
0 | <package> | |
1 | <name>rosbuild</name> | |
2 | <version>1.9.11</version> | |
3 | <description> | |
4 | ||
5 | rosbuild contains scripts for managing the CMake-based build system for ROS. | |
6 | ||
7 | </description> | |
8 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
9 | <license>BSD</license> | |
10 | ||
11 | <url>http://ros.org/wiki/rosbuild</url> | |
12 | <author>Brian Gerkey</author> | |
13 | <author>Troy Straszheim</author> | |
14 | <author>Morgan Quigley</author> | |
15 | ||
16 | <build_depend>catkin</build_depend> | |
17 | </package> |
0 | ############################################################################### | |
1 | # Internal macros below | |
2 | ||
3 | macro(_rosbuild_warn) | |
4 | message("[rosbuild] WARNING: " ${ARGV}) | |
5 | endmacro(_rosbuild_warn) | |
6 | ||
7 | macro(_rosbuild_warn_deprecate_rospack_prefix name) | |
8 | string(REPLACE rospack rosbuild new_name ${name}) | |
9 | message("[rosbuild] WARNING: ${name} is deprecated; please use ${new_name} instead") | |
10 | endmacro(_rosbuild_warn_deprecate_rospack_prefix) | |
11 | ||
12 | macro(_rosbuild_warn_deprecate_no_prefix name) | |
13 | message("[rosbuild] WARNING: ${name} is deprecated; please use rosbuild_${name} instead") | |
14 | endmacro(_rosbuild_warn_deprecate_no_prefix) | |
15 | ||
16 | # look up python interpreter, store in ${PYTHON_EXECUTABLE} | |
17 | find_package(PythonInterp) | |
18 | ||
19 | ############################################################################### | |
20 | # Macro to turn a list into a string (why doesn't CMake have this | |
21 | # built-in?) | |
22 | macro(_rosbuild_list_to_string _string _list) | |
23 | set(${_string}) | |
24 | foreach(_item ${_list}) | |
25 | string(LENGTH "${${_string}}" _len) | |
26 | if(${_len} GREATER 0) | |
27 | set(${_string} "${${_string}} ${_item}") | |
28 | else(${_len} GREATER 0) | |
29 | set(${_string} "${_item}") | |
30 | endif(${_len} GREATER 0) | |
31 | endforeach(_item) | |
32 | endmacro(_rosbuild_list_to_string) | |
33 | ||
34 | ############################################################################### | |
35 | # Macro to dequote a string, in order to properly construct a command line. | |
36 | # There must be an easier way to do this. | |
37 | macro(_rosbuild_dequote_string _out _in) | |
38 | set(${_out}) | |
39 | string(REGEX REPLACE " " ";" tmp "${_in}") | |
40 | foreach(_item ${tmp}) | |
41 | string(LENGTH "${${_out}}" _len) | |
42 | if(${_len} GREATER 0) | |
43 | set(${_out} ${${_out}} ${_item}) | |
44 | else(${_len} GREATER 0) | |
45 | set(${_out} ${_item}) | |
46 | endif(${_len} GREATER 0) | |
47 | endforeach(_item) | |
48 | endmacro(_rosbuild_dequote_string) | |
49 | ||
50 | ||
51 | # list(FIND) was introduced after cmake 2.4.6, so we write our own | |
52 | macro(_rosbuild_list_find _list _item _idx) | |
53 | set(${_idx} -1) | |
54 | list(LENGTH ${_list} _len) | |
55 | math(EXPR _total "${_len} - 1") | |
56 | foreach(_i RANGE ${_total}) | |
57 | list(GET ${_list} ${_i} _it) | |
58 | if(_it STREQUAL ${_item}) | |
59 | set(${_idx} ${_i}) | |
60 | endif(_it STREQUAL ${_item}) | |
61 | endforeach(_i) | |
62 | endmacro(_rosbuild_list_find) | |
63 | ||
64 | # Check validity of manifest.xml, to avoid esoteric build errors | |
65 | macro(_rosbuild_check_manifest) | |
66 | execute_process( | |
67 | COMMAND ${PYTHON_EXECUTABLE} -c "import roslib.manifest; roslib.manifest.parse_file('manifest.xml')" | |
68 | OUTPUT_VARIABLE _manifest_error | |
69 | ERROR_VARIABLE _manifest_error | |
70 | RESULT_VARIABLE _manifest_failed | |
71 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} | |
72 | OUTPUT_STRIP_TRAILING_WHITESPACE | |
73 | ) | |
74 | if(_manifest_failed) | |
75 | message("[rosbuild] Error from syntax check of ${PROJECT_NAME}/manifest.xml") | |
76 | message("${_manifest_error}") | |
77 | message(FATAL_ERROR "[rosbuild] Syntax check of ${PROJECT_NAME}/manifest.xml failed; aborting") | |
78 | endif(_manifest_failed) | |
79 | ||
80 | ||
81 | endmacro(_rosbuild_check_manifest) | |
82 | ||
83 | # Check that the directory where we're building is also where rospack | |
84 | # thinks that the package lives, to avoid esoteric build errors. | |
85 | macro(_rosbuild_check_package_location) | |
86 | # Ask rospack where our package is | |
87 | rosbuild_find_ros_package(${PROJECT_NAME}) | |
88 | # Compare to where we are | |
89 | execute_process( | |
90 | COMMAND $ENV{ROS_ROOT}/core/rosbuild/bin/check_same_directories.py ${${PROJECT_NAME}_PACKAGE_PATH} ${PROJECT_SOURCE_DIR} | |
91 | OUTPUT_VARIABLE _rosbuild_check_package_location_error | |
92 | ERROR_VARIABLE _rosbuild_check_package_location_error | |
93 | RESULT_VARIABLE _rosbuild_check_package_location_failed | |
94 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} | |
95 | OUTPUT_STRIP_TRAILING_WHITESPACE | |
96 | ) | |
97 | if(_rosbuild_check_package_location_failed) | |
98 | message(FATAL_ERROR "[rosbuild] rospack found package \"${PROJECT_NAME}\" at \"${${PROJECT_NAME}_PACKAGE_PATH}\", but the current directory is \"${PROJECT_SOURCE_DIR}\". You should double-check your ROS_PACKAGE_PATH to ensure that packages are found in the correct precedence order.") | |
99 | endif(_rosbuild_check_package_location_failed) | |
100 | endmacro(_rosbuild_check_package_location) | |
101 | ||
102 | # helper function to register check that results were generated (#580) | |
103 | macro(_rosbuild_check_rostest_xml_result test_name test_file) | |
104 | add_custom_target(${test_name}_result | |
105 | COMMAND ${rosunit_path}/bin/check_test_ran.py ${test_file} | |
106 | VERBATIM) | |
107 | # Redeclaration of target is to workaround bug in 2.4.6 | |
108 | if(CMAKE_MINOR_VERSION LESS 6) | |
109 | add_custom_target(test-results-run) | |
110 | endif(CMAKE_MINOR_VERSION LESS 6) | |
111 | add_dependencies(test-results-run ${test_name}_result) | |
112 | endmacro(_rosbuild_check_rostest_xml_result test_name) | |
113 | ||
114 | macro(_rosbuild_add_gtest exe) | |
115 | # Look for optional TIMEOUT argument, #2645 | |
116 | parse_arguments(_gtest "TIMEOUT" "" ${ARGN}) | |
117 | if(NOT _gtest_TIMEOUT) | |
118 | set(_gtest_TIMEOUT 60.0) | |
119 | endif(NOT _gtest_TIMEOUT) | |
120 | ||
121 | # Create the program, with basic + gtest build flags | |
122 | rosbuild_add_executable(${exe} EXCLUDE_FROM_ALL ${_gtest_DEFAULT_ARGS}) | |
123 | rosbuild_add_gtest_build_flags(${exe}) | |
124 | ||
125 | # Create a legal target name, in case the target name has slashes in it | |
126 | string(REPLACE "/" "_" _testname ${exe}) | |
127 | ||
128 | ||
129 | # Create target for this test | |
130 | # We use rosunit to call the executable to get process control, #1629, #3112 | |
131 | # But don't depend on the gtest executable if rosbuild_test_nobuild is set, #3008 | |
132 | if(NOT rosbuild_test_nobuild) | |
133 | add_custom_target(test_${_testname} | |
134 | COMMAND ${rosunit_path}/bin/rosunit --name=${_testname} --time-limit=${_gtest_TIMEOUT} ${EXECUTABLE_OUTPUT_PATH}/${exe} | |
135 | DEPENDS ${EXECUTABLE_OUTPUT_PATH}/${exe} | |
136 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} | |
137 | VERBATIM) | |
138 | else(NOT rosbuild_test_nobuild) | |
139 | add_custom_target(test_${_testname} | |
140 | COMMAND ${rosunit_path}/bin/rosunit --name=${_testname} --time-limit=${_gtest_TIMEOUT} ${EXECUTABLE_OUTPUT_PATH}/${exe} | |
141 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} | |
142 | VERBATIM) | |
143 | endif(NOT rosbuild_test_nobuild) | |
144 | # Don't register to check xml output here, because we may have gotten | |
145 | # here through registration of a future test. Eventually, we should pass | |
146 | # in the overriding target (e.g., test-results vs. test-future-results). | |
147 | # For now, we call _rosbuild_check_rostest_xml_result() in rosbuild_add_gtest() instead. | |
148 | #_rosbuild_check_rostest_xml_result(test_${_testname} ${rosbuild_test_results_dir}/${PROJECT_NAME}/${_testname}.xml) | |
149 | ||
150 | # Make sure that any messages get generated prior to building this target | |
151 | add_dependencies(${exe} rospack_genmsg) | |
152 | add_dependencies(${exe} rospack_gensrv) | |
153 | ||
154 | # Make sure all test programs are built before running this test | |
155 | # but not if rosbuild_test_nobuild is set, #3008 | |
156 | if(NOT rosbuild_test_nobuild) | |
157 | add_dependencies(test_${_testname} tests) | |
158 | endif(NOT rosbuild_test_nobuild) | |
159 | ||
160 | endmacro(_rosbuild_add_gtest) | |
161 | ||
162 | # helper function to register check that results were generated (#580) | |
163 | # this one specifically targets rostest. rostest requires different | |
164 | # arguments as cmake doesn't know the name of the output file | |
165 | macro(_rosbuild_check_rostest_result test_name test_pkg test_file) | |
166 | add_custom_target(${test_name}_result | |
167 | COMMAND ${rosunit_path}/bin/check_test_ran.py --rostest ${test_pkg} ${test_file} | |
168 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} | |
169 | VERBATIM) | |
170 | # Redeclaration of target is to workaround bug in 2.4.6 | |
171 | if(CMAKE_MINOR_VERSION LESS 6) | |
172 | add_custom_target(test-results-run) | |
173 | endif(CMAKE_MINOR_VERSION LESS 6) | |
174 | add_dependencies(test-results-run ${test_name}_result) | |
175 | endmacro(_rosbuild_check_rostest_result test_name) | |
176 | ||
177 | macro(_rosbuild_add_rostest file) | |
178 | ||
179 | # Check that the file exists, #1621 | |
180 | set(_file_name _file_name-NOTFOUND) | |
181 | find_file(_file_name ${file} ${PROJECT_SOURCE_DIR} /) | |
182 | if(NOT _file_name) | |
183 | message(FATAL_ERROR "Can't find rostest file \"${file}\"") | |
184 | endif(NOT _file_name) | |
185 | ||
186 | # Create a legal target name, in case the target name has slashes in it | |
187 | string(REPLACE "/" "_" _testname ${file}) | |
188 | ||
189 | # Create target for this test | |
190 | add_custom_target(rostest_${_testname} | |
191 | COMMAND ${ARGN} rostest ${file} | |
192 | DEPENDS ${file} | |
193 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} | |
194 | VERBATIM) | |
195 | ||
196 | # Make sure all test programs are built before running this test | |
197 | # but not if rosbuild_test_nobuild is set, #3008 | |
198 | if(NOT rosbuild_test_nobuild) | |
199 | add_dependencies(rostest_${_testname} tests) | |
200 | endif(NOT rosbuild_test_nobuild) | |
201 | ||
202 | # rostest-check-results will do the magic of fixing an incorrect file extension | |
203 | # Don't register to check rostest output here, because we may have gotten | |
204 | # here through registration of a future test. Eventually, we should pass | |
205 | # in the overriding target (e.g., test-results vs. test-future-results). | |
206 | # For now, we call _rosbuild_check_rostest_xml_result() in | |
207 | # rosbuild_add_rostest() | |
208 | # and rosbuild_add_rostest_future() instead. | |
209 | #_rosbuild_check_rostest_result(rostest_${_testname} ${PROJECT_NAME} ${file}) | |
210 | endmacro(_rosbuild_add_rostest) | |
211 | ||
212 | macro(_rosbuild_add_pyunit file) | |
213 | # Look for optional TIMEOUT argument, #2645 | |
214 | parse_arguments(_pyunit "TIMEOUT" "" ${ARGN}) | |
215 | if(NOT _pyunit_TIMEOUT) | |
216 | set(_pyunit_TIMEOUT 60.0) | |
217 | endif(NOT _pyunit_TIMEOUT) | |
218 | ||
219 | # Check that the file exists, #1621 | |
220 | set(_file_name _file_name-NOTFOUND) | |
221 | find_file(_file_name ${file} ${PROJECT_SOURCE_DIR} /) | |
222 | if(NOT _file_name) | |
223 | message(FATAL_ERROR "Can't find pyunit file \"${file}\"") | |
224 | endif(NOT _file_name) | |
225 | ||
226 | # Create a legal target name, in case the target name has slashes in it | |
227 | string(REPLACE "/" "_" _testname ${file}) | |
228 | ||
229 | # We look for ROS_TEST_COVERAGE=1 | |
230 | # to indicate that coverage reports are being requested. | |
231 | if("$ENV{ROS_TEST_COVERAGE}" STREQUAL "1") | |
232 | set(_covarg "--cov") | |
233 | else("$ENV{ROS_TEST_COVERAGE}" STREQUAL "1") | |
234 | set(_covarg) | |
235 | endif("$ENV{ROS_TEST_COVERAGE}" STREQUAL "1") | |
236 | ||
237 | # Create target for this test | |
238 | # We use rostest to call the executable to get process control, #1629 | |
239 | add_custom_target(pyunit_${_testname} | |
240 | COMMAND ${rosunit_path}/bin/rosunit --name=${_testname} --time-limit=${_pyunit_TIMEOUT} -- ${file} ${_covarg} | |
241 | DEPENDS ${file} | |
242 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} | |
243 | VERBATIM) | |
244 | ||
245 | # Make sure all test programs are built before running this test | |
246 | # but not if rosbuild_test_nobuild is set, #3008 | |
247 | if(NOT rosbuild_test_nobuild) | |
248 | add_dependencies(pyunit_${_testname} tests) | |
249 | endif(NOT rosbuild_test_nobuild) | |
250 | ||
251 | endmacro(_rosbuild_add_pyunit) | |
252 | ||
253 | # Actual signature: | |
254 | # _rosbuild_add_roslaunch_check targetname file var=val var=val... | |
255 | macro(_rosbuild_add_roslaunch_check targetname file) | |
256 | # Check that the file exists, #1621 | |
257 | set(_file_name _file_name-NOTFOUND) | |
258 | find_file(_file_name ${file} ${CMAKE_CURRENT_SOURCE_DIR} /) | |
259 | if(NOT _file_name) | |
260 | message(FATAL_ERROR "Can't find roslaunch file or directory \"${file}\"") | |
261 | endif(NOT _file_name) | |
262 | ||
263 | # Find rostest | |
264 | rosbuild_invoke_rospack("" rostest path find rostest) | |
265 | ||
266 | # Create target for this test. | |
267 | # First check version on ros_comm to see whether we can give | |
268 | # roslaunch-check.py an output file name (first appears in 1.6.3/1.7.0) | |
269 | # Related to #3674. | |
270 | rosbuild_get_stack_version(ros_comm_version "ros_comm") | |
271 | if(ros_comm_version VERSION_LESS "1.6.3") | |
272 | add_custom_target(${targetname} | |
273 | COMMAND ${rostest_path}/scripts/roslaunch-check.py ${file} ${ARGN} | |
274 | DEPENDS ${file} | |
275 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} | |
276 | VERBATIM) | |
277 | else() | |
278 | # It's the newer roslaunch-check.py, so we can pass the full path to | |
279 | # the test result file. | |
280 | add_custom_target(${targetname} | |
281 | COMMAND ${rostest_path}/scripts/roslaunch-check.py -o ${rosbuild_test_results_dir}/${PROJECT_NAME}/TEST-${targetname}.xml ${file} ${ARGN} | |
282 | DEPENDS ${file} | |
283 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} | |
284 | VERBATIM) | |
285 | endif() | |
286 | ||
287 | # Make sure all test programs are built before running this test | |
288 | # but not if rosbuild_test_nobuild is set, #3008 | |
289 | if(NOT rosbuild_test_nobuild) | |
290 | add_dependencies(${targetname} tests) | |
291 | endif(NOT rosbuild_test_nobuild) | |
292 | ||
293 | endmacro(_rosbuild_add_roslaunch_check) | |
294 | ||
295 | macro(_rosbuild_wget_and_build tarball tarball_url tarball_dir unpack_cmd configure_cmd make_cmd install_cmd) | |
296 | find_package(Wget REQUIRED) | |
297 | ||
298 | _rosbuild_dequote_string(_unpack_cmd ${unpack_cmd}) | |
299 | _rosbuild_dequote_string(_configure_cmd ${configure_cmd}) | |
300 | _rosbuild_dequote_string(_make_cmd ${make_cmd}) | |
301 | _rosbuild_dequote_string(_install_cmd ${install_cmd}) | |
302 | ||
303 | add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/${tarball} | |
304 | COMMAND ${WGET_EXECUTABLE} ${tarball_url} -O ${tarball} | |
305 | VERBATIM) | |
306 | ||
307 | add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/${tarball_dir} | |
308 | COMMAND ${_unpack_cmd} ${tarball} | |
309 | COMMAND touch ${tarball_dir} | |
310 | DEPENDS ${PROJECT_BINARY_DIR}/${tarball} | |
311 | VERBATIM) | |
312 | ||
313 | add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/installed | |
314 | COMMAND cmake -E chdir ${PROJECT_BINARY_DIR}/${tarball_dir} ${_configure_cmd} | |
315 | COMMAND cmake -E chdir ${PROJECT_BINARY_DIR}/${tarball_dir} ${_make_cmd} | |
316 | COMMAND cmake -E chdir ${PROJECT_BINARY_DIR}/${tarball_dir} ${_install_cmd} | |
317 | COMMAND touch ${PROJECT_BINARY_DIR}/installed | |
318 | DEPENDS ${PROJECT_BINARY_DIR}/${tarball_dir} | |
319 | VERBATIM) | |
320 | ||
321 | add_custom_target(fetch_and_build ALL | |
322 | DEPENDS ${PROJECT_BINARY_DIR}/installed) | |
323 | endmacro(_rosbuild_wget_and_build) | |
324 | ||
325 | macro(_rosbuild_add_library lib libname type) | |
326 | ||
327 | add_library(${lib} ${type} ${ARGN}) | |
328 | ||
329 | if(${type} STREQUAL STATIC) | |
330 | # Set output name to be the same as shared lib (may not work on Windows) | |
331 | set_target_properties(${lib} PROPERTIES OUTPUT_NAME ${libname}) | |
332 | # Also add -fPIC, because CMake leaves it out when building static | |
333 | # libs, even though it's necessary on 64-bit machines for linking this | |
334 | # lib against shared libs downstream. The only exception is mingw gcc | |
335 | # which doesn't specifically need to worry about building | |
336 | # position independant libs. | |
337 | if(NOT MINGW) | |
338 | rosbuild_add_compile_flags(${lib} -fPIC) | |
339 | endif() | |
340 | endif(${type} STREQUAL STATIC) | |
341 | ||
342 | # Add explicit dependency of each file on our manifest.xml and those of | |
343 | # our dependencies | |
344 | get_target_property(_srclist ${lib} SOURCES) | |
345 | foreach(_src ${_srclist}) | |
346 | set(_file_name _file_name-NOTFOUND) | |
347 | find_file(_file_name ${_src} ${CMAKE_CURRENT_SOURCE_DIR} /) | |
348 | if(NOT _file_name) | |
349 | message("[rosbuild] Couldn't find source file ${_src}; assuming that it is in ${CMAKE_CURRENT_SOURCE_DIR} and will be generated later") | |
350 | set(_file_name ${CMAKE_CURRENT_SOURCE_DIR}/${_src}) | |
351 | endif(NOT _file_name) | |
352 | add_file_dependencies(${_file_name} ${ROS_MANIFEST_LIST}) | |
353 | endforeach(_src) | |
354 | ||
355 | # Prevent deletion of existing lib of same name | |
356 | set_target_properties(${lib} PROPERTIES CLEAN_DIRECT_OUTPUT 1) | |
357 | # Attach compile and link flags | |
358 | rosbuild_add_compile_flags(${lib} ${${PROJECT_NAME}_CFLAGS_OTHER}) | |
359 | rosbuild_add_link_flags(${lib} ${${PROJECT_NAME}_LDFLAGS_OTHER}) | |
360 | # Link lib against dependent libs | |
361 | target_link_libraries(${lib} ${${PROJECT_NAME}_LIBRARIES}) | |
362 | ||
363 | # Add ROS-wide compile and link flags (usually things like -Wall). These | |
364 | # are set in rosconfig.cmake. | |
365 | rosbuild_add_compile_flags(${lib} ${ROS_COMPILE_FLAGS}) | |
366 | rosbuild_add_link_flags(${lib} ${ROS_LINK_FLAGS}) | |
367 | ||
368 | # Make sure to do any prebuild work (e.g., msg/srv generation) before | |
369 | # building this target. | |
370 | add_dependencies(${lib} rosbuild_precompile) | |
371 | endmacro(_rosbuild_add_library) | |
372 | ||
373 | macro(_rosbuild_get_clock var) | |
374 | execute_process( | |
375 | COMMAND ${PYTHON_EXECUTABLE} -c "import time, sys; sys.stdout.write(str(time.time()));" | |
376 | OUTPUT_VARIABLE ${var} | |
377 | ERROR_VARIABLE _time_error | |
378 | RESULT_VARIABLE _time_failed | |
379 | OUTPUT_STRIP_TRAILING_WHITESPACE | |
380 | ) | |
381 | if(_time_failed) | |
382 | message("[rosbuild] Error from calling to Python to get system time:") | |
383 | message("${_time_error}") | |
384 | message(FATAL_ERROR "[rosbuild] Failed to get system time; aborting") | |
385 | endif(_time_failed) | |
386 | endmacro(_rosbuild_get_clock var) | |
387 | ||
388 | macro(_rosbuild_cmakelist_to_pylist _cmakelist _pylist) | |
389 | # Convert a CMake list into a Python list | |
390 | set(_pyl "[") | |
391 | foreach(_f ${_cmakelist}) | |
392 | set(_pyl "${_pyl} '${_f}',") | |
393 | endforeach(_f) | |
394 | set(_pyl "${_pyl}]") | |
395 | set(${_pylist} "${_pyl}") | |
396 | endmacro(_rosbuild_cmakelist_to_pylist _cmakelist _pylist) | |
397 | ||
398 | macro(_rosbuild_compare_manifests var _t _c _m) | |
399 | if("${_t}" STREQUAL "") | |
400 | # No time was given, so it's too old | |
401 | set(${var} 1) | |
402 | else("${_t}" STREQUAL "") | |
403 | _rosbuild_cmakelist_to_pylist("${_m}" _pylist) | |
404 | _rosbuild_cmakelist_to_pylist("${_c}" _cached_pylist) | |
405 | ||
406 | # Call Python to compare the provided time to the latest mtime on all | |
407 | # the files | |
408 | execute_process( | |
409 | COMMAND ${PYTHON_EXECUTABLE} -c "import os, sys; sys.stdout.write('1' if set(${_pylist}) != set(${_cached_pylist}) or ${_t} < max(os.stat(f).st_mtime for f in ${_pylist}) else '0');" | |
410 | OUTPUT_VARIABLE ${var} | |
411 | ERROR_VARIABLE _mtime_error | |
412 | RESULT_VARIABLE _mtime_failed | |
413 | OUTPUT_STRIP_TRAILING_WHITESPACE | |
414 | ) | |
415 | if(_mtime_failed) | |
416 | message("[rosbuild] Error from calling to Python to get latest mtime:") | |
417 | message("${_mtime_error}") | |
418 | message(FATAL_ERROR "[rosbuild] Failed to get latest mtime; aborting") | |
419 | endif(_mtime_failed) | |
420 | endif("${_t}" STREQUAL "") | |
421 | endmacro(_rosbuild_compare_manifests var _t) | |
422 | ||
423 | # parse_arguments() taken from | |
424 | # http://www.itk.org/Wiki/CMakeMacroParseArguments | |
425 | MACRO(PARSE_ARGUMENTS prefix arg_names option_names) | |
426 | SET(DEFAULT_ARGS) | |
427 | FOREACH(arg_name ${arg_names}) | |
428 | SET(${prefix}_${arg_name}) | |
429 | ENDFOREACH(arg_name) | |
430 | FOREACH(option ${option_names}) | |
431 | SET(${prefix}_${option} FALSE) | |
432 | ENDFOREACH(option) | |
433 | ||
434 | SET(current_arg_name DEFAULT_ARGS) | |
435 | SET(current_arg_list) | |
436 | FOREACH(arg ${ARGN}) | |
437 | SET(larg_names ${arg_names}) | |
438 | LIST(FIND larg_names "${arg}" is_arg_name) | |
439 | IF (is_arg_name GREATER -1) | |
440 | SET(${prefix}_${current_arg_name} ${current_arg_list}) | |
441 | SET(current_arg_name ${arg}) | |
442 | SET(current_arg_list) | |
443 | ELSE (is_arg_name GREATER -1) | |
444 | SET(loption_names ${option_names}) | |
445 | LIST(FIND loption_names "${arg}" is_option) | |
446 | IF (is_option GREATER -1) | |
447 | SET(${prefix}_${arg} TRUE) | |
448 | ELSE (is_option GREATER -1) | |
449 | SET(current_arg_list ${current_arg_list} ${arg}) | |
450 | ENDIF (is_option GREATER -1) | |
451 | ENDIF (is_arg_name GREATER -1) | |
452 | ENDFOREACH(arg) | |
453 | SET(${prefix}_${current_arg_name} ${current_arg_list}) | |
454 | ENDMACRO(PARSE_ARGUMENTS) | |
455 | ||
456 | # Internal macros above | |
457 | ############################################################################### | |
458 |
0 | # Set a flag to indicate that rosbuild_init() has not been called, so that | |
1 | # we can later catch out-of-order calls to macros that must be called prior | |
2 | # to rosbuild_init(), related to #1487. | |
3 | set(ROSBUILD_init_called 0) | |
4 | ||
5 | # Use this package to get add_file_dependencies() | |
6 | include(AddFileDependencies) | |
7 | # Used to check if a function exists | |
8 | include(CheckFunctionExists) | |
9 | ||
10 | # look up python interpreter, store in ${PYTHON_EXECUTABLE}, #3763 BSD supprot | |
11 | find_package(PythonInterp) | |
12 | ||
13 | # Find a ros package. | |
14 | macro(rosbuild_find_ros_package pkgname) | |
15 | # catch the error output to suppress it | |
16 | execute_process( | |
17 | COMMAND rospack find ${pkgname} | |
18 | ERROR_VARIABLE __rospack_err_ignore | |
19 | OUTPUT_VARIABLE __pkg_dir | |
20 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
21 | # todo: catch return code and be smart about it | |
22 | set(${pkgname}_PACKAGE_PATH ${__pkg_dir}) | |
23 | endmacro(rosbuild_find_ros_package) | |
24 | ||
25 | # Find a ros stack. | |
26 | macro(rosbuild_find_ros_stack stackname) | |
27 | # catch the error output to suppress it | |
28 | execute_process( | |
29 | COMMAND rosstack find ${stackname} | |
30 | ERROR_VARIABLE __rospack_err_ignore | |
31 | OUTPUT_VARIABLE __stack_dir | |
32 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
33 | # todo: catch return code and be smart about it | |
34 | set(${stackname}_STACK_PATH ${__stack_dir}) | |
35 | endmacro(rosbuild_find_ros_stack) | |
36 | ||
37 | # Retrieve the current COMPILE_FLAGS for the given target, append the new | |
38 | # ones, and set the result. | |
39 | macro(rosbuild_add_compile_flags target) | |
40 | set(args ${ARGN}) | |
41 | separate_arguments(args) | |
42 | get_target_property(_flags ${target} COMPILE_FLAGS) | |
43 | if(NOT _flags) | |
44 | set(_flags ${ARGN}) | |
45 | else(NOT _flags) | |
46 | separate_arguments(_flags) | |
47 | list(APPEND _flags "${args}") | |
48 | endif(NOT _flags) | |
49 | ||
50 | _rosbuild_list_to_string(_flags_str "${_flags}") | |
51 | set_target_properties(${target} PROPERTIES | |
52 | COMPILE_FLAGS "${_flags_str}") | |
53 | endmacro(rosbuild_add_compile_flags) | |
54 | ||
55 | # Retrieve the current COMPILE_FLAGS for the given target, remove the given | |
56 | # ones, and set the result. | |
57 | macro(rosbuild_remove_compile_flags target) | |
58 | set(args ${ARGN}) | |
59 | separate_arguments(args) | |
60 | get_target_property(_flags ${target} COMPILE_FLAGS) | |
61 | separate_arguments(_flags) | |
62 | list(REMOVE_ITEM _flags ${args}) | |
63 | ||
64 | _rosbuild_list_to_string(_flags_str "${_flags}") | |
65 | set_target_properties(${target} PROPERTIES | |
66 | COMPILE_FLAGS "${_flags_str}") | |
67 | endmacro(rosbuild_remove_compile_flags) | |
68 | ||
69 | # Retrieve the current LINK_FLAGS for the given target, append the new | |
70 | # ones, and set the result. | |
71 | macro(rosbuild_add_link_flags target) | |
72 | set(args ${ARGN}) | |
73 | separate_arguments(args) | |
74 | get_target_property(_flags ${target} LINK_FLAGS) | |
75 | if(NOT _flags) | |
76 | set(_flags ${ARGN}) | |
77 | else(NOT _flags) | |
78 | separate_arguments(_flags) | |
79 | list(APPEND _flags "${args}") | |
80 | endif(NOT _flags) | |
81 | ||
82 | _rosbuild_list_to_string(_flags_str "${_flags}") | |
83 | set_target_properties(${target} PROPERTIES | |
84 | LINK_FLAGS "${_flags_str}") | |
85 | endmacro(rosbuild_add_link_flags) | |
86 | ||
87 | # Retrieve the current LINK_FLAGS for the given target, remove the given | |
88 | # ones, and set the result. | |
89 | macro(rosbuild_remove_link_flags target) | |
90 | set(args ${ARGN}) | |
91 | separate_arguments(args) | |
92 | get_target_property(_flags ${target} LINK_FLAGS) | |
93 | separate_arguments(_flags) | |
94 | list(REMOVE_ITEM _flags ${args}) | |
95 | ||
96 | _rosbuild_list_to_string(_flags_str "${_flags}") | |
97 | set_target_properties(${target} PROPERTIES | |
98 | LINK_FLAGS "${_flags_str}") | |
99 | endmacro(rosbuild_remove_link_flags) | |
100 | ||
101 | macro(rosbuild_invoke_rospack pkgname _prefix _varname) | |
102 | # Check that our cached location of rospack is valid. It can be invalid | |
103 | # if rospack has moved since last time we ran, #1154. If it's invalid, | |
104 | # search again. | |
105 | if(NOT EXISTS ${ROSPACK_EXE}) | |
106 | message("Cached location of rospack is invalid; searching for rospack...") | |
107 | set(ROSPACK_EXE ROSPACK_EXE-NOTFOUND) | |
108 | # Only look in PATH for rospack, #3831 | |
109 | find_program(ROSPACK_EXE NAMES rospack DOC "rospack executable" NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) | |
110 | if (NOT ROSPACK_EXE) | |
111 | message(FATAL_ERROR "Couldn't find rospack. Please run 'make' in $ROS_ROOT") | |
112 | endif(NOT ROSPACK_EXE) | |
113 | endif(NOT EXISTS ${ROSPACK_EXE}) | |
114 | set(_rospack_invoke_result) | |
115 | execute_process( | |
116 | COMMAND ${ROSPACK_EXE} ${ARGN} ${pkgname} | |
117 | OUTPUT_VARIABLE _rospack_invoke_result | |
118 | ERROR_VARIABLE _rospack_err_ignore | |
119 | RESULT_VARIABLE _rospack_failed | |
120 | OUTPUT_STRIP_TRAILING_WHITESPACE | |
121 | ) | |
122 | if (_rospack_failed) | |
123 | #set(_rospack_${_varname} "") | |
124 | #set(${_prefix}_${_varname} "" CACHE INTERNAL "") | |
125 | message("Failed to invoke ${ROSPACK_EXE} ${ARGN} ${pkgname}") | |
126 | message("${_rospack_err_ignore}") | |
127 | message("${_rospack_invoke_result}") | |
128 | message(FATAL_ERROR "\nFailed to invoke rospack to get compile flags for package '${pkgname}'. Look above for errors from rospack itself. Aborting. Please fix the broken dependency!\n") | |
129 | else(_rospack_failed) | |
130 | separate_arguments(_rospack_invoke_result) | |
131 | set(_rospack_${_varname} ${_rospack_invoke_result}) | |
132 | # We don't cache results that contain newlines, because | |
133 | # they make CMake's cache unhappy. This check should only affect calls | |
134 | # to `rospack plugins`, which we don't need to cache. | |
135 | if(_rospack_invoke_result MATCHES ".*\n.*") | |
136 | set(${_prefix}_${_varname} "${_rospack_invoke_result}") | |
137 | else(_rospack_invoke_result MATCHES ".*\n.*") | |
138 | set(${_prefix}_${_varname} "${_rospack_invoke_result}" CACHE INTERNAL "") | |
139 | endif(_rospack_invoke_result MATCHES ".*\n.*") | |
140 | endif(_rospack_failed) | |
141 | endmacro(rosbuild_invoke_rospack) | |
142 | ||
143 | ############################################################################### | |
144 | # This is the user's main entry point. A *lot* of work gets done here. It | |
145 | # should probably be split up into multiple macros. | |
146 | macro(rosbuild_init) | |
147 | # Record that we've been called | |
148 | set(ROSBUILD_init_called 1) | |
149 | ||
150 | # Don't override the project name if the user said not to, #3119 | |
151 | if(NOT DEFINED ROSBUILD_DONT_REDEFINE_PROJECT) | |
152 | # Infer package name from directory name. | |
153 | get_filename_component(_project ${CMAKE_SOURCE_DIR} NAME) | |
154 | project(${_project}) | |
155 | else(NOT DEFINED ROSBUILD_DONT_REDEFINE_PROJECT) | |
156 | set(_project ${PROJECT_NAME}) | |
157 | endif(NOT DEFINED ROSBUILD_DONT_REDEFINE_PROJECT) | |
158 | ||
159 | # Declare that the minimum version of OSX we support is 10.6, #3491. | |
160 | # For background on how CMAKE_OSX_DEPLOYMENT_TARGET works, see: | |
161 | # http://public.kitware.com/Bug/view.php?id=6195 | |
162 | if(APPLE) | |
163 | SET(CMAKE_OSX_DEPLOYMENT_TARGET "10.6" CACHE STRING "Deployment target for OSX" FORCE) | |
164 | endif(APPLE) | |
165 | ||
166 | message("[rosbuild] Building package ${_project}") | |
167 | ||
168 | # Must call include(rosconfig) after project, because rosconfig uses | |
169 | # PROJECT_SOURCE_DIR | |
170 | include($ENV{ROS_ROOT}/core/rosbuild/rosconfig.cmake) | |
171 | ||
172 | # Check that manifest.xml is valid | |
173 | _rosbuild_check_manifest() | |
174 | ||
175 | # Check that the package directory is correct | |
176 | _rosbuild_check_package_location() | |
177 | ||
178 | # Add ROS_PACKAGE_NAME define | |
179 | add_definitions(-DROS_PACKAGE_NAME='\"${PROJECT_NAME}\"') | |
180 | ||
181 | # ROS_BUILD_TYPE is set by rosconfig | |
182 | # RelWithAsserts is our own type, not supported by CMake | |
183 | if("${ROS_BUILD_TYPE}" STREQUAL "RelWithAsserts") | |
184 | set(CMAKE_BUILD_TYPE "") | |
185 | set(ROS_COMPILE_FLAGS "-O3 ${ROS_COMPILE_FLAGS}") | |
186 | else("${ROS_BUILD_TYPE}" STREQUAL "RelWithAsserts") | |
187 | set(CMAKE_BUILD_TYPE ${ROS_BUILD_TYPE}) | |
188 | endif("${ROS_BUILD_TYPE}" STREQUAL "RelWithAsserts") | |
189 | ||
190 | # Set default output directories | |
191 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}) | |
192 | set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) | |
193 | ||
194 | # By default, look in the local include dir | |
195 | include_directories(${PROJECT_SOURCE_DIR}/include) | |
196 | ||
197 | set(_prefix ${PROJECT_NAME}) | |
198 | set(${_prefix}_INCLUDEDIR "" CACHE INTERNAL "") | |
199 | ||
200 | # Get the full paths to the manifests for all packages on which | |
201 | # we depend | |
202 | rosbuild_invoke_rospack(${PROJECT_NAME} _rospack deps_manifests_invoke_result deps-manifests) | |
203 | rosbuild_invoke_rospack(${PROJECT_NAME} _rospack msgsrv_gen_invoke_result deps-msgsrv) | |
204 | set(ROS_MANIFEST_LIST "${PROJECT_SOURCE_DIR}/manifest.xml ${_rospack_deps_manifests_invoke_result} ${_rospack_msgsrv_gen_invoke_result}") | |
205 | # convert whitespace-separated string to ;-separated list | |
206 | separate_arguments(ROS_MANIFEST_LIST) | |
207 | ||
208 | # Check the time at which we last cached flags against the latest | |
209 | # modification time for all manifests that we depend on. If our cache | |
210 | # time is smaller, then we need to rebuild our cached values by calling | |
211 | # out to rospack to get flags. This is an optimization in the service of | |
212 | # speeding up the build, #2109. | |
213 | _rosbuild_compare_manifests(_rebuild_cache "${_rosbuild_cached_flag_time}" "${${_prefix}_cached_manifest_list}" "${ROS_MANIFEST_LIST}") | |
214 | if(_rebuild_cache) | |
215 | # Explicitly unset all cached variables, to avoid possible accumulation | |
216 | # across builds, #2389. | |
217 | set(${_prefix}_INCLUDE_DIRS "" CACHE INTERNAL "") | |
218 | set(${_prefix}_CFLAGS_OTHER "" CACHE INTERNAL "") | |
219 | set(${_prefix}_LIBRARY_DIRS "" CACHE INTERNAL "") | |
220 | set(${_prefix}_LIBRARIES "" CACHE INTERNAL "") | |
221 | set(${_prefix}_LDFLAGS_OTHER "" CACHE INTERNAL "") | |
222 | set(${_prefix}_cached_manifest_list "" CACHE INTERNAL "") | |
223 | ||
224 | message("[rosbuild] Cached build flags older than manifests; calling rospack to get flags") | |
225 | # Get the include dirs | |
226 | rosbuild_invoke_rospack(${PROJECT_NAME} ${_prefix} INCLUDE_DIRS cflags-only-I --deps-only) | |
227 | #message("${pkgname} include dirs: ${${_prefix}_INCLUDE_DIRS}") | |
228 | set(${_prefix}_INCLUDE_DIRS ${${_prefix}_INCLUDE_DIRS} CACHE INTERNAL "") | |
229 | ||
230 | # Get the other cflags | |
231 | rosbuild_invoke_rospack(${PROJECT_NAME} ${_prefix} temp cflags-only-other --deps-only) | |
232 | _rosbuild_list_to_string(${_prefix}_CFLAGS_OTHER "${${_prefix}_temp}") | |
233 | #message("${pkgname} other cflags: ${${_prefix}_CFLAGS_OTHER}") | |
234 | set(${_prefix}_CFLAGS_OTHER ${${_prefix}_CFLAGS_OTHER} CACHE INTERNAL "") | |
235 | ||
236 | # Get the lib dirs | |
237 | rosbuild_invoke_rospack(${PROJECT_NAME} ${_prefix} LIBRARY_DIRS libs-only-L --deps-only) | |
238 | #message("${pkgname} library dirs: ${${_prefix}_LIBRARY_DIRS}") | |
239 | set(${_prefix}_LIBRARY_DIRS ${${_prefix}_LIBRARY_DIRS} CACHE INTERNAL "") | |
240 | ||
241 | # Get the libs | |
242 | rosbuild_invoke_rospack(${PROJECT_NAME} ${_prefix} LIBRARIES libs-only-l --deps-only) | |
243 | # | |
244 | # The following code removes duplicate libraries from the link line, | |
245 | # saving only the last one. | |
246 | # | |
247 | list(REVERSE ${_prefix}_LIBRARIES) | |
248 | list(REMOVE_DUPLICATES ${_prefix}_LIBRARIES) | |
249 | list(REVERSE ${_prefix}_LIBRARIES) | |
250 | ||
251 | # Also throw in the libs that we want to link everything against (only | |
252 | # use case for this so far is -lgcov when building with code coverage | |
253 | # support). | |
254 | list(APPEND ${_prefix}_LIBRARIES "${ROS_LINK_LIBS}") | |
255 | set(${_prefix}_LIBRARIES ${${_prefix}_LIBRARIES} CACHE INTERNAL "") | |
256 | ||
257 | # Get the other lflags | |
258 | rosbuild_invoke_rospack(${PROJECT_NAME} ${_prefix} temp libs-only-other --deps-only) | |
259 | _rosbuild_list_to_string(${_prefix}_LDFLAGS_OTHER "${${_prefix}_temp}") | |
260 | #message("${pkgname} other ldflags: ${${_prefix}_LDFLAGS_OTHER}") | |
261 | set(${_prefix}_LDFLAGS_OTHER ${${_prefix}_LDFLAGS_OTHER} CACHE INTERNAL "") | |
262 | ||
263 | # Record the time at which we cached those values | |
264 | _rosbuild_get_clock(_time) | |
265 | set(_rosbuild_cached_flag_time ${_time} CACHE INTERNAL "") | |
266 | set(${_prefix}_cached_manifest_list ${ROS_MANIFEST_LIST} CACHE INTERNAL "") | |
267 | endif(_rebuild_cache) | |
268 | ||
269 | # Use the (possibly cached) values returned by rospack. | |
270 | include_directories(${${_prefix}_INCLUDE_DIRS}) | |
271 | link_directories(${${_prefix}_LIBRARY_DIRS}) | |
272 | ||
273 | # | |
274 | # Catch absolute pathnames to archive libraries and bracket them with | |
275 | # linker args necessary to force extraction of the entire archive. | |
276 | # | |
277 | # The OS X linker doesn't accept the -whole-archive and -no-whole-archive | |
278 | # arguments. | |
279 | # | |
280 | if(NOT APPLE) | |
281 | foreach(_lib ${${_prefix}_LIBRARIES}) | |
282 | if(_lib MATCHES "/[^ ]*\\.a") | |
283 | set(_bracket_str "-Wl,-whole-archive ${_lib} -Wl,-no-whole-archive") | |
284 | list(APPEND ${_prefix}_LDFLAGS_OTHER "${_bracket_str}") | |
285 | endif(_lib MATCHES "/[^ ]*\\.a") | |
286 | endforeach(_lib) | |
287 | endif(NOT APPLE) | |
288 | ||
289 | # Set up the test targets. Subsequent calls to rosbuild_add_gtest and | |
290 | # friends add targets and dependencies from these targets. | |
291 | # | |
292 | ||
293 | # Find rosunit; rosunit_path will be used later | |
294 | rosbuild_invoke_rospack("" rosunit path find rosunit) | |
295 | ||
296 | # Record where we're going to put test results (#2003) | |
297 | execute_process(COMMAND ${rosunit_path}/scripts/test_results_dir.py | |
298 | OUTPUT_VARIABLE rosbuild_test_results_dir | |
299 | RESULT_VARIABLE _test_results_dir_failed | |
300 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
301 | if(_test_results_dir_failed) | |
302 | message(FATAL_ERROR "Failed to invoke rosunit/scripts/test_results_dir.py") | |
303 | endif(_test_results_dir_failed) | |
304 | ||
305 | # The 'tests' target builds the test program | |
306 | add_custom_target(tests) | |
307 | # The 'test' target runs all but the future tests | |
308 | add_custom_target(test) | |
309 | # We need to build tests before running them. Addition of this | |
310 | # dependency also ensures that old test results get cleaned prior to a | |
311 | # new test run. | |
312 | # but not if rosbuild_test_nobuild is set, #3008 | |
313 | if(NOT rosbuild_test_nobuild) | |
314 | add_dependencies(test tests) | |
315 | endif(NOT rosbuild_test_nobuild) | |
316 | ||
317 | # Clean out previous test results before running tests. Use bash | |
318 | # conditional to ignore failures (most often happens when a stale NFS | |
319 | # handle lingers in the test results directory), because CMake doesn't | |
320 | # seem to be able to do it. | |
321 | add_custom_target(rosbuild_clean-test-results | |
322 | if ! rm -rf ${rosbuild_test_results_dir}/${PROJECT_NAME}\; then echo "WARNING: failed to remove test-results directory"\; fi) | |
323 | # Make the tests target depend on rosbuild_clean-test-results, which will ensure | |
324 | # that test results are deleted before we try to build tests, and thus | |
325 | # before we try to run tests. | |
326 | add_dependencies(tests rosbuild_clean-test-results) | |
327 | # The 'test-future' target runs the future tests | |
328 | add_custom_target(test-future) | |
329 | ||
330 | ||
331 | add_custom_target(test-results-run) | |
332 | add_custom_target(test-results | |
333 | COMMAND ${rosunit_path}/bin/summarize_results.py --nodeps ${_project}) | |
334 | add_dependencies(test-results test-results-run) | |
335 | # Do we want coverage reporting (only matters for Python, because | |
336 | # Bullseye already collects everything into a single file). | |
337 | if("$ENV{ROS_TEST_COVERAGE}" STREQUAL "1") | |
338 | add_custom_target(test-results-coverage | |
339 | COMMAND ${rosunit_path}/bin/pycoverage_to_html.py | |
340 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) | |
341 | # Make tests run before collecting coverage results | |
342 | add_dependencies(test-results-coverage test-results-run) | |
343 | # Make coverage collection happen | |
344 | add_dependencies(test-results test-results-coverage) | |
345 | endif("$ENV{ROS_TEST_COVERAGE}" STREQUAL "1") | |
346 | ||
347 | # Initialize a counter to be used to uniquify target names for | |
348 | # calls to rosbuild_add_roslaunch_check(), #3674. | |
349 | set(ROSBUILD_roslaunch_check_count 1) | |
350 | ||
351 | # Find roslib; roslib_path will be used later | |
352 | rosbuild_invoke_rospack("" roslib path find roslib) | |
353 | ||
354 | # Create targets for client libs attach their message-generation output to | |
355 | add_custom_target(rospack_genmsg) | |
356 | add_custom_target(rospack_gensrv) | |
357 | ||
358 | # Add a target that will fire before doing message or service generation. | |
359 | # This is used by packages that do automatic generation of message and | |
360 | # service files. | |
361 | add_custom_target(rosbuild_premsgsrvgen) | |
362 | ||
363 | # Add a target that will fire before compiling anything. This is used by | |
364 | # message and service generation, as well as things outside of ros, like | |
365 | # dynamic_reconfigure. | |
366 | add_custom_target(rosbuild_precompile) | |
367 | ||
368 | # The rospack_genmsg_libexe target is defined for backward compatibility, | |
369 | # and will eventually be removed. | |
370 | add_custom_target(rospack_genmsg_libexe) | |
371 | add_dependencies(rosbuild_precompile rospack_genmsg_libexe) | |
372 | ||
373 | # ${gendeps_exe} is a convenience variable that roslang cmake rules | |
374 | # must reference as a dependency of msg/srv generation | |
375 | # ${gendeps_exe} is set by roslib-extras.cmake | |
376 | find_package(catkin REQUIRED COMPONENTS roslib) | |
377 | ||
378 | # If the roslang package is available, pull in cmake/roslang.cmake from | |
379 | # there; it will in turn include message-generation logic from client | |
380 | # libs. This is to allow roslang to live outside the ros stack, #3108. | |
381 | rosbuild_find_ros_package("roslang") | |
382 | if(roslang_PACKAGE_PATH) | |
383 | # Can't use rosbuild_include() here, because there's no guarantee that | |
384 | # the package we're building depends on roslang (in fact, it probably | |
385 | # doesn't. | |
386 | include("${roslang_PACKAGE_PATH}/cmake/roslang.cmake") | |
387 | endif(roslang_PACKAGE_PATH) | |
388 | ||
389 | # Collect cmake fragments exported by packages that depend on | |
390 | # rosbuild. This behavior is deprecated, in favor of using | |
391 | # rosbuild_include() to explicitly include cmake code from other packages. | |
392 | rosbuild_invoke_rospack(rosbuild _rosbuild EXPORTS plugins --attrib=cmake --top=${_project}) | |
393 | list(LENGTH _rosbuild_EXPORTS _rosbuild_EXPORTS_length) | |
394 | ||
395 | # rospack plugins outputs the list as: | |
396 | # <package name> <attribute value> | |
397 | # Here we remove <package name> in all cases by: | |
398 | # 1) Remove the first element of the returned list | |
399 | # 2) Search for all instances of <newline><string><semicolon>, replacing them with just a semicolon | |
400 | ||
401 | # 1) Remove the first package name if the list has at least one element | |
402 | if (${_rosbuild_EXPORTS_length} GREATER 0) | |
403 | list(REMOVE_AT _rosbuild_EXPORTS 0) | |
404 | endif(${_rosbuild_EXPORTS_length} GREATER 0) | |
405 | ||
406 | # 2) Remove the rest of the package names | |
407 | string(REGEX REPLACE "\n[^;]*;" ";" _rosbuild_EXPORTS_stripped "${_rosbuild_EXPORTS}") | |
408 | ||
409 | set(_rosbuild_EXPORTS "" CACHE INTERNAL "") | |
410 | ||
411 | set(_cmake_fragments) | |
412 | foreach(_f ${_rosbuild_EXPORTS_stripped}) | |
413 | list(APPEND _cmake_fragments ${_f}) | |
414 | message("\n[rosbuild] WARNING: the file ${_f} is being included automatically. This behavior is deprecated. The package containing that file should instead export the directory containing the file, and you should use rosbuild_include() to include the file explicitly.\n") | |
415 | endforeach(_f) | |
416 | ||
417 | # Now include them all | |
418 | foreach(_f ${_cmake_fragments}) | |
419 | if(NOT EXISTS ${_f}) | |
420 | message(FATAL_ERROR "Cannot include non-existent exported cmake file ${_f}") | |
421 | endif(NOT EXISTS ${_f}) | |
422 | # Include this cmake fragment; presumably it will do / | |
423 | # provide something useful. Only include each file once (a file | |
424 | # might be multiply referenced because of package dependencies | |
425 | # dependencies). | |
426 | if(NOT ${_f}_INCLUDED) | |
427 | message("[rosbuild] Including ${_f}") | |
428 | include(${_f}) | |
429 | set(${_f}_INCLUDED Y) | |
430 | endif(NOT ${_f}_INCLUDED) | |
431 | endforeach(_f) | |
432 | ||
433 | ||
434 | # | |
435 | # Gather the gtest build flags, for use when building unit tests. We | |
436 | # don't require the user to declare a dependency on gtest. | |
437 | # | |
438 | find_program(GTEST_EXE NAMES gtest-config DOC "gtest-config executable" ONLY_CMAKE_FIND_ROOT_PATH) | |
439 | if (NOT GTEST_EXE) | |
440 | if (GTEST_LIBRARIES) | |
441 | set(_gtest_LIBRARIES -l${GTEST_LIBRARIES}) | |
442 | else(GTEST_LIBRARIES) | |
443 | set(_gtest_LIBRARIES -lgtest) | |
444 | endif(GTEST_LIBRARIES) | |
445 | # Couldn't find gtest-config. Hoping that gtest is in our path either in the system install or where ROS_BINDEPS points to | |
446 | else (NOT GTEST_EXE) | |
447 | ||
448 | execute_process(COMMAND ${GTEST_EXE} --includedir | |
449 | OUTPUT_VARIABLE gtest_include_dir | |
450 | RESULT_VARIABLE _gtest_include_dir | |
451 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
452 | if(_gtest_include_dir) | |
453 | message(FATAL_ERROR "Failed to invoke gtest-config") | |
454 | endif(_gtest_include_dir) | |
455 | ||
456 | include_directories(${gtest_include_dir}) | |
457 | ||
458 | execute_process(COMMAND ${GTEST_EXE} --libdir | |
459 | OUTPUT_VARIABLE gtest_lib_dir | |
460 | RESULT_VARIABLE _gtest_lib_dir | |
461 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
462 | if(_gtest_lib_dir) | |
463 | message(FATAL_ERROR "Failed to invoke gtest-config") | |
464 | endif(_gtest_lib_dir) | |
465 | link_directories(${gtest_lib_dir}) | |
466 | ||
467 | ||
468 | execute_process(COMMAND ${GTEST_EXE} --libs | |
469 | OUTPUT_VARIABLE gtest_libs | |
470 | RESULT_VARIABLE _gtest_libs | |
471 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
472 | if(_gtest_libs) | |
473 | message(FATAL_ERROR "Failed to invoke gtest-config") | |
474 | endif(_gtest_libs) | |
475 | ||
476 | ||
477 | set(_gtest_LIBRARIES ${gtest_libs}) | |
478 | set(_gtest_CFLAGS_OTHER "") | |
479 | set(_gtest_LDFLAGS_OTHER "-Wl,-rpath,${gtest_lib_dir}") | |
480 | endif (NOT GTEST_EXE) | |
481 | ||
482 | # | |
483 | # The following code removes duplicate libraries from the link line, | |
484 | # saving only the last one. | |
485 | # | |
486 | list(REVERSE _gtest_LIBRARIES) | |
487 | list(REMOVE_DUPLICATES _gtest_LIBRARIES) | |
488 | list(REVERSE _gtest_LIBRARIES) | |
489 | ||
490 | # Delete the files that let rospack know messages/services have been generated | |
491 | file(REMOVE ${PROJECT_SOURCE_DIR}/msg_gen/generated) | |
492 | file(REMOVE ${PROJECT_SOURCE_DIR}/srv_gen/generated) | |
493 | endmacro(rosbuild_init) | |
494 | ############################################################################### | |
495 | ||
496 | # A wrapper around add_executable(), using info from the rospack | |
497 | # invocation to set up compiling and linking. | |
498 | macro(rosbuild_add_executable exe) | |
499 | add_executable(${ARGV}) | |
500 | ||
501 | # Add explicit dependency of each file on our manifest.xml and those of | |
502 | # our dependencies. | |
503 | get_target_property(_srclist ${exe} SOURCES) | |
504 | foreach(_src ${_srclist}) | |
505 | set(_file_name _file_name-NOTFOUND) | |
506 | find_file(_file_name ${_src} ${CMAKE_CURRENT_SOURCE_DIR} /) | |
507 | if(NOT _file_name) | |
508 | message("[rosbuild] Couldn't find source file ${_src}; assuming that it is in ${CMAKE_CURRENT_SOURCE_DIR} and will be generated later") | |
509 | set(_file_name ${CMAKE_CURRENT_SOURCE_DIR}/${_src}) | |
510 | endif(NOT _file_name) | |
511 | add_file_dependencies(${_file_name} ${ROS_MANIFEST_LIST}) | |
512 | endforeach(_src) | |
513 | ||
514 | rosbuild_add_compile_flags(${exe} ${${PROJECT_NAME}_CFLAGS_OTHER}) | |
515 | rosbuild_add_link_flags(${exe} ${${PROJECT_NAME}_LDFLAGS_OTHER}) | |
516 | ||
517 | if(ROS_BUILD_STATIC_EXES AND ${CMAKE_SYSTEM_NAME} STREQUAL "Linux") | |
518 | # This will probably only work on Linux. The LINK_SEARCH_END_STATIC | |
519 | # property should be sufficient, but it doesn't appear to work | |
520 | # properly. | |
521 | rosbuild_add_link_flags(${exe} -static-libgcc -Wl,-Bstatic) | |
522 | endif(ROS_BUILD_STATIC_EXES AND ${CMAKE_SYSTEM_NAME} STREQUAL "Linux") | |
523 | ||
524 | target_link_libraries(${exe} ${${PROJECT_NAME}_LIBRARIES}) | |
525 | ||
526 | # Add ROS-wide compile and link flags (usually things like -Wall). These | |
527 | # are set in rosconfig.cmake. | |
528 | rosbuild_add_compile_flags(${exe} ${ROS_COMPILE_FLAGS}) | |
529 | rosbuild_add_link_flags(${exe} ${ROS_LINK_FLAGS}) | |
530 | ||
531 | # Make sure to do any prebuild work (e.g., msg/srv generation) before | |
532 | # building this target. | |
533 | add_dependencies(${exe} rosbuild_precompile) | |
534 | ||
535 | # If we're linking boost statically, we have to force allow multiple definitions because | |
536 | # rospack does not remove duplicates | |
537 | if ("$ENV{ROS_BOOST_LINK}" STREQUAL "static") | |
538 | rosbuild_add_link_flags(${exe} "-Wl,--allow-multiple-definition") | |
539 | endif("$ENV{ROS_BOOST_LINK}" STREQUAL "static") | |
540 | ||
541 | endmacro(rosbuild_add_executable) | |
542 | ||
543 | # Wrapper around add_library. We can build shared static and shared libs, and | |
544 | # set up compile and link flags for both. | |
545 | macro(rosbuild_add_library lib) | |
546 | ||
547 | # Sanity check; must build at least one kind of library. | |
548 | if(NOT ROS_BUILD_STATIC_LIBS AND NOT ROS_BUILD_SHARED_LIBS) | |
549 | message(FATAL_ERROR "Neither shared nor static libraries are enabled. Please set either ROS_BUILD_STATIC_LIBS or ROS_BUILD_SHARED_LIBS to true in your $ROS_ROOT/rosconfig.cmake") | |
550 | endif(NOT ROS_BUILD_STATIC_LIBS AND NOT ROS_BUILD_SHARED_LIBS) | |
551 | # Sanity check; it's too hard to support building shared libs and static | |
552 | # executables. | |
553 | if(ROS_BUILD_STATIC_EXES AND ROS_BUILD_SHARED_LIBS) | |
554 | message(FATAL_ERROR "Static executables are requested, but so are shared libs. This configuration is unsupported. Please either set ROS_BUILD_SHARED_LIBS to false or set ROS_BUILD_STATIC_EXES to false.") | |
555 | endif(ROS_BUILD_STATIC_EXES AND ROS_BUILD_SHARED_LIBS) | |
556 | ||
557 | # What are we building? | |
558 | if(ROS_BUILD_SHARED_LIBS) | |
559 | # If shared libs are being built, they get the default CMake target name | |
560 | # No matter what, the libraries get the same name in the end. | |
561 | _rosbuild_add_library(${lib} ${lib} SHARED ${ARGN}) | |
562 | endif(ROS_BUILD_SHARED_LIBS) | |
563 | ||
564 | if(ROS_BUILD_STATIC_LIBS) | |
565 | # If we're only building static libs, then they get the default CMake | |
566 | # target name. | |
567 | if(NOT ROS_BUILD_SHARED_LIBS) | |
568 | set(static_lib_name "${lib}") | |
569 | else(NOT ROS_BUILD_SHARED_LIBS) | |
570 | set(static_lib_name "${lib}-static") | |
571 | endif(NOT ROS_BUILD_SHARED_LIBS) | |
572 | ||
573 | _rosbuild_add_library(${static_lib_name} ${lib} STATIC ${ARGN}) | |
574 | endif(ROS_BUILD_STATIC_LIBS) | |
575 | ||
576 | endmacro(rosbuild_add_library) | |
577 | ||
578 | # Wrapper around add_library for the specific case of building a MODULE, | |
579 | # which works a little differently on dyld systems (e.g., OS X) | |
580 | macro(rosbuild_add_library_module lib) | |
581 | _rosbuild_add_library(${lib} ${lib} MODULE ${ARGN}) | |
582 | endmacro(rosbuild_add_library_module) | |
583 | ||
584 | # Explicitly add flags for gtest. We do this here, instead of using | |
585 | # manifest dependencies, because there are situations in which it is | |
586 | # undesirable to link in gtest where's it's not being used. gtest is | |
587 | # part of the "core" build that happens during a 'make' in ros, so we can | |
588 | # assume that's already built. | |
589 | macro(rosbuild_add_gtest_build_flags exe) | |
590 | if (NOT GTEST_FOUND) | |
591 | # gtest is installed but there might not be any library (e.g. Ubuntu Precise), so build it | |
592 | if (EXISTS "/usr/src/gtest") | |
593 | if (NOT EXISTS "${CMAKE_BINARY_DIR}/_gtest_from_src") | |
594 | # for now, this would only work on Ubuntu | |
595 | add_subdirectory("/usr/src/gtest/" ${CMAKE_BINARY_DIR}/_gtest_from_src) | |
596 | endif() | |
597 | else() | |
598 | message(WARNING "GTest not found; C++ tests will fail to build.") | |
599 | endif() | |
600 | endif() | |
601 | rosbuild_add_compile_flags(${exe} ${_gtest_CFLAGS_OTHER}) | |
602 | if (LIBRARY_OUTPUT_PATH) | |
603 | # add this as GTest builds there | |
604 | set_target_properties(${exe} PROPERTIES LINK_FLAGS "-L${LIBRARY_OUTPUT_PATH}") | |
605 | endif() | |
606 | target_link_libraries(${exe} ${_gtest_LIBRARIES}) | |
607 | rosbuild_add_link_flags(${exe} ${_gtest_LDFLAGS_OTHER}) | |
608 | rosbuild_declare_test(${exe}) | |
609 | add_dependencies(${exe} gtest gtest_main) | |
610 | endmacro(rosbuild_add_gtest_build_flags) | |
611 | ||
612 | # Declare an executable to be a test harness, which excludes it from the | |
613 | # all target, and adds a dependency to the tests target. | |
614 | macro(rosbuild_declare_test exe) | |
615 | # We provide a 'tests' target that just builds the tests. | |
616 | # Redeclaration of target is to workaround bug in 2.4.6 | |
617 | if(CMAKE_MINOR_VERSION LESS 6) | |
618 | add_custom_target(tests) | |
619 | endif(CMAKE_MINOR_VERSION LESS 6) | |
620 | add_dependencies(tests ${exe}) | |
621 | # Don't build this target with the all target, #3110 | |
622 | set_target_properties(${exe} PROPERTIES EXCLUDE_FROM_ALL TRUE) | |
623 | endmacro(rosbuild_declare_test) | |
624 | ||
625 | # A helper to create test programs. It calls rosbuild_add_executable() to | |
626 | # create the program, and augments a test target that was created in the | |
627 | # call rospack() | |
628 | macro(rosbuild_add_gtest exe) | |
629 | _rosbuild_add_gtest(${ARGV}) | |
630 | # Create a legal target name, in case the target name has slashes in it | |
631 | string(REPLACE "/" "_" _testname ${exe}) | |
632 | # Redeclaration of test target is to workaround bug in 2.4.6 | |
633 | if(CMAKE_MINOR_VERSION LESS 6) | |
634 | add_custom_target(test) | |
635 | endif(CMAKE_MINOR_VERSION LESS 6) | |
636 | add_dependencies(test test_${_testname}) | |
637 | # Register check for test output | |
638 | _rosbuild_check_rostest_xml_result(${_testname} ${rosbuild_test_results_dir}/${PROJECT_NAME}/TEST-${_testname}.xml) | |
639 | endmacro(rosbuild_add_gtest) | |
640 | ||
641 | # A version of add_gtest that checks a label against ROS_BUILD_TEST_LABEL | |
642 | macro(rosbuild_add_gtest_labeled label) | |
643 | if("$ENV{ROS_BUILD_TEST_LABEL}" STREQUAL "" OR "${label}" STREQUAL "$ENV{ROS_BUILD_TEST_LABEL}") | |
644 | rosbuild_add_gtest(${ARGN}) | |
645 | endif("$ENV{ROS_BUILD_TEST_LABEL}" STREQUAL "" OR "${label}" STREQUAL "$ENV{ROS_BUILD_TEST_LABEL}") | |
646 | endmacro(rosbuild_add_gtest_labeled) | |
647 | ||
648 | # A helper to run rostests. It generates a command to run rostest on | |
649 | # the specified file and makes this target a dependency of test. | |
650 | macro(rosbuild_add_rostest file) | |
651 | string(REPLACE "/" "_" _testname ${file}) | |
652 | _rosbuild_add_rostest(${file}) | |
653 | # Redeclaration of target is to workaround bug in 2.4.6 | |
654 | if(CMAKE_MINOR_VERSION LESS 6) | |
655 | add_custom_target(test) | |
656 | endif(CMAKE_MINOR_VERSION LESS 6) | |
657 | add_dependencies(test rostest_${_testname}) | |
658 | _rosbuild_check_rostest_result(rostest_${_testname} ${PROJECT_NAME} ${file}) | |
659 | endmacro(rosbuild_add_rostest) | |
660 | ||
661 | # A version of add_rostest that checks a label against ROS_BUILD_TEST_LABEL | |
662 | macro(rosbuild_add_rostest_labeled label) | |
663 | if("$ENV{ROS_BUILD_TEST_LABEL}" STREQUAL "" OR "${label}" STREQUAL "$ENV{ROS_BUILD_TEST_LABEL}") | |
664 | rosbuild_add_rostest(${ARGN}) | |
665 | endif("$ENV{ROS_BUILD_TEST_LABEL}" STREQUAL "" OR "${label}" STREQUAL "$ENV{ROS_BUILD_TEST_LABEL}") | |
666 | endmacro(rosbuild_add_rostest_labeled) | |
667 | ||
668 | # A helper to run Python unit tests. It generates a command to run python | |
669 | # the specified file | |
670 | macro(rosbuild_add_pyunit file) | |
671 | string(REPLACE "/" "_" _testname ${file}) | |
672 | _rosbuild_add_pyunit(${ARGV}) | |
673 | # Redeclaration of target is to workaround bug in 2.4.6 | |
674 | if(CMAKE_MINOR_VERSION LESS 6) | |
675 | add_custom_target(test) | |
676 | endif(CMAKE_MINOR_VERSION LESS 6) | |
677 | add_dependencies(test pyunit_${_testname}) | |
678 | # Register check for test output | |
679 | _rosbuild_check_rostest_xml_result(${_testname} ${rosbuild_test_results_dir}/${PROJECT_NAME}/TEST-${_testname}.xml) | |
680 | endmacro(rosbuild_add_pyunit) | |
681 | ||
682 | # A version of add_pyunit that checks a label against ROS_BUILD_TEST_LABEL | |
683 | macro(rosbuild_add_pyunit_labeled label) | |
684 | if("$ENV{ROS_BUILD_TEST_LABEL}" STREQUAL "" OR "${label}" STREQUAL "$ENV{ROS_BUILD_TEST_LABEL}") | |
685 | rosbuild_add_pyunit(${ARGN}) | |
686 | endif("$ENV{ROS_BUILD_TEST_LABEL}" STREQUAL "" OR "${label}" STREQUAL "$ENV{ROS_BUILD_TEST_LABEL}") | |
687 | endmacro(rosbuild_add_pyunit_labeled) | |
688 | ||
689 | # Declare as a unit test a check of a roslaunch file, or a directory | |
690 | # containing roslaunch files. Following the file/directory, you can | |
691 | # specify environment variables as var=val var=val ... | |
692 | macro(rosbuild_add_roslaunch_check file) | |
693 | string(REPLACE "/" "_" _testname ${file}) | |
694 | # Append a unique count to avoid target name collisions when the same | |
695 | # launch file is tested in different environment variable configurations, | |
696 | # #3674. The counter is initialized in rosbuild_init(). | |
697 | set(_targetname roslaunch_check_${_testname}_${ROSBUILD_roslaunch_check_count}) | |
698 | # Increment counter for the next call | |
699 | math(EXPR ROSBUILD_roslaunch_check_count "${ROSBUILD_roslaunch_check_count} + 1") | |
700 | _rosbuild_add_roslaunch_check(${_targetname} ${ARGV}) | |
701 | # Redeclaration of target is to workaround bug in 2.4.6 | |
702 | if(CMAKE_MINOR_VERSION LESS 6) | |
703 | add_custom_target(test) | |
704 | endif(CMAKE_MINOR_VERSION LESS 6) | |
705 | add_dependencies(test ${_targetname}) | |
706 | endmacro(rosbuild_add_roslaunch_check) | |
707 | ||
708 | set(_ROSBUILD_GENERATED_MSG_FILES "") | |
709 | macro(rosbuild_add_generated_msgs) | |
710 | if(ROSBUILD_init_called) | |
711 | message(FATAL_ERROR "rosbuild_add_generated_msgs() cannot be called after rosbuild_init()") | |
712 | endif(ROSBUILD_init_called) | |
713 | list(APPEND _ROSBUILD_GENERATED_MSG_FILES ${ARGV}) | |
714 | endmacro(rosbuild_add_generated_msgs) | |
715 | ||
716 | # Return a list of all msg/.msg files | |
717 | macro(rosbuild_get_msgs msgvar) | |
718 | file(GLOB _msg_files RELATIVE "${PROJECT_SOURCE_DIR}/msg" "${PROJECT_SOURCE_DIR}/msg/*.msg") | |
719 | set(${msgvar} ${_ROSBUILD_GENERATED_MSG_FILES}) | |
720 | # Loop over each .msg file, establishing a rule to compile it | |
721 | foreach(_msg ${_msg_files}) | |
722 | # Make sure we didn't get a bogus match (e.g., .#Foo.msg, which Emacs | |
723 | # might create as a temporary file). the file() | |
724 | # command doesn't take a regular expression, unfortunately. | |
725 | if(${_msg} MATCHES "^[^\\.].*\\.msg$") | |
726 | list(APPEND ${msgvar} ${_msg}) | |
727 | endif(${_msg} MATCHES "^[^\\.].*\\.msg$") | |
728 | endforeach(_msg) | |
729 | endmacro(rosbuild_get_msgs) | |
730 | ||
731 | set(_ROSBUILD_GENERATED_SRV_FILES "") | |
732 | macro(rosbuild_add_generated_srvs) | |
733 | if(ROSBUILD_init_called) | |
734 | message(FATAL_ERROR "rosbuild_add_generated_srvs() cannot be called after rosbuild_init()") | |
735 | endif(ROSBUILD_init_called) | |
736 | list(APPEND _ROSBUILD_GENERATED_SRV_FILES ${ARGV}) | |
737 | endmacro(rosbuild_add_generated_srvs) | |
738 | ||
739 | # Return a list of all srv/.srv files | |
740 | macro(rosbuild_get_srvs srvvar) | |
741 | file(GLOB _srv_files RELATIVE "${PROJECT_SOURCE_DIR}/srv" "${PROJECT_SOURCE_DIR}/srv/*.srv") | |
742 | set(${srvvar} ${_ROSBUILD_GENERATED_SRV_FILES}) | |
743 | # Loop over each .srv file, establishing a rule to compile it | |
744 | foreach(_srv ${_srv_files}) | |
745 | # Make sure we didn't get a bogus match (e.g., .#Foo.srv, which Emacs | |
746 | # might create as a temporary file). the file() | |
747 | # command doesn't take a regular expression, unfortunately. | |
748 | if(${_srv} MATCHES "^[^\\.].*\\.srv$") | |
749 | list(APPEND ${srvvar} ${_srv}) | |
750 | endif(${_srv} MATCHES "^[^\\.].*\\.srv$") | |
751 | endforeach(_srv) | |
752 | endmacro(rosbuild_get_srvs) | |
753 | ||
754 | # Compute msg/srv depenendency list, with simple caching | |
755 | macro(rosbuild_gendeps _pkg _msgfile) | |
756 | # Did we already compute it? | |
757 | if(NOT ${_pkg}_${_msgfile}_GENDEPS_COMPUTED) | |
758 | # Call out to the gendeps tool to get full paths to .msg files on | |
759 | # which this one depends, for proper dependency tracking | |
760 | execute_process( | |
761 | COMMAND ${gendeps_exe} ${_input} | |
762 | OUTPUT_VARIABLE __other_msgs | |
763 | ERROR_VARIABLE __rospack_err_ignore | |
764 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
765 | # For some reason, the output from gendeps has escaped spaces in it. | |
766 | # Converting to a string and then back to a list removes them. | |
767 | _rosbuild_list_to_string(${_pkg}_${_msgfile}_GENDEPS "${__other_msgs}") | |
768 | separate_arguments(${_pkg}_${_msgfile}_GENDEPS) | |
769 | set(${_pkg}_${_msgfile}_GENDEPS_COMPUTED Y) | |
770 | endif(NOT ${_pkg}_${_msgfile}_GENDEPS_COMPUTED) | |
771 | endmacro(rosbuild_gendeps) | |
772 | ||
773 | # gensrv processes srv/*.srv files into language-specific source files | |
774 | macro(rosbuild_gensrv) | |
775 | # roslang_PACKAGE_PATH was set in rosbuild_init(), if roslang was found | |
776 | if(NOT roslang_PACKAGE_PATH) | |
777 | _rosbuild_warn("rosbuild_gensrv() was called, but the roslang package cannot be found. Service generation will NOT occur") | |
778 | endif(NOT roslang_PACKAGE_PATH) | |
779 | # Check whether there are any .srv files | |
780 | rosbuild_get_srvs(_srvlist) | |
781 | if(NOT _srvlist) | |
782 | _rosbuild_warn("rosbuild_gensrv() was called, but no .srv files were found") | |
783 | else(NOT _srvlist) | |
784 | file(WRITE ${PROJECT_SOURCE_DIR}/srv_gen/generated "yes") | |
785 | # Now set the mtime to something consistent. We only want whether or not this file exists to matter | |
786 | # But we set it to the current time, because setting it to zero causes | |
787 | # annoying warning, #3396. | |
788 | execute_process( | |
789 | COMMAND ${PYTHON_EXECUTABLE} -c "import os; os.utime('${PROJECT_SOURCE_DIR}/srv_gen/generated', (1, 1))" | |
790 | ERROR_VARIABLE _set_mtime_error | |
791 | RESULT_VARIABLE _set_mtime_failed | |
792 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
793 | if(_set_mtime_failed) | |
794 | message("[rosbuild] Error from calling to Python to set the mtime on ${PROJECT_SOURCE_DIR}/srv_gen/generated:") | |
795 | message("${_mtime_error}") | |
796 | message(FATAL_ERROR "[rosbuild] Failed to set mtime; aborting") | |
797 | endif(_set_mtime_failed) | |
798 | endif(NOT _srvlist) | |
799 | # Create target to trigger service generation in the case where no libs | |
800 | # or executables are made. | |
801 | add_custom_target(rospack_gensrv_all ALL) | |
802 | add_dependencies(rospack_gensrv_all rospack_gensrv) | |
803 | # Make the precompile target, on which libraries and executables depend, | |
804 | # depend on the message generation. | |
805 | add_dependencies(rosbuild_precompile rospack_gensrv) | |
806 | # add in the directory that will contain the auto-generated .h files | |
807 | include_directories(${PROJECT_SOURCE_DIR}/srv_gen/cpp/include) | |
808 | endmacro(rosbuild_gensrv) | |
809 | ||
810 | # genmsg processes msg/*.msg files into language-specific source files | |
811 | macro(rosbuild_genmsg) | |
812 | # roslang_PACKAGE_PATH was set in rosbuild_init(), if roslang was found | |
813 | if(NOT roslang_PACKAGE_PATH) | |
814 | _rosbuild_warn("rosbuild_genmsg() was called, but the roslang package cannot be found. Message generation will NOT occur") | |
815 | endif(NOT roslang_PACKAGE_PATH) | |
816 | # Check whether there are any .srv files | |
817 | rosbuild_get_msgs(_msglist) | |
818 | if(NOT _msglist) | |
819 | _rosbuild_warn("rosbuild_genmsg() was called, but no .msg files were found") | |
820 | else(NOT _msglist) | |
821 | file(WRITE ${PROJECT_SOURCE_DIR}/msg_gen/generated "yes") | |
822 | # Now set the mtime to something consistent. We only want whether or not this file exists to matter | |
823 | # But we set it to the current time, because setting it to zero causes | |
824 | # annoying warning, #3396. | |
825 | execute_process( | |
826 | COMMAND ${PYTHON_EXECUTABLE} -c "import os; os.utime('${PROJECT_SOURCE_DIR}/msg_gen/generated', (1, 1))" | |
827 | ERROR_VARIABLE _set_mtime_error | |
828 | RESULT_VARIABLE _set_mtime_failed | |
829 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
830 | if(_set_mtime_failed) | |
831 | message("[rosbuild] Error from calling to Python to set the mtime on ${PROJECT_SOURCE_DIR}/msg_gen/generated:") | |
832 | message("${_mtime_error}") | |
833 | message(FATAL_ERROR "[rosbuild] Failed to set mtime; aborting") | |
834 | endif(_set_mtime_failed) | |
835 | endif(NOT _msglist) | |
836 | # Create target to trigger message generation in the case where no libs | |
837 | # or executables are made. | |
838 | add_custom_target(rospack_genmsg_all ALL) | |
839 | add_dependencies(rospack_genmsg_all rospack_genmsg) | |
840 | # Make the precompile target, on which libraries and executables depend, | |
841 | # depend on the message generation. | |
842 | add_dependencies(rosbuild_precompile rospack_genmsg) | |
843 | # add in the directory that will contain the auto-generated .h files | |
844 | include_directories(${PROJECT_SOURCE_DIR}/msg_gen/cpp/include) | |
845 | endmacro(rosbuild_genmsg) | |
846 | ||
847 | macro(rosbuild_add_boost_directories) | |
848 | set(_sysroot "--sysroot=${CMAKE_FIND_ROOT_PATH}") | |
849 | execute_process(COMMAND "rosboost-cfg" ${_sysroot} "--include_dirs" | |
850 | OUTPUT_VARIABLE BOOST_INCLUDE_DIRS | |
851 | RESULT_VARIABLE _boostcfg_failed | |
852 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
853 | ||
854 | if (_boostcfg_failed) | |
855 | message(FATAL_ERROR "rosboost-cfg --include_dirs failed") | |
856 | endif(_boostcfg_failed) | |
857 | ||
858 | set(_sysroot "--sysroot=${CMAKE_FIND_ROOT_PATH}") | |
859 | execute_process(COMMAND "rosboost-cfg" ${_sysroot} "--lib_dirs" | |
860 | OUTPUT_VARIABLE BOOST_LIB_DIRS | |
861 | RESULT_VARIABLE _boostcfg_failed | |
862 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
863 | ||
864 | if (_boostcfg_failed) | |
865 | message(FATAL_ERROR "rosboost-cfg --lib_dirs failed") | |
866 | endif(_boostcfg_failed) | |
867 | ||
868 | add_definitions(-DBOOST_CB_DISABLE_DEBUG) | |
869 | include_directories(${BOOST_INCLUDE_DIRS}) | |
870 | link_directories(${BOOST_LIB_DIRS}) | |
871 | endmacro(rosbuild_add_boost_directories) | |
872 | ||
873 | macro(rosbuild_link_boost target) | |
874 | set(_libs "") | |
875 | set(_first 1) | |
876 | foreach(arg ${ARGN}) | |
877 | if (_first) | |
878 | set(_first 0) | |
879 | set(_libs "${arg}") | |
880 | else(_first) | |
881 | set(_libs "${_libs},${arg}") | |
882 | endif(_first) | |
883 | endforeach(arg) | |
884 | set(_sysroot "--sysroot=${CMAKE_FIND_ROOT_PATH}") | |
885 | # We ask for --lflags, as recommended in #3773. This means that we might | |
886 | # pass non-library arguments to target_link_libraries() below, but CMake | |
887 | # doesn't seem to mind. | |
888 | execute_process(COMMAND "rosboost-cfg" ${_sysroot} "--lflags" ${_libs} | |
889 | OUTPUT_VARIABLE BOOST_LIBS | |
890 | ERROR_VARIABLE _boostcfg_error | |
891 | RESULT_VARIABLE _boostcfg_failed | |
892 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
893 | ||
894 | if (_boostcfg_failed) | |
895 | message(FATAL_ERROR "[rosboost-cfg --libs ${_libs}] failed with error: ${_boostcfg_error}") | |
896 | endif(_boostcfg_failed) | |
897 | ||
898 | separate_arguments(BOOST_LIBS) | |
899 | ||
900 | target_link_libraries(${target} ${BOOST_LIBS}) | |
901 | endmacro(rosbuild_link_boost) | |
902 | ||
903 | # Macro to download data on the tests target | |
904 | # The real signature is: | |
905 | #macro(rosbuild_download_test_data _url _filename _md5) | |
906 | macro(rosbuild_download_test_data _url _filename) | |
907 | if("${ARGN}" STREQUAL "") | |
908 | _rosbuild_warn("The 2-argument rosbuild_download_test_data(url file) is deprecated; please switch to the 3-argument form, supplying an md5sum for the file: rosbuild_download_test_data(url file md5)") | |
909 | endif("${ARGN}" STREQUAL "") | |
910 | ||
911 | # Create a legal target name, in case the target name has slashes in it | |
912 | string(REPLACE "/" "_" _testname download_data_${_filename}) | |
913 | add_custom_command(OUTPUT ${PROJECT_SOURCE_DIR}/${_filename} | |
914 | COMMAND $ENV{ROS_ROOT}/core/rosbuild/bin/download_checkmd5.py ${_url} ${PROJECT_SOURCE_DIR}/${_filename} ${ARGN} | |
915 | VERBATIM) | |
916 | add_custom_target(${_testname} DEPENDS ${PROJECT_SOURCE_DIR}/${_filename}) | |
917 | # Redeclaration of target is to workaround bug in 2.4.6 | |
918 | if(CMAKE_MINOR_VERSION LESS 6) | |
919 | add_custom_target(tests) | |
920 | endif(CMAKE_MINOR_VERSION LESS 6) | |
921 | add_dependencies(tests ${_testname}) | |
922 | endmacro(rosbuild_download_test_data) | |
923 | ||
924 | # There's an optional 3rd arg, which is a target that should be made to | |
925 | # depend on the result of untarring the file (can be ALL). | |
926 | macro(rosbuild_untar_file _filename _unpacked_name) | |
927 | get_filename_component(unpack_dir ${_filename} PATH) | |
928 | # Check whether the filename has a directory component, #3034 | |
929 | if(NOT unpack_dir) | |
930 | set(unpack_dir ${PROJECT_SOURCE_DIR}) | |
931 | endif(NOT unpack_dir) | |
932 | add_custom_command(OUTPUT ${PROJECT_SOURCE_DIR}/${_unpacked_name} | |
933 | COMMAND rm -rf ${PROJECT_SOURCE_DIR}/${_unpacked_name} | |
934 | COMMAND tar xvCf ${unpack_dir} ${PROJECT_SOURCE_DIR}/${_filename} | |
935 | COMMAND touch -c ${PROJECT_SOURCE_DIR}/${_unpacked_name} | |
936 | DEPENDS ${PROJECT_SOURCE_DIR}/${_filename} | |
937 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} | |
938 | VERBATIM) | |
939 | string(REPLACE "/" "_" _target_name untar_file_${_filename}_${_unpacked_name}) | |
940 | ||
941 | if("${ARGN}" STREQUAL "ALL") | |
942 | add_custom_target(${_target_name} ALL DEPENDS ${PROJECT_SOURCE_DIR}/${_unpacked_name}) | |
943 | else("${ARGN}" STREQUAL "ALL") | |
944 | add_custom_target(${_target_name} DEPENDS ${PROJECT_SOURCE_DIR}/${_unpacked_name}) | |
945 | if(NOT "${ARGN}" STREQUAL "") | |
946 | # Redeclaration of target is to workaround bug in 2.4.6 | |
947 | if(CMAKE_MINOR_VERSION LESS 6) | |
948 | add_custom_target(${ARGN}) | |
949 | endif(CMAKE_MINOR_VERSION LESS 6) | |
950 | add_dependencies(${ARGN} ${_target_name}) | |
951 | endif(NOT "${ARGN}" STREQUAL "") | |
952 | endif("${ARGN}" STREQUAL "ALL") | |
953 | endmacro(rosbuild_untar_file) | |
954 | ||
955 | # Macro to download data on the all target | |
956 | # The real signature is: | |
957 | #macro(rosbuild_download_data _url _filename _md5) | |
958 | macro(rosbuild_download_data _url _filename) | |
959 | if("${ARGN}" STREQUAL "") | |
960 | _rosbuild_warn("The 2-argument rosbuild_download_data(url file) is deprecated; please switch to the 3-argument form, supplying an md5sum for the file: rosbuild_download_data(url file md5)") | |
961 | endif("${ARGN}" STREQUAL "") | |
962 | # Create a legal target name, in case the target name has slashes in it | |
963 | string(REPLACE "/" "_" _testname download_data_${_filename}) | |
964 | add_custom_command(OUTPUT ${PROJECT_SOURCE_DIR}/${_filename} | |
965 | COMMAND $ENV{ROS_ROOT}/core/rosbuild/bin/download_checkmd5.py ${_url} ${PROJECT_SOURCE_DIR}/${_filename} ${ARGN} | |
966 | VERBATIM) | |
967 | add_custom_target(${_testname} ALL DEPENDS ${PROJECT_SOURCE_DIR}/${_filename}) | |
968 | endmacro(rosbuild_download_data) | |
969 | ||
970 | macro(rosbuild_add_openmp_flags target) | |
971 | # Bullseye's wrappers appear to choke on OpenMP pragmas. So if | |
972 | # ROS_TEST_COVERAGE is set (which indicates that we're doing a coverage | |
973 | # build with Bullseye), we make this macro a no-op. | |
974 | if("$ENV{ROS_TEST_COVERAGE}" STREQUAL "1") | |
975 | _rosbuild_warn("because ROS_TEST_COVERAGE is set, OpenMP support is disabled") | |
976 | else("$ENV{ROS_TEST_COVERAGE}" STREQUAL "1") | |
977 | ||
978 | # First, try to use the standard FindOpenMP module (#3184). If that | |
979 | # fails, fall back on manual detection. I don't know why the standard | |
980 | # detection would ever fail; it's possible that the manual | |
981 | # detection is now vestigial and could be removed. | |
982 | include(FindOpenMP) | |
983 | if(${OPENMP_FOUND} STREQUAL "TRUE") | |
984 | rosbuild_add_compile_flags(${target} ${OpenMP_C_FLAGS}) | |
985 | rosbuild_add_link_flags(${target} ${OpenMP_C_FLAGS}) | |
986 | else(${OPENMP_FOUND} STREQUAL "TRUE") | |
987 | # list of OpenMP flags to check | |
988 | set(_rospack_check_openmp_flags | |
989 | "-fopenmp" # gcc | |
990 | "-openmp" # icc | |
991 | "-mp" # SGI & PGI | |
992 | "-xopenmp" # Sun | |
993 | "-omp" # Tru64 | |
994 | "-qsmp=omp" # AIX | |
995 | ) | |
996 | ||
997 | # backup for a variable we will change | |
998 | set(_rospack_openmp_flags_backup ${CMAKE_REQUIRED_FLAGS}) | |
999 | ||
1000 | # mark the fact we do not yet know the flag | |
1001 | set(_rospack_openmp_flag_found FALSE) | |
1002 | set(_rospack_openmp_flag_value) | |
1003 | ||
1004 | # find an OpenMP flag that works | |
1005 | foreach(_rospack_openmp_test_flag ${_rospack_check_openmp_flags}) | |
1006 | if(NOT _rospack_openmp_flag_found) | |
1007 | set(CMAKE_REQUIRED_FLAGS ${_rospack_openmp_test_flag}) | |
1008 | check_function_exists(omp_set_num_threads _rospack_openmp_function_found${_rospack_openmp_test_flag}) | |
1009 | ||
1010 | if(_rospack_openmp_function_found${_rospack_openmp_test_flag}) | |
1011 | set(_rospack_openmp_flag_value ${_rospack_openmp_test_flag}) | |
1012 | set(_rospack_openmp_flag_found TRUE) | |
1013 | endif(_rospack_openmp_function_found${_rospack_openmp_test_flag}) | |
1014 | endif(NOT _rospack_openmp_flag_found) | |
1015 | endforeach(_rospack_openmp_test_flag ${_rospack_check_openmp_flags}) | |
1016 | ||
1017 | # restore the CMake variable | |
1018 | set(CMAKE_REQUIRED_FLAGS ${_rospack_openmp_flags_backup}) | |
1019 | ||
1020 | # add the flags or warn | |
1021 | if(_rospack_openmp_flag_found) | |
1022 | rosbuild_add_compile_flags(${target} ${_rospack_openmp_flag_value}) | |
1023 | rosbuild_add_link_flags(${target} ${_rospack_openmp_flag_value}) | |
1024 | else(_rospack_openmp_flag_found) | |
1025 | message("WARNING: OpenMP compile flag not found") | |
1026 | endif(_rospack_openmp_flag_found) | |
1027 | ||
1028 | endif(${OPENMP_FOUND} STREQUAL "TRUE") | |
1029 | endif("$ENV{ROS_TEST_COVERAGE}" STREQUAL "1") | |
1030 | endmacro(rosbuild_add_openmp_flags) | |
1031 | ||
1032 | macro(rosbuild_make_distribution) | |
1033 | # Noop. This logic is handled in Python instead. | |
1034 | endmacro(rosbuild_make_distribution) | |
1035 | ||
1036 | # Compute the number of hardware cores on the machine. Intended to use for | |
1037 | # gating tests that have heavy processor requirements. | |
1038 | include($ENV{ROS_ROOT}/core/rosbuild/ProcessorCount.cmake) | |
1039 | macro(rosbuild_count_cores num) | |
1040 | ProcessorCount(${num}) | |
1041 | endmacro(rosbuild_count_cores) | |
1042 | ||
1043 | # Check whether we're running as a VM Intended to use for | |
1044 | # gating tests that have heavy processor requirements. It checks for | |
1045 | # /proc/xen | |
1046 | macro(rosbuild_check_for_vm var) | |
1047 | set(_xen_dir _xen_dir-NOTFOUND) | |
1048 | find_file(_xen_dir "xen" PATHS "/proc" NO_DEFAULT_PATH) | |
1049 | if(_xen_dir) | |
1050 | set(${var} 1) | |
1051 | else(_xen_dir) | |
1052 | set(${var} 0) | |
1053 | endif(_xen_dir) | |
1054 | endmacro(rosbuild_check_for_vm var) | |
1055 | ||
1056 | # Check whether there's an X display. Intended to use in gating tests that | |
1057 | # require a display. | |
1058 | macro(rosbuild_check_for_display var) | |
1059 | execute_process(COMMAND "xdpyinfo" | |
1060 | OUTPUT_VARIABLE _dummy | |
1061 | ERROR_VARIABLE _dummy | |
1062 | RESULT_VARIABLE _xdpyinfo_failed) | |
1063 | if(_xdpyinfo_failed) | |
1064 | set(${var} 0) | |
1065 | else(_xdpyinfo_failed) | |
1066 | set(${var} 1) | |
1067 | endif(_xdpyinfo_failed) | |
1068 | endmacro(rosbuild_check_for_display) | |
1069 | ||
1070 | macro(rosbuild_add_swigpy_library target lib) | |
1071 | rosbuild_add_library(${target} ${ARGN}) | |
1072 | # swig python needs a shared library named _<modulename>.[so|dll|...] | |
1073 | # this renames the output file to conform to that by prepending | |
1074 | # an underscore in place of the "lib" prefix. | |
1075 | # If on Darwin, force the suffix so ".so", because the MacPorts | |
1076 | # version of Python won't find _foo.dylib for 'import _foo' | |
1077 | if(APPLE) | |
1078 | set_target_properties(${target} | |
1079 | PROPERTIES OUTPUT_NAME ${lib} | |
1080 | PREFIX "_" SUFFIX ".so") | |
1081 | else(APPLE) | |
1082 | set_target_properties(${target} | |
1083 | PROPERTIES OUTPUT_NAME ${lib} | |
1084 | PREFIX "_") | |
1085 | endif(APPLE) | |
1086 | endmacro(rosbuild_add_swigpy_library) | |
1087 | ||
1088 | macro(rosbuild_check_for_sse) | |
1089 | # check for SSE extensions | |
1090 | include(CheckCXXSourceRuns) | |
1091 | if( CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX ) | |
1092 | set(SSE_FLAGS) | |
1093 | ||
1094 | set(CMAKE_REQUIRED_FLAGS "-msse3") | |
1095 | check_cxx_source_runs(" | |
1096 | #include <pmmintrin.h> | |
1097 | ||
1098 | int main() | |
1099 | { | |
1100 | __m128d a, b; | |
1101 | double vals[2] = {0}; | |
1102 | a = _mm_loadu_pd(vals); | |
1103 | b = _mm_hadd_pd(a,a); | |
1104 | _mm_storeu_pd(vals, b); | |
1105 | return 0; | |
1106 | }" | |
1107 | HAS_SSE3_EXTENSIONS) | |
1108 | ||
1109 | set(CMAKE_REQUIRED_FLAGS "-msse2") | |
1110 | check_cxx_source_runs(" | |
1111 | #include <emmintrin.h> | |
1112 | ||
1113 | int main() | |
1114 | { | |
1115 | __m128d a, b; | |
1116 | double vals[2] = {0}; | |
1117 | a = _mm_loadu_pd(vals); | |
1118 | b = _mm_add_pd(a,a); | |
1119 | _mm_storeu_pd(vals,b); | |
1120 | return 0; | |
1121 | }" | |
1122 | HAS_SSE2_EXTENSIONS) | |
1123 | ||
1124 | set(CMAKE_REQUIRED_FLAGS "-msse") | |
1125 | check_cxx_source_runs(" | |
1126 | #include <xmmintrin.h> | |
1127 | int main() | |
1128 | { | |
1129 | __m128 a, b; | |
1130 | float vals[4] = {0}; | |
1131 | a = _mm_loadu_ps(vals); | |
1132 | b = a; | |
1133 | b = _mm_add_ps(a,b); | |
1134 | _mm_storeu_ps(vals,b); | |
1135 | return 0; | |
1136 | }" | |
1137 | HAS_SSE_EXTENSIONS) | |
1138 | ||
1139 | set(CMAKE_REQUIRED_FLAGS) | |
1140 | ||
1141 | if(HAS_SSE3_EXTENSIONS) | |
1142 | set(SSE_FLAGS "-msse3 -mfpmath=sse") | |
1143 | message(STATUS "[rosbuild] Found SSE3 extensions, using flags: ${SSE_FLAGS}") | |
1144 | elseif(HAS_SSE2_EXTENSIONS) | |
1145 | set(SSE_FLAGS "-msse2 -mfpmath=sse") | |
1146 | message(STATUS "[rosbuild] Found SSE2 extensions, using flags: ${SSE_FLAGS}") | |
1147 | elseif(HAS_SSE_EXTENSIONS) | |
1148 | set(SSE_FLAGS "-msse -mfpmath=sse") | |
1149 | message(STATUS "[rosbuild] Found SSE extensions, using flags: ${SSE_FLAGS}") | |
1150 | endif() | |
1151 | elseif(MSVC) | |
1152 | check_cxx_source_runs(" | |
1153 | #include <emmintrin.h> | |
1154 | ||
1155 | int main() | |
1156 | { | |
1157 | __m128d a, b; | |
1158 | double vals[2] = {0}; | |
1159 | a = _mm_loadu_pd(vals); | |
1160 | b = _mm_add_pd(a,a); | |
1161 | _mm_storeu_pd(vals,b); | |
1162 | return 0; | |
1163 | }" | |
1164 | HAS_SSE2_EXTENSIONS) | |
1165 | if( HAS_SSE2_EXTENSIONS ) | |
1166 | message(STATUS "[rosbuild] Found SSE2 extensions") | |
1167 | set(SSE_FLAGS "/arch:SSE2 /fp:fast -D__SSE__ -D__SSE2__" ) | |
1168 | endif() | |
1169 | endif() | |
1170 | endmacro(rosbuild_check_for_sse) | |
1171 | ||
1172 | macro(rosbuild_include pkg module) | |
1173 | # Find exported cmake directories | |
1174 | rosbuild_invoke_rospack(rosbuild _rosbuild EXPORTS plugins --attrib=cmake_directory --top=${_project}) | |
1175 | string(REGEX REPLACE "\n" ";" _rosbuild_EXPORTS_stripped "${_rosbuild_EXPORTS}") | |
1176 | list(LENGTH _rosbuild_EXPORTS_stripped _rosbuild_EXPORTS_stripped_length) | |
1177 | set(_idx 0) | |
1178 | set(_found False) | |
1179 | while(_idx LESS ${_rosbuild_EXPORTS_stripped_length} AND NOT _found) | |
1180 | list(GET _rosbuild_EXPORTS_stripped ${_idx} _pkg) | |
1181 | math(EXPR _idx "${_idx} + 1") | |
1182 | list(GET _rosbuild_EXPORTS_stripped ${_idx} _dir) | |
1183 | if("${_pkg}" STREQUAL "${pkg}") | |
1184 | message("[rosbuild] Including ${_dir}/${module}.cmake") | |
1185 | # Add this directory to CMake's search path, so that included files | |
1186 | # can include other files in a relative manner, #2813. | |
1187 | if(NOT CMAKE_MODULE_PATH MATCHES ${_dir}) | |
1188 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${_dir}) | |
1189 | endif() | |
1190 | include(${_dir}/${module}.cmake) | |
1191 | # Poor man's break | |
1192 | set(_found True) | |
1193 | endif("${_pkg}" STREQUAL "${pkg}") | |
1194 | math(EXPR _idx "${_idx} + 1") | |
1195 | endwhile(_idx LESS ${_rosbuild_EXPORTS_stripped_length} AND NOT _found) | |
1196 | if(NOT _found) | |
1197 | message(FATAL_ERROR "[rosbuild] Failed to include ${module} from ${pkg}") | |
1198 | endif(NOT _found) | |
1199 | endmacro(rosbuild_include) | |
1200 | ||
1201 | macro(rosbuild_get_package_version _var pkgname) | |
1202 | execute_process( | |
1203 | COMMAND rosstack contains ${pkgname} | |
1204 | ERROR_VARIABLE __rosstack_err_ignore | |
1205 | OUTPUT_VARIABLE __stack | |
1206 | RESULT_VARIABLE _rosstack_failed | |
1207 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
1208 | if(_rosstack_failed OR NOT __stack) | |
1209 | message(FATAL_ERROR "[rosbuild] Failed to find stack containing package ${pkgname}") | |
1210 | else(_rosstack_failed OR NOT __stack) | |
1211 | rosbuild_get_stack_version(${_var} ${__stack}) | |
1212 | endif(_rosstack_failed OR NOT __stack) | |
1213 | endmacro(rosbuild_get_package_version) | |
1214 | ||
1215 | macro(rosbuild_get_stack_version _var stackname) | |
1216 | execute_process( | |
1217 | COMMAND rosversion ${stackname} | |
1218 | ERROR_VARIABLE __rosversion_err_ignore | |
1219 | OUTPUT_VARIABLE __version | |
1220 | RESULT_VARIABLE _rosversion_failed | |
1221 | OUTPUT_STRIP_TRAILING_WHITESPACE) | |
1222 | if(_rosversion_failed OR NOT __version) | |
1223 | message(FATAL_ERROR "[rosbuild] Failed to find version of stack ${stackname}") | |
1224 | else(_rosversion_failed OR NOT __version) | |
1225 | set(${_var} ${__version}) | |
1226 | endif(_rosversion_failed OR NOT __version) | |
1227 | endmacro(rosbuild_get_stack_version _var stackname) | |
1228 | ||
1229 | # *_future() calls are deprecated | |
1230 | macro(rosbuild_add_gtest_future exe) | |
1231 | _rosbuild_warn("The *_future() macros are deprecated.") | |
1232 | # Still need to create the executable, just in case somebody is using | |
1233 | # this macro, then doing something that refers to the executable target. | |
1234 | _rosbuild_add_gtest(${ARGV}) | |
1235 | endmacro(rosbuild_add_gtest_future) | |
1236 | ||
1237 | macro(rosbuild_add_rostest_future file) | |
1238 | _rosbuild_warn("The *_future() macros are deprecated.") | |
1239 | endmacro(rosbuild_add_rostest_future) | |
1240 | ||
1241 | macro(rosbuild_add_pyunit_future file) | |
1242 | _rosbuild_warn("The *_future() macros are deprecated.") | |
1243 | endmacro(rosbuild_add_pyunit_future) | |
1244 |
0 | ||
1 | cmake_minimum_required(VERSION 2.4.6) | |
2 | ||
3 | # | |
4 | # Catkin-compat thunks | |
5 | # | |
6 | cmake_policy(SET CMP0011 OLD) | |
7 | ||
8 | macro(rosbuild_catkinize) | |
9 | endmacro() | |
10 | ||
11 | # Policy settings to prevent warnings on 2.6 but ensure proper operation on | |
12 | # 2.4. | |
13 | if(COMMAND cmake_policy) | |
14 | # Logical target names must be globally unique. | |
15 | cmake_policy(SET CMP0002 OLD) | |
16 | # Libraries linked via full path no longer produce linker search paths. | |
17 | cmake_policy(SET CMP0003 OLD) | |
18 | # Preprocessor definition values are now escaped automatically. | |
19 | cmake_policy(SET CMP0005 OLD) | |
20 | if(POLICY CMP0011) | |
21 | # Included scripts do automatic cmake_policy PUSH and POP. | |
22 | cmake_policy(SET CMP0011 OLD) | |
23 | endif(POLICY CMP0011) | |
24 | endif(COMMAND cmake_policy) | |
25 | ||
26 | set(CMAKE_OSX_ARCHITECTURES "x86_64") | |
27 | ||
28 | ############################################################################## | |
29 | # First things first: we must have rospack. But only look at PATH, #3831 | |
30 | find_program(ROSPACK_EXE NAMES rospack DOC "rospack executable" NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) | |
31 | if (NOT ROSPACK_EXE) | |
32 | message(FATAL_ERROR "Couldn't find rospack. Please run 'make' in $ROS_ROOT") | |
33 | endif(NOT ROSPACK_EXE) | |
34 | ############################################################################## | |
35 | ||
36 | # Load private macros (not to be used externally) | |
37 | include($ENV{ROS_ROOT}/core/rosbuild/private.cmake) | |
38 | # Load public macros (developer's API) | |
39 | include($ENV{ROS_ROOT}/core/rosbuild/public.cmake) |
0 | # DO NOT EDIT THIS FILE | |
1 | ||
2 | # To change your build configuration, create a file called | |
3 | # 'rosconfig.cmake' and put it where it will be loaded from here. | |
4 | # The order of processing is the following, with later steps overriding | |
5 | # earlier ones: | |
6 | # - read the beginning of the package's CMakeLists.txt (i.e., the | |
7 | # content before the invocation of rosbuild_init()), | |
8 | # which is how we get here | |
9 | # - read this file | |
10 | # - if present, read $(ROS_ROOT)/rosconfig.cmake | |
11 | # - if present, read rosconfig.cmake from the current packages's top-level | |
12 | # directory. | |
13 | ||
14 | # Unset variables, to force cmake to go looking for the rosconfig.cmake | |
15 | # again (as opposed to looking up the variables' values in the cache). This | |
16 | # specifically avoids build failures when a rosconfig.cmake was created, | |
17 | # then removed, #617. | |
18 | set(USERCONFIG USERCONFIG-NOTFOUND) | |
19 | set(PROJECTCONFIG PROJECTCONFIG-NOTFOUND) | |
20 | ||
21 | ############################################################# | |
22 | # look for user's override in $ROS_ROOT/rosconfig.cmake | |
23 | find_file(USERCONFIG | |
24 | rosconfig.cmake | |
25 | PATHS ENV ROS_ROOT | |
26 | NO_DEFAULT_PATH | |
27 | NO_CMAKE_FIND_ROOT_PATH) | |
28 | if(USERCONFIG) | |
29 | message("including user's config file: ${USERCONFIG}") | |
30 | include(${USERCONFIG}) | |
31 | endif(USERCONFIG) | |
32 | ||
33 | # look for user's override in rosconfig.cmake in current package directory | |
34 | find_file(PROJECTCONFIG | |
35 | rosconfig.cmake | |
36 | PATHS ${PROJECT_SOURCE_DIR} | |
37 | NO_DEFAULT_PATH) | |
38 | if(PROJECTCONFIG) | |
39 | message("including package's config file: ${PROJECTCONFIG}") | |
40 | include(${PROJECTCONFIG}) | |
41 | endif(PROJECTCONFIG) | |
42 | ############################################################# | |
43 | ||
44 | ||
45 | ############################################################# | |
46 | # These are default ROS-wide build configuration settings for CMake. These | |
47 | # settings are used wherever rospack(<packagename>) is done in a | |
48 | # CMakeLists.txt file. Note that these setting only affect packages | |
49 | # that are built with CMake. | |
50 | ||
51 | # Set the build type. Options are: | |
52 | # Debug : w/ debug symbols, w/o optimization | |
53 | # Release : w/o debug symbols, w/ optimization | |
54 | # RelWithDebInfo : w/ debug symbols, w/ optimization | |
55 | # RelWithAsserts : w/o debug symbols, w/ optimization | |
56 | # MinSizeRel : w/o debug symbols, w/ optimization, stripped binaries | |
57 | if(NOT DEFINED ROS_BUILD_TYPE) | |
58 | set(ROS_BUILD_TYPE RelWithDebInfo) | |
59 | endif(NOT DEFINED ROS_BUILD_TYPE) | |
60 | ||
61 | # Build static-only executables (e.g., for copying over to another | |
62 | # machine)? true or false | |
63 | if(NOT DEFINED ROS_BUILD_STATIC_EXES) | |
64 | set(ROS_BUILD_STATIC_EXES false) | |
65 | endif(NOT DEFINED ROS_BUILD_STATIC_EXES) | |
66 | ||
67 | # Build shared libs? true or false | |
68 | if(NOT DEFINED ROS_BUILD_SHARED_LIBS) | |
69 | set(ROS_BUILD_SHARED_LIBS true) | |
70 | endif(NOT DEFINED ROS_BUILD_SHARED_LIBS) | |
71 | ||
72 | # Build static libs? true or false | |
73 | if(NOT DEFINED ROS_BUILD_STATIC_LIBS) | |
74 | set(ROS_BUILD_STATIC_LIBS false) | |
75 | endif(NOT DEFINED ROS_BUILD_STATIC_LIBS) | |
76 | ||
77 | # Default compile flags for all source files | |
78 | include(CheckCXXCompilerFlag) | |
79 | if(NOT DEFINED ROS_COMPILE_FLAGS) | |
80 | set(ROS_COMPILE_FLAGS "-W -Wall -Wno-unused-parameter -fno-strict-aliasing") | |
81 | # Old versions of gcc need -pthread to enable threading, #2095. | |
82 | # Also, some linkers, e.g., goLD, require -pthread (or another way to | |
83 | # generate -lpthread). | |
84 | # CYGWIN gcc has their -pthread disabled | |
85 | if(UNIX AND NOT CYGWIN) | |
86 | set(ROS_COMPILE_FLAGS "${ROS_COMPILE_FLAGS} -pthread") | |
87 | endif(UNIX AND NOT CYGWIN) | |
88 | endif(NOT DEFINED ROS_COMPILE_FLAGS) | |
89 | ||
90 | # Default link flags for all executables and libraries | |
91 | if(NOT DEFINED ROS_LINK_FLAGS) | |
92 | set(ROS_LINK_FLAGS "") | |
93 | # Old versions of gcc need -pthread to enable threading, #2095. | |
94 | # Also, some linkers, e.g., goLD, require -pthread (or another way to | |
95 | # generate -lpthread). | |
96 | # CYGWIN gcc has their -pthread disabled | |
97 | if(UNIX AND NOT CYGWIN) | |
98 | set(ROS_LINK_FLAGS "${ROS_LINK_FLAGS} -pthread") | |
99 | endif(UNIX AND NOT CYGWIN) | |
100 | endif(NOT DEFINED ROS_LINK_FLAGS) | |
101 | ||
102 | # Default libraries to link against for all executables and libraries | |
103 | if(NOT DEFINED ROS_LINK_LIBS) | |
104 | set(ROS_LINK_LIBS "") | |
105 | endif(NOT DEFINED ROS_LINK_LIBS) | |
106 | ||
107 | ############################################################# | |
108 |
0 | # DO NOT EDIT THIS FILE | |
1 | # | |
2 | # To set up cross-compilation, create the file | |
3 | # $(ROS_ROOT)/rostoolchain.cmake. It gets read first, prior to | |
4 | # any of cmake's system tests. | |
5 | ||
6 | ############################################################# | |
7 | # | |
8 | # An example for using the gumstix arm-linux toolchain is below. | |
9 | # Copy these lines to $(ROS_ROOT)/rostoolchain.cmake to try them out. | |
10 | # | |
11 | #set(CMAKE_SYSTEM_NAME Linux) | |
12 | #set(CMAKE_C_COMPILER /opt/arm-linux/bin/arm-linux-gcc) | |
13 | #set(CMAKE_CXX_COMPILER /opt/arm-linux/bin/arm-linux-g++) | |
14 | #set(CMAKE_FIND_ROOT_PATH /opt/arm-linux) | |
15 | # Have to set this one to BOTH, to allow CMake to find rospack | |
16 | #set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) | |
17 | #set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) | |
18 | #set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) | |
19 | ||
20 | ############################################################# | |
21 | # Cross-compilation loader | |
22 | # | |
23 | # This file is passed to cmake on the command line via | |
24 | # -DCMAKE_TOOLCHAIN_FILE. | |
25 | ||
26 | ############################################################# | |
27 | # look for user's toolchain file in $ROS_ROOT/rostoolchain.cmake | |
28 | find_file(TOOLCHAINCONFIG | |
29 | rostoolchain.cmake | |
30 | PATHS ENV ROS_ROOT | |
31 | NO_DEFAULT_PATH) | |
32 | if(TOOLCHAINCONFIG) | |
33 | include(${TOOLCHAINCONFIG}) | |
34 | endif(TOOLCHAINCONFIG) | |
35 | ############################################################# |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(roslang) | |
2 | find_package(catkin REQUIRED) | |
3 | catkin_package() | |
4 | catkin_package_export() | |
5 | ||
6 | install(FILES cmake/roslang.cmake | |
7 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/cmake) |
0 | # Figure out which languages we're building for. "rospack langs" will | |
1 | # return a list of packages that: | |
2 | # - depend directly on roslang | |
3 | # - are not in the env var ROS_LANG_DISABLE | |
4 | rosbuild_invoke_rospack("" _roslang LANGS langs) | |
5 | separate_arguments(_roslang_LANGS) | |
6 | ||
7 | # Iterate over the languages, retrieving any exported cmake fragment from | |
8 | # each one. | |
9 | set(_cmake_fragments) | |
10 | foreach(_l ${_roslang_LANGS}) | |
11 | # Get the roslang attributes from this package. | |
12 | ||
13 | # cmake | |
14 | rosbuild_invoke_rospack(${_l} ${_l} CMAKE export --lang=roslang --attrib=cmake) | |
15 | if(${_l}_CMAKE) | |
16 | foreach(_f ${${_l}_CMAKE}) | |
17 | list(APPEND _cmake_fragments ${_f}) | |
18 | endforeach(_f) | |
19 | endif(${_l}_CMAKE) | |
20 | endforeach(_l) | |
21 | ||
22 | # Now include them all | |
23 | foreach(_f ${_cmake_fragments}) | |
24 | if(NOT EXISTS ${_f}) | |
25 | message(FATAL_ERROR "Cannot include non-existent exported cmake file ${_f}") | |
26 | endif(NOT EXISTS ${_f}) | |
27 | # Include this cmake fragment; presumably it will do / | |
28 | # provide something useful. Only include each file once (a file | |
29 | # might be multiply referenced because of package dependencies | |
30 | # dependencies). | |
31 | if(NOT ${_f}_INCLUDED) | |
32 | message("[rosbuild] Including ${_f}") | |
33 | include(${_f}) | |
34 | set(${_f}_INCLUDED Y) | |
35 | endif(NOT ${_f}_INCLUDED) | |
36 | endforeach(_f) |
0 | <package> | |
1 | <description brief="ROS client library base"> | |
2 | ||
3 | roslang is a common package that all | |
4 | <a href="http://www.ros.org/wiki/Client%20Libraries">ROS client | |
5 | libraries</a> depend on. This is mainly used to find client | |
6 | libraries (via 'rospack depends-on1 roslang'). | |
7 | ||
8 | </description> | |
9 | <author>Brian Gerkey/gerkey@willowgarage.com</author> | |
10 | <license>BSD</license> | |
11 | <review status="Doc reviewed" notes="2010/01/10"/> | |
12 | <url>http://ros.org/wiki/roslang</url> | |
13 | </package> | |
14 |
0 | <package> | |
1 | <name>roslang</name> | |
2 | <version>1.9.11</version> | |
3 | <description brief="ROS client library base"> | |
4 | ||
5 | roslang is a common package that all | |
6 | <a href="http://www.ros.org/wiki/Client%20Libraries">ROS client | |
7 | libraries</a> depend on. This is mainly used to find client | |
8 | libraries (via 'rospack depends-on1 roslang'). | |
9 | ||
10 | </description> | |
11 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
12 | <license>BSD</license> | |
13 | ||
14 | <url>http://ros.org/wiki/roslang</url> | |
15 | <author email="gerkey@willowgarage.com">Brian Gerkey</author> | |
16 | ||
17 | <build_depend>catkin</build_depend> | |
18 | </package> |
0 | project(roslib) | |
1 | find_package(catkin REQUIRED COMPONENTS rospack) | |
2 | catkin_package() | |
3 | catkin_package_export( | |
4 | INCLUDE_DIRS include | |
5 | LIBRARIES roslib | |
6 | CFG_EXTRAS roslib-extras.cmake) | |
7 | ||
8 | catkin_python_setup() | |
9 | ||
10 | find_package(Boost REQUIRED COMPONENTS thread) | |
11 | ||
12 | include_directories(include ${Boost_INCLUDE_DIRS} ${catkin_INCLUDE_DIRS}) | |
13 | link_directories(${catkin_LIBRARY_DIRS}) | |
14 | ||
15 | # Avoid a boost warning that pops up when using msvc compiler | |
16 | if(MSVC) | |
17 | add_definitions(-D_SCL_SECURE_NO_WARNINGS) | |
18 | endif() | |
19 | ||
20 | add_library(roslib src/package.cpp) | |
21 | target_link_libraries(roslib ${Boost_LIBRARIES} ${catkin_LIBRARIES}) | |
22 | ||
23 | if(NOT (APPLE OR WIN32 OR MINGW)) | |
24 | target_link_libraries(roslib rt) | |
25 | endif() | |
26 | ||
27 | ||
28 | #explicitly install library and includes | |
29 | install(TARGETS roslib | |
30 | ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} | |
31 | LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} | |
32 | ) | |
33 | ||
34 | install(DIRECTORY include/ | |
35 | DESTINATION ${CATKIN_GLOBAL_INCLUDE_DESTINATION} | |
36 | FILES_MATCHING PATTERN "*.h") | |
37 | install(PROGRAMS scripts/gendeps | |
38 | DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) | |
39 | ||
40 | #integration tests | |
41 | ||
42 | catkin_add_nosetests(test) | |
43 | ||
44 | include_directories(${GTEST_INCLUDE_DIRS}) | |
45 | link_directories(${GTEST_LIBRARY_DIRS}) | |
46 | ||
47 | catkin_add_gtest(${PROJECT_NAME}-utest test/utest.cpp WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) | |
48 | if(TARGET ${PROJECT_NAME}-utest) | |
49 | target_link_libraries(${PROJECT_NAME}-utest roslib ${Boost_LIBRARIES} ${catkin_LIBRARIES}) | |
50 | endif() | |
51 | ||
52 | catkin_add_gtest(${PROJECT_NAME}-test_package test/package.cpp) | |
53 | if(TARGET ${PROJECT_NAME}-test_package) | |
54 | target_link_libraries(${PROJECT_NAME}-test_package roslib ${catkin_LIBRARIES}) | |
55 | endif() |
0 | # generated from ros/core/roslib/cmake/roslib-extras.cmake.in | |
1 | ||
2 | # set path to gendeps executable | |
3 | if (@BUILDSPACE@) | |
4 | set(gendeps_exe @CMAKE_CURRENT_SOURCE_DIR@/scripts/gendeps) | |
5 | else() | |
6 | set(gendeps_exe @CMAKE_INSTALL_PREFIX@/lib/roslib/gendeps) | |
7 | endif() |
0 | [epydoc] | |
1 | name: roslib | |
2 | modules: roslib | |
3 | inheritance: included | |
4 | url: http://ros.org/wiki/rospy | |
5 | frames: yes | |
6 | private: no | |
7 | exclude: roslib.msg | |
8 |
0 | /* | |
1 | * Copyright (C) 2009, Willow Garage, Inc. | |
2 | * | |
3 | * Redistribution and use in source and binary forms, with or without | |
4 | * modification, are permitted provided that the following conditions are met: | |
5 | * * Redistributions of source code must retain the above copyright notice, | |
6 | * this list of conditions and the following disclaimer. | |
7 | * * Redistributions in binary form must reproduce the above copyright | |
8 | * notice, this list of conditions and the following disclaimer in the | |
9 | * documentation and/or other materials provided with the distribution. | |
10 | * * Neither the names of Stanford University or Willow Garage, Inc. nor the names of its | |
11 | * contributors may be used to endorse or promote products derived from | |
12 | * this software without specific prior written permission. | |
13 | * | |
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
24 | * POSSIBILITY OF SUCH DAMAGE. | |
25 | */ | |
26 | ||
27 | #ifndef ROSLIB_PACKAGE_H | |
28 | #define ROSLIB_PACKAGE_H | |
29 | ||
30 | #include <string> | |
31 | #include <vector> | |
32 | #include <map> | |
33 | ||
34 | #if defined(__GNUC__) | |
35 | # define ROS_DEPRECATED __attribute__((deprecated)) | |
36 | # define ROS_FORCE_INLINE __attribute__((always_inline)) | |
37 | #elif defined(MSVC) | |
38 | # define ROS_DEPRECATED | |
39 | # define ROS_FORCE_INLINE __forceinline | |
40 | #else | |
41 | # define ROS_DEPRECATED | |
42 | # define ROS_FORCE_INLINE inline | |
43 | #endif | |
44 | ||
45 | /* | |
46 | Windows import/export and gnu http://gcc.gnu.org/wiki/Visibility | |
47 | macros. | |
48 | */ | |
49 | ||
50 | #if defined(_MSC_VER) | |
51 | # define ROS_HELPER_IMPORT __declspec(dllimport) | |
52 | # define ROS_HELPER_EXPORT __declspec(dllexport) | |
53 | #elif __GNUC__ >= 4 | |
54 | # define ROS_HELPER_IMPORT __attribute__ ((visibility("default"))) | |
55 | # define ROS_HELPER_EXPORT __attribute__ ((visibility("default"))) | |
56 | #else | |
57 | # define ROS_HELPER_IMPORT | |
58 | # define ROS_HELPER_EXPORT | |
59 | #endif | |
60 | ||
61 | // Ignore warnings about import/exports when deriving from std classes. | |
62 | #ifdef _MSC_VER | |
63 | # pragma warning(disable: 4251) | |
64 | # pragma warning(disable: 4275) | |
65 | #endif | |
66 | ||
67 | #ifdef ROS_BUILD_SHARED_LIBS // ros is being built around shared libraries | |
68 | # ifdef roslib_EXPORTS // we are building a shared lib/dll | |
69 | # define ROSLIB_DECL ROS_HELPER_EXPORT | |
70 | # else // we are using shared lib/dll | |
71 | # define ROSLIB_DECL ROS_HELPER_IMPORT | |
72 | # endif | |
73 | #else // ros is being built around static libraries | |
74 | # define ROSLIB_DECL | |
75 | #endif | |
76 | ||
77 | namespace ros | |
78 | { | |
79 | namespace package | |
80 | { | |
81 | ||
82 | typedef std::vector<std::string> V_string; | |
83 | typedef std::map<std::string, std::string> M_string; | |
84 | ||
85 | /** | |
86 | * \brief Runs a rospack command of the form 'rospack <cmd>', returning the output as a single string | |
87 | */ | |
88 | ROSLIB_DECL std::string command(const std::string& cmd); | |
89 | ||
90 | /** | |
91 | * \brief Runs a rospack command of the form 'rospack <cmd>', returning the output as a vector of strings, split by newlines | |
92 | */ | |
93 | ROSLIB_DECL void command(const std::string& cmd, V_string& output); | |
94 | /** | |
95 | * \brief Returns the fully-qualified path to a package, or an empty string if the package is not found | |
96 | */ | |
97 | ROSLIB_DECL std::string getPath(const std::string& package_name); | |
98 | /** | |
99 | * \brief Gets a list of all packages. Returns false if it could not run the command. | |
100 | */ | |
101 | ROSLIB_DECL bool getAll(V_string& packages); | |
102 | ||
103 | /** | |
104 | * \brief Call the "rospack plugins" command, eg. "rospack plugins --attrib=<attribute> <package>". Returns a vector of strings which | |
105 | * are export values | |
106 | */ | |
107 | ROSLIB_DECL void getPlugins(const std::string& package, const std::string& attribute, V_string& plugins); | |
108 | ||
109 | /** | |
110 | * \brief Call the "rospack plugins" command, eg. "rospack plugins --attrib=<attribute> <package>". Returns a map of package name to | |
111 | * export value. | |
112 | */ | |
113 | ROSLIB_DECL void getPlugins(const std::string& package, const std::string& attribute, M_string& plugins); | |
114 | ||
115 | } // namespace package | |
116 | } // namespace ros | |
117 | ||
118 | #endif |
0 | /** | |
1 | \mainpage | |
2 | \htmlinclude manifest.html | |
3 | ||
4 | ||
5 | \b %roslib is the base library support for ROS <a href="http://ros.org/wiki/Client Libraries">client implementations</a> as well as ROS tools. It includes: | |
6 | ||
7 | - common message definitions used in ROS clients (e.g. Header) | |
8 | - a Python library for manipulating ROS system resources (e.g. .msg files, names) | |
9 | - message and services generation callout scripts | |
10 | ||
11 | \section codeapi Code API | |
12 | ||
13 | - Time-related: ros::Time, ros::Duration, ros::Rate, ros::WallTime, ros::WallDuration, ros::WallRate. Also see the <a href="http://ros.org/wiki/roscpp/Overview/Time">roscpp Time overview</a>. | |
14 | - Package-related: ros::package namespace | |
15 | - Debug-related: ros::debug namespace | |
16 | ||
17 | */ |
0 | <package> | |
1 | <description brief="ROS base library"> | |
2 | ||
3 | Base dependencies and support libraries for ROS. roslib contains many of the common data structures and tools that are shared across ROS client library implementations. | |
4 | ||
5 | </description> | |
6 | <author>Ken Conley/kwc@willowgarage.com, Morgan Quigley/mquigley@cs.stanford.edu, Josh Faust/jfaust@willowgarage.com</author> | |
7 | <license>BSD</license> | |
8 | <review status="Doc reviewed" notes="2010/01/12"/> | |
9 | <url>http://ros.org/wiki/roslib</url> | |
10 | <export> | |
11 | <cpp cflags="`pkg-config --cflags roslib`" | |
12 | lflags="`pkg-config --libs roslib`"/> | |
13 | <rosdoc config="${prefix}/rosdoc.yaml" /> | |
14 | </export> | |
15 | ||
16 | <rosdep name="python"/> | |
17 | <rosdep name="boost"/> | |
18 | </package> |
0 | <package> | |
1 | <name>roslib</name> | |
2 | <version>1.9.11</version> | |
3 | <description brief="ROS base library"> | |
4 | ||
5 | Base dependencies and support libraries for ROS. roslib contains many of the common data structures and tools that are shared across ROS client library implementations. | |
6 | ||
7 | </description> | |
8 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
9 | <license>BSD</license> | |
10 | ||
11 | <url>http://ros.org/wiki/roslib</url> | |
12 | <author email="kwc@willowgarage.com">Ken Conley</author> | |
13 | <author email="mquigley@cs.stanford.edu">Morgan Quigley</author> | |
14 | <author email="jfaust@willowgarage.com">Josh Faust</author> | |
15 | ||
16 | <build_depend>catkin</build_depend> | |
17 | <build_depend>boost</build_depend> | |
18 | <build_depend>python</build_depend> | |
19 | <build_depend>rospack</build_depend> | |
20 | ||
21 | <export> | |
22 | <rosdoc config="${prefix}/rosdoc.yaml" /> | |
23 | </export> | |
24 | </package> |
0 | - builder: epydoc | |
1 | output_dir: python | |
2 | config: epydoc.config | |
3 | - builder: doxygen | |
4 | name: C++ API | |
5 | output_dir: c++ | |
6 | file_patterns: '*.c *.cpp *.h *.cc *.hh *.dox'⏎ |
0 | #! /usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id: gendeps 3009 2008-12-05 00:47:44Z sfkwc $ | |
34 | # $Author: sfkwc $ | |
35 | ||
36 | ## Script for determining the ROS messages a particular message | |
37 | ## depends on. This is important for determining messages file that | |
38 | ## need to be rebuilt. | |
39 | ||
40 | import sys | |
41 | ||
42 | import roslib.msgs | |
43 | import roslib.srvs | |
44 | import roslib.gentools | |
45 | ||
46 | NAME='gendeps' | |
47 | ||
48 | def usage(progname, stdout=sys.stdout): | |
49 | print >> stdout, "%(progname)s msg-or-srv-file"%vars() | |
50 | ||
51 | ## main method for gendeps command | |
52 | ## @param argv [str]: sys args | |
53 | ## @param stdout pipe: stdout pipe | |
54 | ## @param stderr pipe: stderr pipe | |
55 | def gendeps_main(argv, stdout, stderr): | |
56 | from optparse import OptionParser | |
57 | parser = OptionParser(usage="usage: %prog [options] [files...]", prog=NAME) | |
58 | parser.add_option("-m", "--md5", | |
59 | dest="md5", default=False, | |
60 | action="store_true", | |
61 | help="Generate md5 hash of files") | |
62 | parser.add_option("-s", "--sha1", | |
63 | dest="sha1", default=False, | |
64 | action="store_true", | |
65 | help="Generate SHA1 hash of files") | |
66 | parser.add_option("-c", "--cat", | |
67 | dest="cat_files", default=False, | |
68 | action="store_true", | |
69 | help="Generate concatenated list of files") | |
70 | (options, args) = parser.parse_args(argv) | |
71 | ||
72 | # get the file name | |
73 | if len(args) != 2: | |
74 | parser.error("you must specify one input file") | |
75 | f = args[1] | |
76 | ||
77 | # validate that options are compatible | |
78 | if options.md5 and (options.sha1 or options.cat_files): | |
79 | parser.error("md5 option is not compatible with other options") | |
80 | if options.sha1 and (options.md5 or options.cat_files): | |
81 | parser.error("sha1 option is not compatible with other options") | |
82 | if options.cat_files and (options.md5 or options.sha1): | |
83 | parser.error("cat option is not compatible with other options") | |
84 | ||
85 | retval = roslib.gentools.get_file_dependencies(f, stdout=stdout, stderr=stderr) | |
86 | ||
87 | if options.md5: | |
88 | print >> stdout, roslib.gentools.compute_md5(retval) | |
89 | elif options.sha1: | |
90 | print >> stdout, roslib.gentools.compute_sha1(retval) | |
91 | elif options.cat_files: | |
92 | # this option is used for the message definition that is | |
93 | # stored in exchanged in ROS handshakes and then stored in bag | |
94 | # files | |
95 | print >> stdout, roslib.gentools.compute_full_text(retval) | |
96 | else: | |
97 | print >> stdout, ' '.join(retval['files'].itervalues()) | |
98 | ||
99 | if __name__ == "__main__": | |
100 | try: | |
101 | gendeps_main(sys.argv, sys.stdout, sys.stderr) | |
102 | except Exception, e: | |
103 | print >> sys.stderr, e | |
104 | sys.exit(1) | |
105 |
0 | #!/usr/bin/env python | |
1 | ||
2 | from distutils.core import setup | |
3 | from catkin_pkg.package import parse_package_for_distutils | |
4 | ||
5 | d = parse_package_for_distutils() | |
6 | d['packages'] = ['roslib'] | |
7 | d['package_dir'] = {'': 'src'} | |
8 | d['install_requires'] = ['rospkg'] | |
9 | ||
10 | setup(**d) |
0 | /* | |
1 | * Copyright (C) 2009, Willow Garage, Inc. | |
2 | * | |
3 | * Redistribution and use in source and binary forms, with or without | |
4 | * modification, are permitted provided that the following conditions are met: | |
5 | * * Redistributions of source code must retain the above copyright notice, | |
6 | * this list of conditions and the following disclaimer. | |
7 | * * Redistributions in binary form must reproduce the above copyright | |
8 | * notice, this list of conditions and the following disclaimer in the | |
9 | * documentation and/or other materials provided with the distribution. | |
10 | * * Neither the names of Stanford University or Willow Garage, Inc. nor the names of its | |
11 | * contributors may be used to endorse or promote products derived from | |
12 | * this software without specific prior written permission. | |
13 | * | |
14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
24 | * POSSIBILITY OF SUCH DAMAGE. | |
25 | */ | |
26 | ||
27 | #include "ros/package.h" | |
28 | #include "rospack/rospack.h" | |
29 | ||
30 | #include <cstdio> | |
31 | #include <iostream> | |
32 | ||
33 | #include <boost/algorithm/string/split.hpp> | |
34 | #include <boost/algorithm/string/join.hpp> | |
35 | #include <boost/algorithm/string/classification.hpp> | |
36 | #include <boost/thread/mutex.hpp> | |
37 | ||
38 | namespace ros | |
39 | { | |
40 | namespace package | |
41 | { | |
42 | ||
43 | // Mutex used to lock calls into librospack, which is not thread-safe. | |
44 | static boost::mutex librospack_mutex; | |
45 | ||
46 | std::string command(const std::string& _cmd) | |
47 | { | |
48 | boost::mutex::scoped_lock lock(librospack_mutex); | |
49 | ||
50 | rospack::ROSPack rp; | |
51 | int ret; | |
52 | try | |
53 | { | |
54 | ret = rp.run(_cmd); | |
55 | if(ret == 0) | |
56 | return rp.getOutput(); | |
57 | else { | |
58 | if ( !rp.is_quiet() ) | |
59 | std::cerr << "ROSPack::run returned non-zero." << std::endl; | |
60 | } | |
61 | } | |
62 | catch(std::runtime_error &e) | |
63 | { | |
64 | if ( !rp.is_quiet() ) | |
65 | std::cerr << "[rospack] " << e.what() << std::endl; | |
66 | } | |
67 | return std::string(""); | |
68 | } | |
69 | ||
70 | void command(const std::string& cmd, V_string& output) | |
71 | { | |
72 | std::string out_string = command(cmd); | |
73 | V_string full_list; | |
74 | boost::split(full_list, out_string, boost::is_any_of("\r\n")); | |
75 | ||
76 | // strip empties | |
77 | V_string::iterator it = full_list.begin(); | |
78 | V_string::iterator end = full_list.end(); | |
79 | for (; it != end; ++it) | |
80 | { | |
81 | if (!it->empty()) | |
82 | { | |
83 | output.push_back(*it); | |
84 | } | |
85 | } | |
86 | } | |
87 | ||
88 | std::string getPath(const std::string& package_name) | |
89 | { | |
90 | std::string path = command("find " + package_name); | |
91 | ||
92 | // scrape any newlines out of it | |
93 | for (size_t newline = path.find('\n'); newline != std::string::npos; | |
94 | newline = path.find('\n')) | |
95 | { | |
96 | path.erase(newline, 1); | |
97 | } | |
98 | ||
99 | return path; | |
100 | } | |
101 | ||
102 | bool getAll(V_string& packages) | |
103 | { | |
104 | command("list-names", packages); | |
105 | ||
106 | return true; | |
107 | } | |
108 | ||
109 | void getPlugins(const std::string& package, const std::string& attribute, V_string& plugins) | |
110 | { | |
111 | M_string plugins_map; | |
112 | getPlugins(package, attribute, plugins_map); | |
113 | M_string::iterator it = plugins_map.begin(); | |
114 | M_string::iterator end = plugins_map.end(); | |
115 | for (; it != end; ++it) | |
116 | { | |
117 | plugins.push_back(it->second); | |
118 | } | |
119 | } | |
120 | ||
121 | void getPlugins(const std::string& package, const std::string& attribute, M_string& plugins) | |
122 | { | |
123 | V_string lines; | |
124 | command("plugins --attrib=" + attribute + " " + package, lines); | |
125 | ||
126 | V_string::iterator it = lines.begin(); | |
127 | V_string::iterator end = lines.end(); | |
128 | for (; it != end; ++it) | |
129 | { | |
130 | V_string tokens; | |
131 | boost::split(tokens, *it, boost::is_any_of(" ")); | |
132 | ||
133 | if (tokens.size() >= 2) | |
134 | { | |
135 | std::string package = tokens[0]; | |
136 | std::string rest = boost::join(V_string(tokens.begin() + 1, tokens.end()), " "); | |
137 | plugins[package] = rest; | |
138 | } | |
139 | } | |
140 | } | |
141 | ||
142 | } // namespace package | |
143 | } // namespace ros |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id: __init__.py 3544 2009-01-24 00:09:21Z sfkwc $ | |
33 | ||
34 | ## The ros module enables dynamic importing of any ROS python module. | |
35 | ## The common syntax is 'from ros import foo', where foo is a ROS package | |
36 | ## name. | |
37 | ||
38 | import sys | |
39 | ||
40 | import roslib | |
41 | ||
42 | ## @internal | |
43 | class Module(object): | |
44 | def __init__(self, wrapped): | |
45 | self.wrapped = wrapped | |
46 | ||
47 | def __getattr__(self, name): | |
48 | try: | |
49 | return getattr(self.wrapped, name) | |
50 | except AttributeError: | |
51 | import roslib.packages | |
52 | try: | |
53 | roslib.load_manifest(name.split('.')[0]) | |
54 | except roslib.packages.InvalidROSPkgException as e: | |
55 | raise ImportError("Cannot import module '%s': \n%s"%(name, str(e))) | |
56 | return __import__(name) | |
57 | ||
58 | ## rewrite our own entry in sys.modules so that dynamic loading | |
59 | ## works. | |
60 | sys.modules[__name__] = Module(sys.modules[__name__]) | |
61 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | """ | |
33 | roslib is largely deprecated starting in the ROS Fuerte release. | |
34 | ||
35 | roslib has a very important role in all Python code written for ROS: | |
36 | it contains the L{load_manifest()} method, which updates the | |
37 | PYTHONPATH based on a set of ROS Package manifest.xml files. | |
38 | ||
39 | Beyond the important load_manifest() call, most of the rest of roslib | |
40 | consists of low-level libraries that 99% of ROS users need not | |
41 | interact with. These libraries are primarily to support higher-level | |
42 | ROS Python libraries, such as the rospy client library, as well as | |
43 | numerous ROS tools (e.g. rostopic). | |
44 | ||
45 | """ | |
46 | ||
47 | __version__ = '1.7.0' | |
48 | ||
49 | from roslib.launcher import load_manifest | |
50 | ||
51 | # this import is necessary due to a bug in purge_build.py in our | |
52 | # debian assets. | |
53 | import roslib.stacks | |
54 | ||
55 | _is_interactive = False | |
56 | def set_interactive(interactive): | |
57 | """ | |
58 | General API for a script specifying that it is being run in an | |
59 | interactive environment. Many libraries may wish to change their | |
60 | behavior based on being interactive (e.g. disabling signal | |
61 | handlers on Ctrl-C). | |
62 | ||
63 | @param interactive: True if current script is being run in an interactive shell | |
64 | @type interactive: bool | |
65 | """ | |
66 | global _is_interactive | |
67 | _is_interactive = interactive | |
68 | ||
69 | def is_interactive(): | |
70 | """ | |
71 | General API for a script specifying that it is being run in an | |
72 | interactive environment. Many libraries may wish to change their | |
73 | behavior based on being interactive (e.g. disabling signal | |
74 | handlers on Ctrl-C). | |
75 | ||
76 | @return: True if interactive flag has been set | |
77 | @rtype: bool | |
78 | """ | |
79 | return _is_interactive | |
80 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | # $Author$ | |
34 | ||
35 | """ | |
36 | Provides the L{ROSLibException} class, which is common to many roslib libraries. | |
37 | """ | |
38 | ||
39 | class ROSLibException(Exception): | |
40 | """ | |
41 | Base class for exceptions in roslib | |
42 | """ | |
43 | pass |
0 | #! /usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | # $Author$ | |
35 | ||
36 | """ | |
37 | Library for supporting message and service generation for all ROS | |
38 | client libraries. This is mainly responsible for calculating the | |
39 | md5sums and message definitions of classes. | |
40 | """ | |
41 | ||
42 | # NOTE: this should not contain any rospy-specific code. The rospy | |
43 | # generator library is rospy.genpy. | |
44 | ||
45 | import sys | |
46 | ||
47 | try: | |
48 | from cStringIO import StringIO # Python 2.x | |
49 | except ImportError: | |
50 | from io import StringIO # Python 3.x | |
51 | ||
52 | import rospkg | |
53 | ||
54 | import roslib.msgs | |
55 | from roslib.msgs import MsgSpecException | |
56 | import roslib.names | |
57 | import roslib.srvs | |
58 | ||
59 | # name of the Header type as gentools knows it | |
60 | _header_type_name = 'std_msgs/Header' | |
61 | ||
62 | def _add_msgs_depends(rospack, spec, deps, package_context): | |
63 | """ | |
64 | Add the list of message types that spec depends on to depends. | |
65 | @param spec: message to compute dependencies for | |
66 | @type spec: roslib.msgs.MsgSpec/roslib.srvs.SrvSpec | |
67 | @param deps [str]: list of dependencies. This list will be updated | |
68 | with the dependencies of spec when the method completes | |
69 | @type deps: [str] | |
70 | @raise KeyError for invalid dependent types due to missing package dependencies. | |
71 | """ | |
72 | valid_packages = ['', package_context] | |
73 | try: | |
74 | valid_packages = valid_packages + rospack.get_depends(package_context, implicit=True) | |
75 | except rospkg.ResourceNotFound: | |
76 | # this happens in dynamic generation situations where the | |
77 | # package is not present. we soft fail here because we assume | |
78 | # missing messages will be caught later during lookup. | |
79 | pass | |
80 | ||
81 | for t in spec.types: | |
82 | t = roslib.msgs.base_msg_type(t) | |
83 | if not roslib.msgs.is_builtin(t): | |
84 | t_package, t_base = roslib.names.package_resource_name(t) | |
85 | ||
86 | # special mapping for header | |
87 | if t == roslib.msgs.HEADER: | |
88 | # have to re-names Header | |
89 | deps.append(_header_type_name) | |
90 | ||
91 | if roslib.msgs.is_registered(t): | |
92 | depspec = roslib.msgs.get_registered(t) | |
93 | if t != roslib.msgs.HEADER: | |
94 | if '/' in t: | |
95 | deps.append(t) | |
96 | else: | |
97 | deps.append(package_context+'/'+t) | |
98 | ||
99 | elif t_package in valid_packages: | |
100 | # if we are allowed to load the message, load it. | |
101 | key, depspec = roslib.msgs.load_by_type(t, package_context) | |
102 | if t != roslib.msgs.HEADER: | |
103 | deps.append(key) | |
104 | roslib.msgs.register(key, depspec) | |
105 | else: | |
106 | # not allowed to load the message, so error. | |
107 | raise KeyError(t) | |
108 | _add_msgs_depends(rospack, depspec, deps, package_context) | |
109 | ||
110 | def compute_md5_text(get_deps_dict, spec): | |
111 | """ | |
112 | Compute the text used for md5 calculation. MD5 spec states that we | |
113 | removes comments and non-meaningful whitespace. We also strip | |
114 | packages names from type names. For convenience sake, constants are | |
115 | reordered ahead of other declarations, in the order that they were | |
116 | originally defined. | |
117 | ||
118 | @return: text for ROS MD5-processing | |
119 | @rtype: str | |
120 | """ | |
121 | uniquedeps = get_deps_dict['uniquedeps'] | |
122 | package = get_deps_dict['package'] | |
123 | # #1554: need to suppress computation of files in dynamic generation case | |
124 | compute_files = 'files' in get_deps_dict | |
125 | ||
126 | buff = StringIO() | |
127 | ||
128 | for c in spec.constants: | |
129 | buff.write("%s %s=%s\n"%(c.type, c.name, c.val_text)) | |
130 | for type_, name in zip(spec.types, spec.names): | |
131 | base_msg_type = roslib.msgs.base_msg_type(type_) | |
132 | # md5 spec strips package names | |
133 | if roslib.msgs.is_builtin(base_msg_type): | |
134 | buff.write("%s %s\n"%(type_, name)) | |
135 | else: | |
136 | # recursively generate md5 for subtype. have to build up | |
137 | # dependency representation for subtype in order to | |
138 | # generate md5 | |
139 | ||
140 | # - ugly special-case handling of Header | |
141 | if base_msg_type == roslib.msgs.HEADER: | |
142 | base_msg_type = _header_type_name | |
143 | ||
144 | sub_pkg, _ = roslib.names.package_resource_name(base_msg_type) | |
145 | sub_pkg = sub_pkg or package | |
146 | sub_spec = roslib.msgs.get_registered(base_msg_type, package) | |
147 | sub_deps = get_dependencies(sub_spec, sub_pkg, compute_files=compute_files) | |
148 | sub_md5 = compute_md5(sub_deps) | |
149 | buff.write("%s %s\n"%(sub_md5, name)) | |
150 | ||
151 | return buff.getvalue().strip() # remove trailing new line | |
152 | ||
153 | def _compute_hash(get_deps_dict, hash): | |
154 | """ | |
155 | subroutine of compute_md5() | |
156 | @param get_deps_dict: dictionary returned by get_dependencies call | |
157 | @type get_deps_dict: dict | |
158 | @param hash: hash instance | |
159 | @type hash: hash instance | |
160 | """ | |
161 | # accumulate the hash | |
162 | # - root file | |
163 | from roslib.msgs import MsgSpec | |
164 | from roslib.srvs import SrvSpec | |
165 | spec = get_deps_dict['spec'] | |
166 | if isinstance(spec, MsgSpec): | |
167 | hash.update(compute_md5_text(get_deps_dict, spec)) | |
168 | elif isinstance(spec, SrvSpec): | |
169 | hash.update(compute_md5_text(get_deps_dict, spec.request)) | |
170 | hash.update(compute_md5_text(get_deps_dict, spec.response)) | |
171 | else: | |
172 | raise Exception("[%s] is not a message or service"%spec) | |
173 | return hash.hexdigest() | |
174 | ||
175 | def _compute_hash_v1(get_deps_dict, hash): | |
176 | """ | |
177 | subroutine of compute_md5_v1() | |
178 | @param get_deps_dict: dictionary returned by get_dependencies call | |
179 | @type get_deps_dict: dict | |
180 | @param hash: hash instance | |
181 | @type hash: hash instance | |
182 | """ | |
183 | uniquedeps = get_deps_dict['uniquedeps'] | |
184 | spec = get_deps_dict['spec'] | |
185 | # accumulate the hash | |
186 | # - root file | |
187 | hash.update(spec.text) | |
188 | # - dependencies | |
189 | for d in uniquedeps: | |
190 | hash.update(roslib.msgs.get_registered(d).text) | |
191 | return hash.hexdigest() | |
192 | ||
193 | def compute_md5_v1(get_deps_dict): | |
194 | """ | |
195 | Compute original V1 md5 hash for message/service. This was replaced with V2 in ROS 0.6. | |
196 | @param get_deps_dict: dictionary returned by get_dependencies call | |
197 | @type get_deps_dict: dict | |
198 | @return: md5 hash | |
199 | @rtype: str | |
200 | """ | |
201 | import hashlib | |
202 | return _compute_hash_v1(get_deps_dict, hashlib.md5()) | |
203 | ||
204 | def compute_md5(get_deps_dict): | |
205 | """ | |
206 | Compute md5 hash for message/service | |
207 | @param get_deps_dict dict: dictionary returned by get_dependencies call | |
208 | @type get_deps_dict: dict | |
209 | @return: md5 hash | |
210 | @rtype: str | |
211 | """ | |
212 | try: | |
213 | # md5 is deprecated in Python 2.6 in favor of hashlib, but hashlib is | |
214 | # unavailable in Python 2.4 | |
215 | import hashlib | |
216 | return _compute_hash(get_deps_dict, hashlib.md5()) | |
217 | except ImportError: | |
218 | import md5 | |
219 | return _compute_hash(get_deps_dict, md5.new()) | |
220 | ||
221 | ## alias | |
222 | compute_md5_v2 = compute_md5 | |
223 | ||
224 | def compute_full_text(get_deps_dict): | |
225 | """ | |
226 | Compute full text of message/service, including text of embedded | |
227 | types. The text of the main msg/srv is listed first. Embedded | |
228 | msg/srv files are denoted first by an 80-character '=' separator, | |
229 | followed by a type declaration line,'MSG: pkg/type', followed by | |
230 | the text of the embedded type. | |
231 | ||
232 | @param get_deps_dict dict: dictionary returned by get_dependencies call | |
233 | @type get_deps_dict: dict | |
234 | @return: concatenated text for msg/srv file and embedded msg/srv types. | |
235 | @rtype: str | |
236 | """ | |
237 | buff = StringIO() | |
238 | sep = '='*80+'\n' | |
239 | ||
240 | # write the text of the top-level type | |
241 | buff.write(get_deps_dict['spec'].text) | |
242 | buff.write('\n') | |
243 | # append the text of the dependencies (embedded types) | |
244 | for d in get_deps_dict['uniquedeps']: | |
245 | buff.write(sep) | |
246 | buff.write("MSG: %s\n"%d) | |
247 | buff.write(roslib.msgs.get_registered(d).text) | |
248 | buff.write('\n') | |
249 | # #1168: remove the trailing \n separator that is added by the concatenation logic | |
250 | return buff.getvalue()[:-1] | |
251 | ||
252 | def get_file_dependencies(f, stdout=sys.stdout, stderr=sys.stderr): | |
253 | """ | |
254 | Compute dependencies of the specified message/service file | |
255 | @param f: message or service file to get dependencies for | |
256 | @type f: str | |
257 | @param stdout pipe: stdout pipe | |
258 | @type stdout: file | |
259 | @param stderr pipe: stderr pipe | |
260 | @type stderr: file | |
261 | @return: 'files': list of files that \a file depends on, | |
262 | 'deps': list of dependencies by type, 'spec': Msgs/Srvs | |
263 | instance. | |
264 | @rtype: dict | |
265 | """ | |
266 | package = rospkg.get_package_name(f) | |
267 | spec = None | |
268 | if f.endswith(roslib.msgs.EXT): | |
269 | _, spec = roslib.msgs.load_from_file(f) | |
270 | elif f.endswith(roslib.srvs.EXT): | |
271 | _, spec = roslib.srvs.load_from_file(f) | |
272 | else: | |
273 | raise Exception("[%s] does not appear to be a message or service"%spec) | |
274 | return get_dependencies(spec, package, stdout, stderr) | |
275 | ||
276 | def get_dependencies(spec, package, compute_files=True, stdout=sys.stdout, stderr=sys.stderr): | |
277 | """ | |
278 | Compute dependencies of the specified Msgs/Srvs | |
279 | @param spec: message or service instance | |
280 | @type spec: L{roslib.msgs.MsgSpec}/L{roslib.srvs.SrvSpec} | |
281 | @param package: package name | |
282 | @type package: str | |
283 | @param stdout: (optional) stdout pipe | |
284 | @type stdout: file | |
285 | @param stderr: (optional) stderr pipe | |
286 | @type stderr: file | |
287 | @param compute_files: (optional, default=True) compute file | |
288 | dependencies of message ('files' key in return value) | |
289 | @type compute_files: bool | |
290 | @return: dict: | |
291 | * 'files': list of files that \a file depends on | |
292 | * 'deps': list of dependencies by type | |
293 | * 'spec': Msgs/Srvs instance. | |
294 | * 'uniquedeps': list of dependencies with duplicates removed, | |
295 | * 'package': package that dependencies were generated relative to. | |
296 | @rtype: dict | |
297 | """ | |
298 | ||
299 | # #518: as a performance optimization, we're going to manually control the loading | |
300 | # of msgs instead of doing package-wide loads. | |
301 | ||
302 | #we're going to manipulate internal apis of msgs, so have to | |
303 | #manually init | |
304 | roslib.msgs._init() | |
305 | ||
306 | deps = [] | |
307 | try: | |
308 | rospack = rospkg.RosPack() | |
309 | if isinstance(spec, roslib.msgs.MsgSpec): | |
310 | _add_msgs_depends(rospack, spec, deps, package) | |
311 | elif isinstance(spec, roslib.srvs.SrvSpec): | |
312 | _add_msgs_depends(rospack, spec.request, deps, package) | |
313 | _add_msgs_depends(rospack, spec.response, deps, package) | |
314 | else: | |
315 | raise MsgSpecException("spec does not appear to be a message or service") | |
316 | except KeyError, e: | |
317 | raise MsgSpecException("Cannot load type %s. Perhaps the package is missing a dependency."%(str(e))) | |
318 | ||
319 | # convert from type names to file names | |
320 | ||
321 | if compute_files: | |
322 | files = {} | |
323 | for d in set(deps): | |
324 | d_pkg, t = roslib.names.package_resource_name(d) | |
325 | d_pkg = d_pkg or package # convert '' -> local package | |
326 | files[d] = roslib.msgs.msg_file(d_pkg, t) | |
327 | else: | |
328 | files = None | |
329 | ||
330 | # create unique dependency list | |
331 | uniquedeps = [] | |
332 | for d in deps: | |
333 | if not d in uniquedeps: | |
334 | uniquedeps.append(d) | |
335 | ||
336 | if compute_files: | |
337 | return { 'files': files, 'deps': deps, 'spec': spec, 'package': package, 'uniquedeps': uniquedeps } | |
338 | else: | |
339 | return { 'deps': deps, 'spec': spec, 'package': package, 'uniquedeps': uniquedeps } | |
340 | ||
341 | ||
342 |
0 | #! /usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | ||
33 | """ | |
34 | Python path loader for python scripts and applications. Paths are | |
35 | derived from dependency structure declared in ROS manifest files. | |
36 | """ | |
37 | ||
38 | import os | |
39 | import sys | |
40 | ||
41 | import rospkg | |
42 | ||
43 | # bootstrapped keeps track of which packages we've loaded so we don't | |
44 | # update the path multiple times | |
45 | _bootstrapped = [] | |
46 | # _rospack is our cache of ROS package data | |
47 | _rospack = rospkg.RosPack() | |
48 | ||
49 | def get_depends(package, rospack): | |
50 | vals = rospack.get_depends(package, implicit=True) | |
51 | return [v for v in vals if not rospack.get_manifest(v).is_catkin] | |
52 | ||
53 | def load_manifest(package_name, bootstrap_version="0.7"): | |
54 | """ | |
55 | Update the Python sys.path with package's dependencies | |
56 | ||
57 | :param package_name: name of the package that load_manifest() is being called from, ``str`` | |
58 | """ | |
59 | if package_name in _bootstrapped: | |
60 | return | |
61 | sys.path = _generate_python_path(package_name, _rospack) + sys.path | |
62 | ||
63 | def _append_package_paths(manifest_, paths, pkg_dir): | |
64 | """ | |
65 | Added paths for package to paths | |
66 | :param manifest_: package manifest, ``Manifest`` | |
67 | :param pkg_dir: package's filesystem directory path, ``str`` | |
68 | :param paths: list of paths, ``[str]`` | |
69 | """ | |
70 | exports = manifest_.get_export('python','path') | |
71 | if exports: | |
72 | for export in exports: | |
73 | if ':' in export: | |
74 | export = export.split(':') | |
75 | else: | |
76 | export = [export] | |
77 | for e in export: | |
78 | paths.append(e.replace('${prefix}', pkg_dir)) | |
79 | else: | |
80 | dirs = [os.path.join(pkg_dir, d) for d in ['src', 'lib']] | |
81 | paths.extend([d for d in dirs if os.path.isdir(d)]) | |
82 | ||
83 | def _generate_python_path(pkg, rospack): | |
84 | """ | |
85 | Recursive subroutine for building dependency list and python path | |
86 | :raises: :exc:`rospkg.ResourceNotFound` If an error occurs while attempting to load package or dependencies | |
87 | """ | |
88 | if pkg in _bootstrapped: | |
89 | return [] | |
90 | ||
91 | # short-circuit if this is a catkin-ized package | |
92 | m = rospack.get_manifest(pkg) | |
93 | if m.is_catkin: | |
94 | _bootstrapped.append(pkg) | |
95 | return [] | |
96 | ||
97 | packages = get_depends(pkg, rospack) | |
98 | packages.append(pkg) | |
99 | ||
100 | paths = [] | |
101 | try: | |
102 | for p in packages: | |
103 | m = rospack.get_manifest(p) | |
104 | d = rospack.get_path(p) | |
105 | _append_package_paths(m, paths, d) | |
106 | _bootstrapped.append(p) | |
107 | except: | |
108 | if pkg in _bootstrapped: | |
109 | _bootstrapped.remove(pkg) | |
110 | raise | |
111 | return paths |
0 | #! /usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | # $Author$ | |
35 | ||
36 | """ | |
37 | Warning: do not use this library. It is unstable and most of the routines | |
38 | here have been superceded by other libraries (e.g. rospkg). These | |
39 | routines will likely be *deleted* in future releases. | |
40 | """ | |
41 | ||
42 | import sys | |
43 | import os | |
44 | import getopt | |
45 | ||
46 | import roslib.packages | |
47 | ||
48 | MANIFEST_FILE = 'manifest.xml' | |
49 | ||
50 | import roslib.manifestlib | |
51 | # re-export symbols for backwards compatibility | |
52 | from roslib.manifestlib import ManifestException, Depend, Export, ROSDep, VersionControl | |
53 | ||
54 | class Manifest(roslib.manifestlib._Manifest): | |
55 | """ | |
56 | Object representation of a ROS manifest file | |
57 | """ | |
58 | __slots__ = [] | |
59 | def __init__(self): | |
60 | """ | |
61 | Initialize new empty manifest. | |
62 | """ | |
63 | super(Manifest, self).__init__('package') | |
64 | ||
65 | def get_export(self, tag, attr): | |
66 | """ | |
67 | @return: exports that match the specified tag and attribute, e.g. 'python', 'path' | |
68 | @rtype: [L{Export}] | |
69 | """ | |
70 | return [e.get(attr) for e in self.exports if e.tag == tag if e.get(attr) is not None] | |
71 | ||
72 | def _manifest_file_by_dir(package_dir, required=True, env=None): | |
73 | """ | |
74 | @param package_dir: path to package directory | |
75 | @type package_dir: str | |
76 | @param env: environment dictionary | |
77 | @type env: dict | |
78 | @param required: require that the directory exist | |
79 | @type required: bool | |
80 | @return: path to manifest file of package | |
81 | @rtype: str | |
82 | @raise InvalidROSPkgException: if required is True and manifest file cannot be located | |
83 | """ | |
84 | if env is None: | |
85 | env = os.environ | |
86 | try: | |
87 | p = os.path.join(package_dir, MANIFEST_FILE) | |
88 | if not required and not os.path.exists(p): | |
89 | return p | |
90 | if not os.path.isfile(p): | |
91 | raise roslib.packages.InvalidROSPkgException(""" | |
92 | Package '%(package_dir)s' is improperly configured: no manifest file is present. | |
93 | """%locals()) | |
94 | return p | |
95 | except roslib.packages.InvalidROSPkgException as e: | |
96 | if required: | |
97 | raise | |
98 | ||
99 | def manifest_file(package, required=True, env=None): | |
100 | """ | |
101 | @param package str: package name | |
102 | @type package: str | |
103 | @param env: override os.environ dictionary | |
104 | @type env: dict | |
105 | @param required: require that the directory exist | |
106 | @type required: bool | |
107 | @return: path to manifest file of package | |
108 | @rtype: str | |
109 | @raise InvalidROSPkgException: if required is True and manifest file cannot be located | |
110 | """ | |
111 | # ros_root needs to be determined from the environment or else | |
112 | # everything breaks when trying to launch nodes via ssh where the | |
113 | # path isn't setup correctly. | |
114 | if env is None: | |
115 | env = os.environ | |
116 | d = roslib.packages.get_pkg_dir(package, required, ros_root=env['ROS_ROOT']) | |
117 | return _manifest_file_by_dir(d, required=required, env=env) | |
118 | ||
119 | def load_manifest(package): | |
120 | """ | |
121 | Load manifest for specified package. | |
122 | @param pacakge: package name | |
123 | @type package: str | |
124 | @return: Manifest instance | |
125 | @rtype: L{Manifest} | |
126 | @raise InvalidROSPkgException: if package is unknown | |
127 | """ | |
128 | return parse_file(manifest_file(package)) | |
129 | ||
130 | def parse_file(file): | |
131 | """ | |
132 | Parse manifest.xml file | |
133 | @param file: manifest.xml file path | |
134 | @type file: str | |
135 | @return: Manifest instance | |
136 | @rtype: L{Manifest} | |
137 | """ | |
138 | return roslib.manifestlib.parse_file(Manifest(), file) | |
139 | ||
140 | def parse(string, filename='string'): | |
141 | """ | |
142 | Parse manifest.xml string contents | |
143 | @param string: manifest.xml contents | |
144 | @type string: str | |
145 | @return: Manifest instance | |
146 | @rtype: L{Manifest} | |
147 | """ | |
148 | v = roslib.manifestlib.parse(Manifest(), string, filename) | |
149 | if v.version: | |
150 | raise ManifestException("<version> tag is not valid in a package manifest.xml file") | |
151 | return v |
0 | #! /usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | # $Author$ | |
35 | ||
36 | """ | |
37 | Internal library for processing 'manifest' files, i.e. manifest.xml and stack.xml. | |
38 | For external code apis, see L{roslib.manifest} and L{roslib.stack_manifest}. | |
39 | """ | |
40 | ||
41 | import sys | |
42 | import os | |
43 | import xml.dom | |
44 | import xml.dom.minidom as dom | |
45 | ||
46 | import roslib.exceptions | |
47 | ||
48 | # stack.xml and manifest.xml have the same internal tags right now | |
49 | REQUIRED = ['author', 'license'] | |
50 | ALLOWXHTML = ['description'] | |
51 | OPTIONAL = ['logo', 'url', 'brief', 'description', 'status', | |
52 | 'notes', 'depend', 'rosdep', 'export', 'review', | |
53 | 'versioncontrol', 'platform', 'version', 'rosbuild2', | |
54 | 'catkin'] | |
55 | VALID = REQUIRED + OPTIONAL | |
56 | ||
57 | class ManifestException(roslib.exceptions.ROSLibException): pass | |
58 | ||
59 | def get_nodes_by_name(n, name): | |
60 | return [t for t in n.childNodes if t.nodeType == t.ELEMENT_NODE and t.tagName == name] | |
61 | ||
62 | def check_optional(name, allowXHTML=False, merge_multiple=False): | |
63 | """ | |
64 | Validator for optional elements. | |
65 | @raise ManifestException: if validation fails | |
66 | """ | |
67 | def check(n, filename): | |
68 | n = get_nodes_by_name(n, name) | |
69 | if len(n) > 1 and not merge_multiple: | |
70 | raise ManifestException("Invalid manifest file: must have a single '%s' element"%name) | |
71 | if n: | |
72 | values = [] | |
73 | for child in n: | |
74 | if allowXHTML: | |
75 | values.append(''.join([x.toxml() for x in child.childNodes])) | |
76 | else: | |
77 | values.append(_get_text(child.childNodes).strip()) | |
78 | return ', '.join(values) | |
79 | return check | |
80 | ||
81 | def check_required(name, allowXHTML=False, merge_multiple=False): | |
82 | """ | |
83 | Validator for required elements. | |
84 | @raise ManifestException: if validation fails | |
85 | """ | |
86 | def check(n, filename): | |
87 | n = get_nodes_by_name(n, name) | |
88 | if not n: | |
89 | #print >> sys.stderr, "Invalid manifest file[%s]: missing required '%s' element"%(filename, name) | |
90 | return '' | |
91 | if len(n) != 1 and not merge_multiple: | |
92 | raise ManifestException("Invalid manifest file: must have only one '%s' element"%name) | |
93 | values = [] | |
94 | for child in n: | |
95 | if allowXHTML: | |
96 | values.append(''.join([x.toxml() for x in child.childNodes])) | |
97 | else: | |
98 | values.append(_get_text(child.childNodes).strip()) | |
99 | return ', '.join(values) | |
100 | return check | |
101 | ||
102 | def check_platform(name): | |
103 | """ | |
104 | Validator for manifest platform. | |
105 | @raise ManifestException: if validation fails | |
106 | """ | |
107 | def check(n, filename): | |
108 | platforms = get_nodes_by_name(n, name) | |
109 | try: | |
110 | vals = [(p.attributes['os'].value, p.attributes['version'].value, p.getAttribute('notes')) for p in platforms] | |
111 | except KeyError as e: | |
112 | raise ManifestException("<platform> tag is missing required '%s' attribute"%str(e)) | |
113 | return [Platform(*v) for v in vals] | |
114 | return check | |
115 | ||
116 | def check_depends(name): | |
117 | """ | |
118 | Validator for manifest depends. | |
119 | @raise ManifestException: if validation fails | |
120 | """ | |
121 | def check(n, filename): | |
122 | nodes = get_nodes_by_name(n, name) | |
123 | # TDS 20110419: this is a hack. | |
124 | # rosbuild2 has a <depend thirdparty="depname"/> tag, | |
125 | # which is confusing this subroutine with | |
126 | # KeyError: 'package' | |
127 | # for now, explicitly don't consider thirdparty depends | |
128 | depends = [e.attributes for e in nodes if 'thirdparty' not in e.attributes.keys()] | |
129 | try: | |
130 | packages = [d['package'].value for d in depends] | |
131 | except KeyError: | |
132 | raise ManifestException("Invalid manifest file: depends is missing 'package' attribute") | |
133 | ||
134 | return [Depend(p) for p in packages] | |
135 | return check | |
136 | ||
137 | def check_stack_depends(name): | |
138 | """ | |
139 | Validator for stack depends. | |
140 | @raise ManifestException: if validation fails | |
141 | """ | |
142 | def check(n, filename): | |
143 | nodes = get_nodes_by_name(n, name) | |
144 | depends = [e.attributes for e in nodes] | |
145 | packages = [d['stack'].value for d in depends] | |
146 | return [StackDepend(p) for p in packages] | |
147 | return check | |
148 | ||
149 | def check_rosdeps(name): | |
150 | """ | |
151 | Validator for stack rosdeps. | |
152 | @raise ManifestException: if validation fails | |
153 | """ | |
154 | def check(n, filename): | |
155 | nodes = get_nodes_by_name(n, name) | |
156 | rosdeps = [e.attributes for e in nodes] | |
157 | names = [d['name'].value for d in rosdeps] | |
158 | return [ROSDep(n) for n in names] | |
159 | return check | |
160 | ||
161 | def _attrs(node): | |
162 | attrs = {} | |
163 | for k in node.attributes.keys(): | |
164 | attrs[k] = node.attributes.get(k).value | |
165 | return attrs | |
166 | ||
167 | def check_exports(name): | |
168 | def check(n, filename): | |
169 | ret_val = [] | |
170 | for e in get_nodes_by_name(n, name): | |
171 | elements = [c for c in e.childNodes if c.nodeType == c.ELEMENT_NODE] | |
172 | ret_val.extend([Export(t.tagName, _attrs(t), _get_text(t.childNodes)) for t in elements]) | |
173 | return ret_val | |
174 | return check | |
175 | ||
176 | def check_versioncontrol(name): | |
177 | def check(n, filename): | |
178 | e = get_nodes_by_name(n, name) | |
179 | if not e: | |
180 | return None | |
181 | # note: 'url' isn't actually required, but as we only support type=svn it implicitly is for now | |
182 | return VersionControl(e[0].attributes['type'].value, e[0].attributes['url'].value) | |
183 | return check | |
184 | ||
185 | def check(name, merge_multiple=False): | |
186 | if name == 'depend': | |
187 | return check_depends('depend') | |
188 | elif name == 'export': | |
189 | return check_exports('export') | |
190 | elif name == 'versioncontrol': | |
191 | return check_versioncontrol('versioncontrol') | |
192 | elif name == 'rosdep': | |
193 | return check_rosdeps('rosdep') | |
194 | elif name == 'platform': | |
195 | return check_platform('platform') | |
196 | elif name in REQUIRED: | |
197 | if name in ALLOWXHTML: | |
198 | return check_required(name, True, merge_multiple) | |
199 | return check_required(name, merge_multiple=merge_multiple) | |
200 | elif name in OPTIONAL: | |
201 | if name in ALLOWXHTML: | |
202 | return check_optional(name, True, merge_multiple) | |
203 | return check_optional(name, merge_multiple=merge_multiple) | |
204 | ||
205 | class Export(object): | |
206 | """ | |
207 | Manifest 'export' tag | |
208 | """ | |
209 | ||
210 | def __init__(self, tag, attrs, str): | |
211 | """ | |
212 | Create new export instance. | |
213 | @param tag: name of the XML tag | |
214 | @type tag: str | |
215 | @param attrs: dictionary of XML attributes for this export tag | |
216 | @type attrs: dict | |
217 | @param str: string value contained by tag, if any | |
218 | @type str: str | |
219 | """ | |
220 | self.tag = tag | |
221 | self.attrs = attrs | |
222 | self.str = str | |
223 | ||
224 | def get(self, attr): | |
225 | """ | |
226 | @return: value of attribute or None if attribute not set | |
227 | @rtype: str | |
228 | """ | |
229 | return self.attrs.get(attr, None) | |
230 | def xml(self): | |
231 | """ | |
232 | @return: export instance represented as manifest XML | |
233 | @rtype: str | |
234 | """ | |
235 | attrs = ' '.join([' %s="%s"'%(k,v) for k,v in self.attrs.items()]) #py3k | |
236 | if self.str: | |
237 | return '<%s%s>%s</%s>'%(self.tag, attrs, self.str, self.tag) | |
238 | else: | |
239 | return '<%s%s />'%(self.tag, attrs) | |
240 | ||
241 | class Platform(object): | |
242 | """ | |
243 | Manifest 'platform' tag | |
244 | """ | |
245 | __slots__ = ['os', 'version', 'notes'] | |
246 | ||
247 | def __init__(self, os, version, notes=None): | |
248 | """ | |
249 | Create new depend instance. | |
250 | @param os: OS name. must be non-empty | |
251 | @type os: str | |
252 | @param version: OS version. must be non-empty | |
253 | @type version: str | |
254 | @param notes: (optional) notes about platform support | |
255 | @type notes: str | |
256 | """ | |
257 | if not os: | |
258 | raise ValueError("bad 'os' attribute") | |
259 | if not version: | |
260 | raise ValueError("bad 'version' attribute") | |
261 | self.os = os | |
262 | self.version = version | |
263 | self.notes = notes | |
264 | ||
265 | def __str__(self): | |
266 | return "%s %s"%(self.os, self.version) | |
267 | def __repr__(self): | |
268 | return "%s %s"%(self.os, self.version) | |
269 | def __eq__(self, obj): | |
270 | """ | |
271 | Override equality test. notes *are* considered in the equality test. | |
272 | """ | |
273 | if not isinstance(obj, Platform): | |
274 | return False | |
275 | return self.os == obj.os and self.version == obj.version and self.notes == obj.notes | |
276 | def xml(self): | |
277 | """ | |
278 | @return: instance represented as manifest XML | |
279 | @rtype: str | |
280 | """ | |
281 | if self.notes is not None: | |
282 | return '<platform os="%s" version="%s" notes="%s"/>'%(self.os, self.version, self.notes) | |
283 | else: | |
284 | return '<platform os="%s" version="%s"/>'%(self.os, self.version) | |
285 | ||
286 | class Depend(object): | |
287 | """ | |
288 | Manifest 'depend' tag | |
289 | """ | |
290 | __slots__ = ['package'] | |
291 | ||
292 | def __init__(self, package): | |
293 | """ | |
294 | Create new depend instance. | |
295 | @param package: package name. must be non-empty | |
296 | @type package: str | |
297 | """ | |
298 | if not package: | |
299 | raise ValueError("bad 'package' attribute") | |
300 | self.package = package | |
301 | def __str__(self): | |
302 | return self.package | |
303 | def __repr__(self): | |
304 | return self.package | |
305 | def __eq__(self, obj): | |
306 | if not isinstance(obj, Depend): | |
307 | return False | |
308 | return self.package == obj.package | |
309 | def xml(self): | |
310 | """ | |
311 | @return: depend instance represented as manifest XML | |
312 | @rtype: str | |
313 | """ | |
314 | return '<depend package="%s" />'%self.package | |
315 | ||
316 | class StackDepend(object): | |
317 | """ | |
318 | Stack Manifest 'depend' tag | |
319 | """ | |
320 | __slots__ = ['stack', 'annotation'] | |
321 | ||
322 | def __init__(self, stack): | |
323 | """ | |
324 | @param stack: stack name. must be non-empty | |
325 | @type stack: str | |
326 | """ | |
327 | if not stack: | |
328 | raise ValueError("bad 'stack' attribute") | |
329 | self.stack = stack | |
330 | self.annotation = None | |
331 | ||
332 | def __str__(self): | |
333 | return self.stack | |
334 | def __repr__(self): | |
335 | return self.stack | |
336 | def __eq__(self, obj): | |
337 | if not isinstance(obj, StackDepend): | |
338 | return False | |
339 | return self.stack == obj.stack | |
340 | def xml(self): | |
341 | """ | |
342 | @return: stack depend instance represented as stack manifest XML | |
343 | @rtype: str | |
344 | """ | |
345 | if self.annotation: | |
346 | return '<depend stack="%s" /> <!-- %s -->'%(self.stack, self.annotation) | |
347 | else: | |
348 | return '<depend stack="%s" />'%self.stack | |
349 | ||
350 | class ROSDep(object): | |
351 | """ | |
352 | Manifest 'rosdep' tag | |
353 | """ | |
354 | __slots__ = ['name',] | |
355 | ||
356 | def __init__(self, name): | |
357 | """ | |
358 | Create new rosdep instance. | |
359 | @param name: dependency name. Must be non-empty. | |
360 | @type name: str | |
361 | """ | |
362 | if not name: | |
363 | raise ValueError("bad 'name' attribute") | |
364 | self.name = name | |
365 | def xml(self): | |
366 | """ | |
367 | @return: rosdep instance represented as manifest XML | |
368 | @rtype: str | |
369 | """ | |
370 | return '<rosdep name="%s" />'%self.name | |
371 | ||
372 | class VersionControl(object): | |
373 | """ | |
374 | Manifest 'versioncontrol' tag | |
375 | """ | |
376 | __slots__ = ['type', 'url'] | |
377 | ||
378 | def __init__(self, type_, url): | |
379 | """ | |
380 | @param type_: version control type (e.g. 'svn'). must be non empty | |
381 | @type type_: str | |
382 | @param url: URL associated with version control. must be non empty | |
383 | @type url: str | |
384 | """ | |
385 | if not type_ or not isinstance(type_, basestring): | |
386 | raise ValueError("bad 'type' attribute") | |
387 | if not url is None and not isinstance(url, basestring): | |
388 | raise ValueError("bad 'url' attribute") | |
389 | self.type = type_ | |
390 | self.url = url | |
391 | def xml(self): | |
392 | """ | |
393 | @return: versioncontrol instance represented as manifest XML | |
394 | @rtype: str | |
395 | """ | |
396 | if self.url: | |
397 | return '<versioncontrol type="%s" url="%s" />'%(self.type, self.url) | |
398 | else: | |
399 | return '<versioncontrol type="%s" />'%self.type | |
400 | ||
401 | class _Manifest(object): | |
402 | """ | |
403 | Object representation of a ROS manifest file | |
404 | """ | |
405 | __slots__ = ['description', 'brief', \ | |
406 | 'author', 'license', 'license_url', 'url', \ | |
407 | 'depends', 'rosdeps','platforms',\ | |
408 | 'logo', 'exports', 'version',\ | |
409 | 'versioncontrol', 'status', 'notes',\ | |
410 | 'unknown_tags',\ | |
411 | '_type'] | |
412 | def __init__(self, _type='package'): | |
413 | self.description = self.brief = self.author = \ | |
414 | self.license = self.license_url = \ | |
415 | self.url = self.logo = self.status = \ | |
416 | self.version = self.notes = '' | |
417 | self.depends = [] | |
418 | self.rosdeps = [] | |
419 | self.exports = [] | |
420 | self.platforms = [] | |
421 | self._type = _type | |
422 | ||
423 | # store unrecognized tags during parsing | |
424 | self.unknown_tags = [] | |
425 | ||
426 | def __str__(self): | |
427 | return self.xml() | |
428 | def get_export(self, tag, attr): | |
429 | """ | |
430 | @return: exports that match the specified tag and attribute, e.g. 'python', 'path' | |
431 | @rtype: [L{Export}] | |
432 | """ | |
433 | return [e.get(attr) for e in self.exports if e.tag == tag if e.get(attr) is not None] | |
434 | def xml(self): | |
435 | """ | |
436 | @return: Manifest instance as ROS XML manifest | |
437 | @rtype: str | |
438 | """ | |
439 | if not self.brief: | |
440 | desc = " <description>%s</description>"%self.description | |
441 | else: | |
442 | desc = ' <description brief="%s">%s</description>'%(self.brief, self.description) | |
443 | author = " <author>%s</author>"%self.author | |
444 | if self.license_url: | |
445 | license = ' <license url="%s">%s</license>'%(self.license_url, self.license) | |
446 | else: | |
447 | license = " <license>%s</license>"%self.license | |
448 | versioncontrol = url = logo = exports = version = "" | |
449 | if self.url: | |
450 | url = " <url>%s</url>"%self.url | |
451 | if self.version: | |
452 | version = " <version>%s</version>"%self.version | |
453 | if self.logo: | |
454 | logo = " <logo>%s</logo>"%self.logo | |
455 | depends = '\n'.join([" %s"%d.xml() for d in self.depends]) | |
456 | rosdeps = '\n'.join([" %s"%rd.xml() for rd in self.rosdeps]) | |
457 | platforms = '\n'.join([" %s"%p.xml() for p in self.platforms]) | |
458 | if self.exports: | |
459 | exports = ' <export>\n' + '\n'.join([" %s"%e.xml() for e in self.exports]) + ' </export>' | |
460 | if self.versioncontrol: | |
461 | versioncontrol = " %s"%self.versioncontrol.xml() | |
462 | if self.status or self.notes: | |
463 | review = ' <review status="%s" notes="%s" />'%(self.status, self.notes) | |
464 | ||
465 | ||
466 | fields = filter(lambda x: x, | |
467 | [desc, author, license, review, url, logo, depends, | |
468 | rosdeps, platforms, exports, versioncontrol, version]) | |
469 | return "<%s>\n"%self._type + "\n".join(fields) + "\n</%s>"%self._type | |
470 | ||
471 | def _get_text(nodes): | |
472 | """ | |
473 | DOM utility routine for getting contents of text nodes | |
474 | """ | |
475 | return "".join([n.data for n in nodes if n.nodeType == n.TEXT_NODE]) | |
476 | ||
477 | def parse_file(m, file): | |
478 | """ | |
479 | Parse manifest file (package, stack) | |
480 | @param m: field to populate | |
481 | @type m: L{_Manifest} | |
482 | @param file: manifest.xml file path | |
483 | @type file: str | |
484 | @return: return m, populated with parsed fields | |
485 | @rtype: L{_Manifest} | |
486 | """ | |
487 | if not file: | |
488 | raise ValueError("Missing manifest file argument") | |
489 | if not os.path.isfile(file): | |
490 | raise ValueError("Invalid/non-existent manifest file: %s"%file) | |
491 | with open(file, 'r') as f: | |
492 | text = f.read() | |
493 | try: | |
494 | return parse(m, text, file) | |
495 | except ManifestException as e: | |
496 | raise ManifestException("Invalid manifest file [%s]: %s"%(os.path.abspath(file), e)) | |
497 | ||
498 | def parse(m, string, filename='string'): | |
499 | """ | |
500 | Parse manifest.xml string contents | |
501 | @param string: manifest.xml contents | |
502 | @type string: str | |
503 | @param m: field to populate | |
504 | @type m: L{_Manifest} | |
505 | @return: return m, populated with parsed fields | |
506 | @rtype: L{_Manifest} | |
507 | """ | |
508 | try: | |
509 | d = dom.parseString(string) | |
510 | except Exception as e: | |
511 | raise ManifestException("invalid XML: %s"%e) | |
512 | ||
513 | p = get_nodes_by_name(d, m._type) | |
514 | if len(p) != 1: | |
515 | raise ManifestException("manifest must have a single '%s' element"%m._type) | |
516 | p = p[0] | |
517 | m.description = check('description')(p, filename) | |
518 | m.brief = '' | |
519 | try: | |
520 | tag = get_nodes_by_name(p, 'description')[0] | |
521 | m.brief = tag.getAttribute('brief') or '' | |
522 | except: | |
523 | # means that 'description' tag is missing | |
524 | pass | |
525 | #TODO: figure out how to multiplex | |
526 | if m._type == 'package': | |
527 | m.depends = check_depends('depend')(p, filename) | |
528 | elif m._type == 'stack': | |
529 | m.depends = check_stack_depends('depend')(p, filename) | |
530 | elif m._type == 'app': | |
531 | # not implemented yet | |
532 | pass | |
533 | m.rosdeps = check('rosdep')(p, filename) | |
534 | m.platforms = check('platform')(p, filename) | |
535 | m.exports = check('export')(p, filename) | |
536 | m.versioncontrol = check('versioncontrol')(p,filename) | |
537 | m.license = check('license')(p, filename) | |
538 | m.license_url = '' | |
539 | try: | |
540 | tag = get_nodes_by_name(p, 'license')[0] | |
541 | m.license_url = tag.getAttribute('url') or '' | |
542 | except: | |
543 | pass #manifest is missing required 'license' tag | |
544 | ||
545 | m.status='unreviewed' | |
546 | try: | |
547 | tag = get_nodes_by_name(p, 'review')[0] | |
548 | m.status=tag.getAttribute('status') or '' | |
549 | except: | |
550 | pass #manifest is missing optional 'review status' tag | |
551 | ||
552 | m.notes='' | |
553 | try: | |
554 | tag = get_nodes_by_name(p, 'review')[0] | |
555 | m.notes=tag.getAttribute('notes') or '' | |
556 | except: | |
557 | pass #manifest is missing optional 'review notes' tag | |
558 | ||
559 | m.author = check('author', True)(p, filename) | |
560 | m.url = check('url')(p, filename) | |
561 | m.version = check('version')(p, filename) | |
562 | m.logo = check('logo')(p, filename) | |
563 | ||
564 | # do some validation on what we just parsed | |
565 | if m._type == 'stack': | |
566 | if m.exports: | |
567 | raise ManifestException("stack manifests are not allowed to have exports") | |
568 | if m.rosdeps: | |
569 | raise ManifestException("stack manifests are not allowed to have rosdeps") | |
570 | ||
571 | # store unrecognized tags | |
572 | m.unknown_tags = [e for e in p.childNodes if e.nodeType == e.ELEMENT_NODE and e.tagName not in VALID] | |
573 | return m |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | """ | |
35 | Support library for Python autogenerated message files. This defines | |
36 | the Message base class used by genmsg_py as well as support | |
37 | libraries for type checking and retrieving message classes by type | |
38 | name. | |
39 | """ | |
40 | ||
41 | import os | |
42 | import sys | |
43 | import rospkg | |
44 | import roslib | |
45 | ||
46 | import genmsg | |
47 | import genpy.message #for wrapping get_message_class, get_service_class | |
48 | ||
49 | # forward a bunch of old symbols from genpy for backwards compat | |
50 | from genpy import Message, DeserializationError, SerializationError, \ | |
51 | Time, Duration, TVal | |
52 | from genpy.message import get_printable_message_args, fill_message_args | |
53 | from genpy.message import check_type, strify_message | |
54 | ||
55 | def _get_message_or_service_class(type_str, message_type, reload_on_error=False): | |
56 | ## parse package and local type name for import | |
57 | package, base_type = genmsg.package_resource_name(message_type) | |
58 | if not package: | |
59 | if base_type == 'Header': | |
60 | package = 'std_msgs' | |
61 | else: | |
62 | raise ValueError("message type is missing package name: %s"%str(message_type)) | |
63 | pypkg = val = None | |
64 | try: | |
65 | # bootstrap our sys.path | |
66 | roslib.launcher.load_manifest(package) | |
67 | # import the package and return the class | |
68 | pypkg = __import__('%s.%s'%(package, type_str)) | |
69 | val = getattr(getattr(pypkg, type_str), base_type) | |
70 | except rospkg.ResourceNotFound: | |
71 | val = None | |
72 | except ImportError: | |
73 | val = None | |
74 | except AttributeError: | |
75 | val = None | |
76 | ||
77 | # this logic is mainly to support rosh, so that a user doesn't | |
78 | # have to exit a shell just because a message wasn't built yet | |
79 | if val is None and reload_on_error: | |
80 | try: | |
81 | if pypkg: | |
82 | reload(pypkg) | |
83 | val = getattr(getattr(pypkg, type_str), base_type) | |
84 | except: | |
85 | val = None | |
86 | return val | |
87 | ||
88 | ## cache for get_message_class | |
89 | _message_class_cache = {} | |
90 | ||
91 | ## cache for get_service_class | |
92 | _service_class_cache = {} | |
93 | ||
94 | def get_message_class(message_type, reload_on_error=False): | |
95 | if message_type in _message_class_cache: | |
96 | return _message_class_cache[message_type] | |
97 | # try w/o bootstrapping | |
98 | cls = genpy.message.get_message_class(message_type, reload_on_error=reload_on_error) | |
99 | if cls is None: | |
100 | # try old loader w/ bootstrapping | |
101 | cls = _get_message_or_service_class('msg', message_type, reload_on_error=reload_on_error) | |
102 | if cls: | |
103 | _message_class_cache[message_type] = cls | |
104 | return cls | |
105 | ||
106 | def get_service_class(service_type, reload_on_error=False): | |
107 | if service_type in _service_class_cache: | |
108 | return _service_class_cache[service_type] | |
109 | cls = genpy.message.get_service_class(service_type, reload_on_error=reload_on_error) | |
110 | # try w/o bootstrapping | |
111 | if cls is None: | |
112 | # try old loader w/ bootstrapping | |
113 | cls = _get_message_or_service_class('srv', service_type, reload_on_error=reload_on_error) | |
114 | if cls: | |
115 | _service_class_cache[service_type] = cls | |
116 | return cls |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | # $Author$ | |
34 | ||
35 | from __future__ import print_function | |
36 | ||
37 | """ | |
38 | Warning: do not use this library. It is unstable and most of the routines | |
39 | here have been superceded by other libraries (e.g. genmsg). These | |
40 | routines will likely be *deleted* in future releases. | |
41 | """ | |
42 | ||
43 | try: | |
44 | from cStringIO import StringIO # Python 2.x | |
45 | except ImportError: | |
46 | from io import StringIO # Python 3.x | |
47 | ||
48 | import os | |
49 | import sys | |
50 | import string | |
51 | ||
52 | import rospkg | |
53 | ||
54 | import roslib.manifest | |
55 | import roslib.packages | |
56 | import roslib.names | |
57 | import roslib.resources | |
58 | ||
59 | VERBOSE = False | |
60 | ||
61 | ## @return: True if msg-related scripts should print verbose output | |
62 | def is_verbose(): | |
63 | return VERBOSE | |
64 | ||
65 | ## set whether msg-related scripts should print verbose output | |
66 | def set_verbose(v): | |
67 | global VERBOSE | |
68 | VERBOSE = v | |
69 | ||
70 | EXT = '.msg' | |
71 | SEP = '/' #e.g. std_msgs/String | |
72 | ## character that designates a constant assignment rather than a field | |
73 | CONSTCHAR = '=' | |
74 | COMMENTCHAR = '#' | |
75 | ||
76 | class MsgSpecException(Exception): pass | |
77 | ||
78 | #TODOXXX: unit test | |
79 | def base_msg_type(type_): | |
80 | """ | |
81 | Compute the base data type, e.g. for arrays, get the underlying array item type | |
82 | @param type_: ROS msg type (e.g. 'std_msgs/String') | |
83 | @type type_: str | |
84 | @return: base type | |
85 | @rtype: str | |
86 | """ | |
87 | if type_ is None: | |
88 | return None | |
89 | if '[' in type_: | |
90 | return type_[:type_.find('[')] | |
91 | return type_ | |
92 | ||
93 | def resolve_type(type_, package_context): | |
94 | """ | |
95 | Resolve type name based on current package context. | |
96 | ||
97 | NOTE: in ROS Diamondback, 'Header' resolves to | |
98 | 'std_msgs/Header'. In previous releases, it resolves to | |
99 | 'roslib/Header' (REP 100). | |
100 | ||
101 | e.g.:: | |
102 | resolve_type('String', 'std_msgs') -> 'std_msgs/String' | |
103 | resolve_type('String[]', 'std_msgs') -> 'std_msgs/String[]' | |
104 | resolve_type('std_msgs/String', 'foo') -> 'std_msgs/String' | |
105 | resolve_type('uint16', 'std_msgs') -> 'uint16' | |
106 | resolve_type('uint16[]', 'std_msgs') -> 'uint16[]' | |
107 | """ | |
108 | bt = base_msg_type(type_) | |
109 | if bt in BUILTIN_TYPES: | |
110 | return type_ | |
111 | elif bt == 'Header': | |
112 | return 'std_msgs/Header' | |
113 | elif SEP in type_: | |
114 | return type_ | |
115 | else: | |
116 | return "%s%s%s"%(package_context, SEP, type_) | |
117 | ||
118 | #NOTE: this assumes that we aren't going to support multi-dimensional | |
119 | ||
120 | def parse_type(type_): | |
121 | """ | |
122 | Parse ROS message field type | |
123 | @param type_: ROS field type | |
124 | @type type_: str | |
125 | @return: base_type, is_array, array_length | |
126 | @rtype: str, bool, int | |
127 | @raise MsgSpecException: if type_ cannot be parsed | |
128 | """ | |
129 | if not type_: | |
130 | raise MsgSpecException("Invalid empty type") | |
131 | if '[' in type_: | |
132 | var_length = type_.endswith('[]') | |
133 | splits = type_.split('[') | |
134 | if len(splits) > 2: | |
135 | raise MsgSpecException("Currently only support 1-dimensional array types: %s"%type_) | |
136 | if var_length: | |
137 | return type_[:-2], True, None | |
138 | else: | |
139 | try: | |
140 | length = int(splits[1][:-1]) | |
141 | return splits[0], True, length | |
142 | except ValueError: | |
143 | raise MsgSpecException("Invalid array dimension: [%s]"%splits[1][:-1]) | |
144 | else: | |
145 | return type_, False, None | |
146 | ||
147 | ################################################################################ | |
148 | # name validation | |
149 | ||
150 | def is_valid_msg_type(x): | |
151 | """ | |
152 | @return: True if the name is a syntatically legal message type name | |
153 | @rtype: bool | |
154 | """ | |
155 | if not x or len(x) != len(x.strip()): | |
156 | return False | |
157 | base = base_msg_type(x) | |
158 | if not roslib.names.is_legal_resource_name(base): | |
159 | return False | |
160 | #parse array indicies | |
161 | x = x[len(base):] | |
162 | state = 0 | |
163 | i = 0 | |
164 | for c in x: | |
165 | if state == 0: | |
166 | if c != '[': | |
167 | return False | |
168 | state = 1 #open | |
169 | elif state == 1: | |
170 | if c == ']': | |
171 | state = 0 #closed | |
172 | else: | |
173 | try: | |
174 | string.atoi(c) | |
175 | except: | |
176 | return False | |
177 | return state == 0 | |
178 | ||
179 | def is_valid_constant_type(x): | |
180 | """ | |
181 | @return: True if the name is a legal constant type. Only simple types are allowed. | |
182 | @rtype: bool | |
183 | """ | |
184 | return x in PRIMITIVE_TYPES | |
185 | ||
186 | def is_valid_msg_field_name(x): | |
187 | """ | |
188 | @return: True if the name is a syntatically legal message field name | |
189 | @rtype: bool | |
190 | """ | |
191 | return roslib.names.is_legal_resource_base_name(x) | |
192 | ||
193 | # msg spec representation ########################################## | |
194 | ||
195 | class Constant(object): | |
196 | """ | |
197 | Container class for holding a Constant declaration | |
198 | """ | |
199 | __slots__ = ['type', 'name', 'val', 'val_text'] | |
200 | ||
201 | def __init__(self, type_, name, val, val_text): | |
202 | """ | |
203 | @param type_: constant type | |
204 | @type type_: str | |
205 | @param name: constant name | |
206 | @type name: str | |
207 | @param val: constant value | |
208 | @type val: str | |
209 | @param val_text: Original text definition of \a val | |
210 | @type val_text: str | |
211 | """ | |
212 | if type is None or name is None or val is None or val_text is None: | |
213 | raise ValueError('Constant must have non-None parameters') | |
214 | self.type = type_ | |
215 | self.name = name.strip() #names are always stripped of whitespace | |
216 | self.val = val | |
217 | self.val_text = val_text | |
218 | ||
219 | def __eq__(self, other): | |
220 | if not isinstance(other, Constant): | |
221 | return False | |
222 | return self.type == other.type and self.name == other.name and self.val == other.val | |
223 | ||
224 | def __repr__(self): | |
225 | return "%s %s=%s"%(self.type, self.name, self.val) | |
226 | ||
227 | def __str__(self): | |
228 | return "%s %s=%s"%(self.type, self.name, self.val) | |
229 | ||
230 | def _strify_spec(spec, buff=None, indent=''): | |
231 | """ | |
232 | Convert spec into a string representation. Helper routine for MsgSpec. | |
233 | @param indent: internal use only | |
234 | @type indent: str | |
235 | @param buff: internal use only | |
236 | @type buff: StringIO | |
237 | @return: string representation of spec | |
238 | @rtype: str | |
239 | """ | |
240 | if buff is None: | |
241 | buff = StringIO() | |
242 | for c in spec.constants: | |
243 | buff.write("%s%s %s=%s\n"%(indent, c.type, c.name, c.val_text)) | |
244 | for type_, name in zip(spec.types, spec.names): | |
245 | buff.write("%s%s %s\n"%(indent, type_, name)) | |
246 | base_type = base_msg_type(type_) | |
247 | if not base_type in BUILTIN_TYPES: | |
248 | subspec = get_registered(base_type) | |
249 | _strify_spec(subspec, buff, indent + ' ') | |
250 | return buff.getvalue() | |
251 | ||
252 | class Field(object): | |
253 | """ | |
254 | Container class for storing information about a single field in a MsgSpec | |
255 | ||
256 | Contains: | |
257 | name | |
258 | type | |
259 | base_type | |
260 | is_array | |
261 | array_len | |
262 | is_builtin | |
263 | is_header | |
264 | """ | |
265 | ||
266 | def __init__(self, name, type): | |
267 | self.name = name | |
268 | self.type = type | |
269 | (self.base_type, self.is_array, self.array_len) = parse_type(type) | |
270 | self.is_header = is_header_type(self.base_type) | |
271 | self.is_builtin = is_builtin(self.base_type) | |
272 | ||
273 | def __repr__(self): | |
274 | return "[%s, %s, %s, %s, %s]"%(self.name, self.type, self.base_type, self.is_array, self.array_len) | |
275 | ||
276 | class MsgSpec(object): | |
277 | """ | |
278 | Container class for storing loaded msg description files. Field | |
279 | types and names are stored in separate lists with 1-to-1 | |
280 | correspondence. MsgSpec can also return an md5 of the source text. | |
281 | """ | |
282 | ||
283 | def __init__(self, types, names, constants, text, full_name = '', short_name = '', package = ''): | |
284 | """ | |
285 | @param types: list of field types, in order of declaration | |
286 | @type types: [str] | |
287 | @param names: list of field names, in order of declaration | |
288 | @type names: [str] | |
289 | @param constants: Constant declarations | |
290 | @type constants: [L{Constant}] | |
291 | @param text: text of declaration | |
292 | @type text: str | |
293 | @raise MsgSpecException: if spec is invalid (e.g. fields with the same name) | |
294 | """ | |
295 | self.types = types | |
296 | if len(set(names)) != len(names): | |
297 | raise MsgSpecException("Duplicate field names in message: %s"%names) | |
298 | self.names = names | |
299 | self.constants = constants | |
300 | assert len(self.types) == len(self.names), "len(%s) != len(%s)"%(self.types, self.names) | |
301 | #Header.msg support | |
302 | if (len(self.types)): | |
303 | self.header_present = self.types[0] == HEADER and self.names[0] == 'header' | |
304 | else: | |
305 | self.header_present = False | |
306 | self.text = text | |
307 | self.full_name = full_name | |
308 | self.short_name = short_name | |
309 | self.package = package | |
310 | self._parsed_fields = [Field(name, type) for (name, type) in zip(self.names, self.types)] | |
311 | ||
312 | def fields(self): | |
313 | """ | |
314 | @return: zip list of types and names (e.g. [('int32', 'x'), ('int32', 'y')] | |
315 | @rtype: [(str,str),] | |
316 | """ | |
317 | return list(zip(self.types, self.names)) #py3k | |
318 | ||
319 | def parsed_fields(self): | |
320 | """ | |
321 | @return: list of Field classes | |
322 | @rtype: [Field,] | |
323 | """ | |
324 | return self._parsed_fields | |
325 | ||
326 | def has_header(self): | |
327 | """ | |
328 | @return: True if msg decription contains a 'Header header' | |
329 | declaration at the beginning | |
330 | @rtype: bool | |
331 | """ | |
332 | return self.header_present | |
333 | def __eq__(self, other): | |
334 | if not other or not isinstance(other, MsgSpec): | |
335 | return False | |
336 | return self.types == other.types and self.names == other.names and \ | |
337 | self.constants == other.constants and self.text == other.text | |
338 | def __ne__(self, other): | |
339 | if not other or not isinstance(other, MsgSpec): | |
340 | return True | |
341 | return not self.__eq__(other) | |
342 | ||
343 | def __repr__(self): | |
344 | if self.constants: | |
345 | return "MsgSpec[%s, %s, %s]"%(repr(self.constants), repr(self.types), repr(self.names)) | |
346 | else: | |
347 | return "MsgSpec[%s, %s]"%(repr(self.types), repr(self.names)) | |
348 | ||
349 | def __str__(self): | |
350 | return _strify_spec(self) | |
351 | ||
352 | # msg spec loading utilities ########################################## | |
353 | ||
354 | def reinit(): | |
355 | """ | |
356 | Reinitialize roslib.msgs. This API is for message generators | |
357 | (e.g. genpy) that need to re-initialize the registration table. | |
358 | """ | |
359 | global _initialized , _loaded_packages | |
360 | # unset the initialized state and unregister everything | |
361 | _initialized = False | |
362 | del _loaded_packages[:] | |
363 | REGISTERED_TYPES.clear() | |
364 | _init() | |
365 | ||
366 | _initialized = False | |
367 | def _init(): | |
368 | #lazy-init | |
369 | global _initialized | |
370 | if _initialized: | |
371 | return | |
372 | ||
373 | fname = '%s%s'%(HEADER, EXT) | |
374 | std_msgs_dir = roslib.packages.get_pkg_dir('std_msgs') | |
375 | if std_msgs_dir is None: | |
376 | raise MsgSpecException("Unable to locate roslib: %s files cannot be loaded"%EXT) | |
377 | ||
378 | header = os.path.join(std_msgs_dir, 'msg', fname) | |
379 | if not os.path.isfile(header): | |
380 | sys.stderr.write("ERROR: cannot locate %s. Expected to find it at '%s'\n"%(fname, header)) | |
381 | return False | |
382 | ||
383 | # register Header under both contexted and de-contexted name | |
384 | _, spec = load_from_file(header, '') | |
385 | register(HEADER, spec) | |
386 | register('std_msgs/'+HEADER, spec) | |
387 | # backwards compat, REP 100 | |
388 | register('roslib/'+HEADER, spec) | |
389 | for k, spec in EXTENDED_BUILTINS.items(): | |
390 | register(k, spec) | |
391 | ||
392 | _initialized = True | |
393 | ||
394 | # .msg file routines ############################################################## | |
395 | ||
396 | def _msg_filter(f): | |
397 | """ | |
398 | Predicate for filtering directory list. matches message files | |
399 | @param f: filename | |
400 | @type f: str | |
401 | """ | |
402 | return os.path.isfile(f) and f.endswith(EXT) | |
403 | ||
404 | # also used by doxymaker | |
405 | def list_msg_types(package, include_depends): | |
406 | """ | |
407 | List all messages in the specified package | |
408 | @param package str: name of package to search | |
409 | @param include_depends bool: if True, will also list messages in package dependencies | |
410 | @return [str]: message type names | |
411 | """ | |
412 | types = roslib.resources.list_package_resources(package, include_depends, 'msg', _msg_filter) | |
413 | return [x[:-len(EXT)] for x in types] | |
414 | ||
415 | def msg_file(package, type_): | |
416 | """ | |
417 | Determine the file system path for the specified .msg | |
418 | resource. .msg resource does not have to exist. | |
419 | ||
420 | @param package: name of package .msg file is in | |
421 | @type package: str | |
422 | @param type_: type name of message, e.g. 'Point2DFloat32' | |
423 | @type type_: str | |
424 | @return: file path of .msg file in specified package | |
425 | @rtype: str | |
426 | """ | |
427 | return roslib.packages.resource_file(package, 'msg', type_+EXT) | |
428 | ||
429 | def get_pkg_msg_specs(package): | |
430 | """ | |
431 | List all messages that a package contains. | |
432 | ||
433 | @param package: package to load messages from | |
434 | @type package: str | |
435 | @return: list of message type names and specs for package, as well as a list | |
436 | of message names that could not be processed. | |
437 | @rtype: [(str, L{MsgSpec}), [str]] | |
438 | """ | |
439 | _init() | |
440 | types = list_msg_types(package, False) | |
441 | specs = [] #no fancy list comprehension as we want to show errors | |
442 | failures = [] | |
443 | for t in types: | |
444 | try: | |
445 | typespec = load_from_file(msg_file(package, t), package) | |
446 | specs.append(typespec) | |
447 | except Exception as e: | |
448 | failures.append(t) | |
449 | print("ERROR: unable to load %s, %s"%(t, e)) | |
450 | return specs, failures | |
451 | ||
452 | def load_package_dependencies(package, load_recursive=False): | |
453 | """ | |
454 | Register all messages that the specified package depends on. | |
455 | ||
456 | @param load_recursive: (optional) if True, load all dependencies, | |
457 | not just direct dependencies. By default, this is false to | |
458 | prevent packages from incorrectly inheriting dependencies. | |
459 | @type load_recursive: bool | |
460 | """ | |
461 | global _loaded_packages | |
462 | _init() | |
463 | if VERBOSE: | |
464 | print("Load dependencies for package", package) | |
465 | ||
466 | if not load_recursive: | |
467 | manifest_file = roslib.manifest.manifest_file(package, True) | |
468 | m = roslib.manifest.parse_file(manifest_file) | |
469 | depends = [d.package for d in m.depends] # #391 | |
470 | else: | |
471 | depends = rospkg.RosPack().get_depends(package, implicit=True) | |
472 | ||
473 | msgs = [] | |
474 | failures = [] | |
475 | for d in depends: | |
476 | if VERBOSE: | |
477 | print("Load dependency", d) | |
478 | #check if already loaded | |
479 | # - we are dependent on manifest.getAll returning first-order dependencies first | |
480 | if d in _loaded_packages or d == package: | |
481 | continue | |
482 | _loaded_packages.append(d) | |
483 | specs, failed = get_pkg_msg_specs(d) | |
484 | msgs.extend(specs) | |
485 | failures.extend(failed) | |
486 | for key, spec in msgs: | |
487 | register(key, spec) | |
488 | ||
489 | def load_package(package): | |
490 | """ | |
491 | Load package into the local registered namespace. All messages found | |
492 | in the package will be registered if they are successfully | |
493 | loaded. This should only be done with one package (i.e. the 'main' | |
494 | package) per Python instance. | |
495 | ||
496 | @param package: package name | |
497 | @type package: str | |
498 | """ | |
499 | global _loaded_packages | |
500 | _init() | |
501 | if VERBOSE: | |
502 | print("Load package", package) | |
503 | ||
504 | #check if already loaded | |
505 | # - we are dependent on manifest.getAll returning first-order dependencies first | |
506 | if package in _loaded_packages: | |
507 | if VERBOSE: | |
508 | print("Package %s is already loaded"%package) | |
509 | return | |
510 | ||
511 | _loaded_packages.append(package) | |
512 | specs, failed = get_pkg_msg_specs(package) | |
513 | if VERBOSE: | |
514 | print("Package contains the following messages: %s"%specs) | |
515 | for key, spec in specs: | |
516 | #register spec under both local and fully-qualified key | |
517 | register(key, spec) | |
518 | register(package + roslib.names.PRN_SEPARATOR + key, spec) | |
519 | ||
520 | def _convert_val(type_, val): | |
521 | """ | |
522 | Convert constant value declaration to python value. Does not do | |
523 | type-checking, so ValueError or other exceptions may be raised. | |
524 | ||
525 | @param type_: ROS field type | |
526 | @type type_: str | |
527 | @param val: string representation of constant | |
528 | @type val: str: | |
529 | @raise ValueError: if unable to convert to python representation | |
530 | @raise MsgSpecException: if value exceeds specified integer width | |
531 | """ | |
532 | if type_ in ['float32','float64']: | |
533 | return float(val) | |
534 | elif type_ in ['string']: | |
535 | return val.strip() #string constants are always stripped | |
536 | elif type_ in ['int8', 'uint8', 'int16','uint16','int32','uint32','int64','uint64', 'char', 'byte']: | |
537 | # bounds checking | |
538 | bits = [('int8', 8), ('uint8', 8), ('int16', 16),('uint16', 16),\ | |
539 | ('int32', 32),('uint32', 32), ('int64', 64),('uint64', 64),\ | |
540 | ('byte', 8), ('char', 8)] | |
541 | b = [b for t, b in bits if t == type_][0] | |
542 | import math | |
543 | if type_[0] == 'u' or type_ == 'char': | |
544 | lower = 0 | |
545 | upper = int(math.pow(2, b)-1) | |
546 | else: | |
547 | upper = int(math.pow(2, b-1)-1) | |
548 | lower = -upper - 1 #two's complement min | |
549 | val = int(val) #python will autocast to long if necessary | |
550 | if val > upper or val < lower: | |
551 | raise MsgSpecException("cannot coerce [%s] to %s (out of bounds)"%(val, type_)) | |
552 | return val | |
553 | elif type_ == 'bool': | |
554 | # TODO: need to nail down constant spec for bool | |
555 | return True if eval(val) else False | |
556 | raise MsgSpecException("invalid constant type: [%s]"%type_) | |
557 | ||
558 | def load_by_type(msgtype, package_context=''): | |
559 | """ | |
560 | Load message specification for specified type | |
561 | ||
562 | @param package_context: package name to use for the type name or | |
563 | '' to use the local (relative) naming convention. | |
564 | @type package_context: str | |
565 | @return: Message type name and message specification | |
566 | @rtype: (str, L{MsgSpec}) | |
567 | """ | |
568 | pkg, basetype = roslib.names.package_resource_name(msgtype) | |
569 | pkg = pkg or package_context # convert '' -> local package | |
570 | try: | |
571 | m_f = msg_file(pkg, basetype) | |
572 | except roslib.packages.InvalidROSPkgException: | |
573 | raise MsgSpecException("Cannot locate message type [%s], package [%s] does not exist"%(msgtype, pkg)) | |
574 | return load_from_file(m_f, pkg) | |
575 | ||
576 | def load_from_string(text, package_context='', full_name='', short_name=''): | |
577 | """ | |
578 | Load message specification from a string. | |
579 | @param text: .msg text | |
580 | @type text: str | |
581 | @param package_context: package name to use for the type name or | |
582 | '' to use the local (relative) naming convention. | |
583 | @type package_context: str | |
584 | @return: Message specification | |
585 | @rtype: L{MsgSpec} | |
586 | @raise MsgSpecException: if syntax errors or other problems are detected in file | |
587 | """ | |
588 | types = [] | |
589 | names = [] | |
590 | constants = [] | |
591 | for orig_line in text.split('\n'): | |
592 | l = orig_line.split(COMMENTCHAR)[0].strip() #strip comments | |
593 | if not l: | |
594 | continue #ignore empty lines | |
595 | splits = [s for s in [x.strip() for x in l.split(" ")] if s] #split type/name, filter out empties | |
596 | type_ = splits[0] | |
597 | if not is_valid_msg_type(type_): | |
598 | raise MsgSpecException("%s is not a legal message type"%type_) | |
599 | if CONSTCHAR in l: | |
600 | if not is_valid_constant_type(type_): | |
601 | raise MsgSpecException("%s is not a legal constant type"%type_) | |
602 | if type_ == 'string': | |
603 | # strings contain anything to the right of the equals sign, there are no comments allowed | |
604 | idx = orig_line.find(CONSTCHAR) | |
605 | name = orig_line[orig_line.find(' ')+1:idx] | |
606 | val = orig_line[idx+1:] | |
607 | else: | |
608 | splits = [x.strip() for x in ' '.join(splits[1:]).split(CONSTCHAR)] #resplit on '=' | |
609 | if len(splits) != 2: | |
610 | raise MsgSpecException("Invalid declaration: %s"%l) | |
611 | name = splits[0] | |
612 | val = splits[1] | |
613 | try: | |
614 | val_converted = _convert_val(type_, val) | |
615 | except Exception as e: | |
616 | raise MsgSpecException("Invalid declaration: %s"%e) | |
617 | constants.append(Constant(type_, name, val_converted, val.strip())) | |
618 | else: | |
619 | if len(splits) != 2: | |
620 | raise MsgSpecException("Invalid declaration: %s"%l) | |
621 | name = splits[1] | |
622 | if not is_valid_msg_field_name(name): | |
623 | raise MsgSpecException("%s is not a legal message field name"%name) | |
624 | if package_context and not SEP in type_: | |
625 | if not base_msg_type(type_) in RESERVED_TYPES: | |
626 | #print "rewrite", type_, "to", "%s/%s"%(package_context, type_) | |
627 | type_ = "%s/%s"%(package_context, type_) | |
628 | types.append(type_) | |
629 | names.append(name) | |
630 | return MsgSpec(types, names, constants, text, full_name, short_name, package_context) | |
631 | ||
632 | def load_from_file(file_path, package_context=''): | |
633 | """ | |
634 | Convert the .msg representation in the file to a MsgSpec instance. | |
635 | This does *not* register the object. | |
636 | @param file_path: path of file to load from | |
637 | @type file_path: str: | |
638 | @param package_context: package name to prepend to type name or | |
639 | '' to use local (relative) naming convention. | |
640 | @type package_context: str | |
641 | @return: Message type name and message specification | |
642 | @rtype: (str, L{MsgSpec}) | |
643 | @raise MsgSpecException: if syntax errors or other problems are detected in file | |
644 | """ | |
645 | if VERBOSE: | |
646 | if package_context: | |
647 | print("Load spec from", file_path, "into package [%s]"%package_context) | |
648 | else: | |
649 | print("Load spec from", file_path) | |
650 | ||
651 | file_name = os.path.basename(file_path) | |
652 | type_ = file_name[:-len(EXT)] | |
653 | base_type_ = type_ | |
654 | # determine the type name | |
655 | if package_context: | |
656 | while package_context.endswith(SEP): | |
657 | package_context = package_context[:-1] #strip message separators | |
658 | type_ = "%s%s%s"%(package_context, SEP, type_) | |
659 | if not roslib.names.is_legal_resource_name(type_): | |
660 | raise MsgSpecException("%s: [%s] is not a legal type name"%(file_path, type_)) | |
661 | ||
662 | f = open(file_path, 'r') | |
663 | try: | |
664 | try: | |
665 | text = f.read() | |
666 | return (type_, load_from_string(text, package_context, type_, base_type_)) | |
667 | except MsgSpecException as e: | |
668 | raise MsgSpecException('%s: %s'%(file_name, e)) | |
669 | finally: | |
670 | f.close() | |
671 | ||
672 | # data structures and builtins specification ########################### | |
673 | ||
674 | # adjustable constants, in case we change our minds | |
675 | HEADER = 'Header' | |
676 | TIME = 'time' | |
677 | DURATION = 'duration' | |
678 | ||
679 | def is_header_type(type_): | |
680 | """ | |
681 | @param type_: message type name | |
682 | @type type_: str | |
683 | @return: True if \a type_ refers to the ROS Header type | |
684 | @rtype: bool | |
685 | """ | |
686 | # for backwards compatibility, include roslib/Header. REP 100 | |
687 | return type_ in [HEADER, 'std_msgs/Header', 'roslib/Header'] | |
688 | ||
689 | # time and duration types are represented as aggregate data structures | |
690 | # for the purposes of serialization from the perspective of | |
691 | # roslib.msgs. genmsg_py will do additional special handling is required | |
692 | # to convert them into rospy.msg.Time/Duration instances. | |
693 | ||
694 | ## time as msg spec. time is unsigned | |
695 | TIME_MSG = "uint32 secs\nuint32 nsecs" | |
696 | ## duration as msg spec. duration is just like time except signed | |
697 | DURATION_MSG = "int32 secs\nint32 nsecs" | |
698 | ||
699 | ## primitive types are those for which we allow constants, i.e. have primitive representation | |
700 | PRIMITIVE_TYPES = ['int8','uint8','int16','uint16','int32','uint32','int64','uint64','float32','float64', | |
701 | 'string', | |
702 | 'bool', | |
703 | # deprecated: | |
704 | 'char','byte'] | |
705 | BUILTIN_TYPES = PRIMITIVE_TYPES + [TIME, DURATION] | |
706 | ||
707 | def is_builtin(msg_type_name): | |
708 | """ | |
709 | @param msg_type_name: name of message type | |
710 | @type msg_type_name: str | |
711 | @return: True if msg_type_name is a builtin/primitive type | |
712 | @rtype: bool | |
713 | """ | |
714 | return msg_type_name in BUILTIN_TYPES | |
715 | ||
716 | ## extended builtins are builtin types that can be represented as MsgSpec instances | |
717 | EXTENDED_BUILTINS = { TIME : load_from_string(TIME_MSG), DURATION: load_from_string(DURATION_MSG) } | |
718 | ||
719 | RESERVED_TYPES = BUILTIN_TYPES + [HEADER] | |
720 | ||
721 | REGISTERED_TYPES = { } | |
722 | _loaded_packages = [] #keep track of packages so that we only load once (note: bug #59) | |
723 | ||
724 | def is_registered(msg_type_name): | |
725 | """ | |
726 | @param msg_type_name: name of message type | |
727 | @type msg_type_name: str | |
728 | @return: True if msg spec for specified msg type name is | |
729 | registered. NOTE: builtin types are not registered. | |
730 | @rtype: bool | |
731 | """ | |
732 | return msg_type_name in REGISTERED_TYPES | |
733 | ||
734 | def get_registered(msg_type_name, default_package=None): | |
735 | """ | |
736 | @param msg_type_name: name of message type | |
737 | @type msg_type_name: str | |
738 | @return: msg spec for msg type name | |
739 | @rtype: L{MsgSpec} | |
740 | """ | |
741 | if msg_type_name in REGISTERED_TYPES: | |
742 | return REGISTERED_TYPES[msg_type_name] | |
743 | elif default_package: | |
744 | # if msg_type_name has no package specifier, try with default package resolution | |
745 | p, n = roslib.names.package_resource_name(msg_type_name) | |
746 | if not p: | |
747 | return REGISTERED_TYPES[roslib.names.resource_name(default_package, msg_type_name)] | |
748 | raise KeyError(msg_type_name) | |
749 | ||
750 | def register(msg_type_name, msg_spec): | |
751 | """ | |
752 | Load MsgSpec into the type dictionary | |
753 | ||
754 | @param msg_type_name: name of message type | |
755 | @type msg_type_name: str | |
756 | @param msg_spec: spec to load | |
757 | @type msg_spec: L{MsgSpec} | |
758 | """ | |
759 | if VERBOSE: | |
760 | print("Register msg %s"%msg_type_name) | |
761 | REGISTERED_TYPES[msg_type_name] = msg_spec | |
762 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | """ | |
35 | Warning: do not use this library. It is unstable and most of the routines | |
36 | here have been superceded by other libraries (e.g. genmsg). These | |
37 | routines will likely be *deleted* in future releases. | |
38 | """ | |
39 | ||
40 | import os | |
41 | import sys | |
42 | ||
43 | #TODO: deprecate PRN_SEPARATOR | |
44 | PRN_SEPARATOR = '/' | |
45 | TYPE_SEPARATOR = PRN_SEPARATOR #alias | |
46 | SEP = '/' | |
47 | GLOBALNS = '/' | |
48 | PRIV_NAME = '~' | |
49 | REMAP = ":=" | |
50 | ANYTYPE = '*' | |
51 | ||
52 | if sys.hexversion > 0x03000000: #Python3 | |
53 | def isstring(s): | |
54 | return isinstance(s, str) #Python 3.x | |
55 | else: | |
56 | def isstring(s): | |
57 | """ | |
58 | Small helper version to check an object is a string in a way that works | |
59 | for both Python 2 and 3 | |
60 | """ | |
61 | return isinstance(s, basestring) #Python 2.x | |
62 | ||
63 | def get_ros_namespace(env=None, argv=None): | |
64 | """ | |
65 | @param env: environment dictionary (defaults to os.environ) | |
66 | @type env: dict | |
67 | @param argv: command-line arguments (defaults to sys.argv) | |
68 | @type argv: [str] | |
69 | @return: ROS namespace of current program | |
70 | @rtype: str | |
71 | """ | |
72 | #we force command-line-specified namespaces to be globally scoped | |
73 | if argv is None: | |
74 | argv = sys.argv | |
75 | for a in argv: | |
76 | if a.startswith('__ns:='): | |
77 | return make_global_ns(a[len('__ns:='):]) | |
78 | if env is None: | |
79 | env = os.environ | |
80 | return make_global_ns(env.get('ROS_NAMESPACE', GLOBALNS)) | |
81 | ||
82 | def make_caller_id(name): | |
83 | """ | |
84 | Resolve a local name to the caller ID based on ROS environment settings (i.e. ROS_NAMESPACE) | |
85 | ||
86 | @param name: local name to calculate caller ID from, e.g. 'camera', 'node' | |
87 | @type name: str | |
88 | @return: caller ID based on supplied local name | |
89 | @rtype: str | |
90 | """ | |
91 | return make_global_ns(ns_join(get_ros_namespace(), name)) | |
92 | ||
93 | def make_global_ns(name): | |
94 | """ | |
95 | Convert name to a global name with a trailing namespace separator. | |
96 | ||
97 | @param name: ROS resource name. Cannot be a ~name. | |
98 | @type name: str | |
99 | @return str: name as a global name, e.g. 'foo' -> '/foo/'. | |
100 | This does NOT resolve a name. | |
101 | @rtype: str | |
102 | @raise ValueError: if name is a ~name | |
103 | """ | |
104 | if is_private(name): | |
105 | raise ValueError("cannot turn [%s] into a global name"%name) | |
106 | if not is_global(name): | |
107 | name = SEP + name | |
108 | if name[-1] != SEP: | |
109 | name = name + SEP | |
110 | return name | |
111 | ||
112 | def is_global(name): | |
113 | """ | |
114 | Test if name is a global graph resource name. | |
115 | ||
116 | @param name: must be a legal name in canonical form | |
117 | @type name: str | |
118 | @return: True if name is a globally referenced name (i.e. /ns/name) | |
119 | @rtype: bool | |
120 | """ | |
121 | return name and name[0] == SEP | |
122 | ||
123 | def is_private(name): | |
124 | """ | |
125 | Test if name is a private graph resource name. | |
126 | ||
127 | @param name: must be a legal name in canonical form | |
128 | @type name: str | |
129 | @return bool: True if name is a privately referenced name (i.e. ~name) | |
130 | """ | |
131 | return name and name[0] == PRIV_NAME | |
132 | ||
133 | def namespace(name): | |
134 | """ | |
135 | Get the namespace of name. The namespace is returned with a | |
136 | trailing slash in order to favor easy concatenation and easier use | |
137 | within the global context. | |
138 | ||
139 | @param name: name to return the namespace of. Must be a legal | |
140 | name. NOTE: an empty name will return the global namespace. | |
141 | @type name: str | |
142 | @return str: Namespace of name. For example, '/wg/node1' returns '/wg/'. The | |
143 | global namespace is '/'. | |
144 | @rtype: str | |
145 | @raise ValueError: if name is invalid | |
146 | """ | |
147 | "map name to its namespace" | |
148 | if name is None: | |
149 | raise ValueError('name') | |
150 | if not isstring(name): | |
151 | raise TypeError('name') | |
152 | if not name: | |
153 | return SEP | |
154 | elif name[-1] == SEP: | |
155 | name = name[:-1] | |
156 | return name[:name.rfind(SEP)+1] or SEP | |
157 | ||
158 | def ns_join(ns, name): | |
159 | """ | |
160 | Join a namespace and name. If name is unjoinable (i.e. ~private or | |
161 | /global) it will be returned without joining | |
162 | ||
163 | @param ns: namespace ('/' and '~' are both legal). If ns is the empty string, name will be returned. | |
164 | @type ns: str | |
165 | @param name str: a legal name | |
166 | @return str: name concatenated to ns, or name if it is | |
167 | unjoinable. | |
168 | @rtype: str | |
169 | """ | |
170 | if is_private(name) or is_global(name): | |
171 | return name | |
172 | if ns == PRIV_NAME: | |
173 | return PRIV_NAME + name | |
174 | if not ns: | |
175 | return name | |
176 | if ns[-1] == SEP: | |
177 | return ns + name | |
178 | return ns + SEP + name | |
179 | ||
180 | def load_mappings(argv): | |
181 | """ | |
182 | Load name mappings encoded in command-line arguments. This will filter | |
183 | out any parameter assignment mappings (see roslib.param.load_param_mappings()). | |
184 | ||
185 | @param argv: command-line arguments | |
186 | @type argv: [str] | |
187 | @return: name->name remappings. | |
188 | @rtype: dict {str: str} | |
189 | """ | |
190 | mappings = {} | |
191 | for arg in argv: | |
192 | if REMAP in arg: | |
193 | try: | |
194 | src, dst = [x.strip() for x in arg.split(REMAP)] | |
195 | if src and dst: | |
196 | if len(src) > 1 and src[0] == '_' and src[1] != '_': | |
197 | #ignore parameter assignment mappings | |
198 | pass | |
199 | else: | |
200 | mappings[src] = dst | |
201 | except: | |
202 | sys.stderr.write("ERROR: Invalid remapping argument '%s'\n"%arg) | |
203 | return mappings | |
204 | ||
205 | ####################################################################### | |
206 | # RESOURCE NAMES | |
207 | # resource names refer to entities in a file system | |
208 | ||
209 | def resource_name(res_pkg_name, name, my_pkg=None): | |
210 | """ | |
211 | Convert package name + resource into a fully qualified resource name | |
212 | ||
213 | @param res_pkg_name: name of package resource is located in | |
214 | @type res_pkg_name: str | |
215 | @param name: resource base name | |
216 | @type name: str | |
217 | @param my_pkg: name of package resource is being referred to | |
218 | in. If specified, name will be returned in local form if | |
219 | res_pkg_name is my_pkg | |
220 | @type my_pkg: str | |
221 | @return: name for resource | |
222 | @rtype: str | |
223 | """ | |
224 | if res_pkg_name != my_pkg: | |
225 | return res_pkg_name+PRN_SEPARATOR+name | |
226 | return name | |
227 | ||
228 | def resource_name_base(name): | |
229 | """ | |
230 | pkg/typeName -> typeName, typeName -> typeName | |
231 | ||
232 | Convert fully qualified resource name into the package-less resource name | |
233 | @param name: package resource name, e.g. 'std_msgs/String' | |
234 | @type name: str | |
235 | @return: resource name sans package-name scope | |
236 | @rtype: str | |
237 | """ | |
238 | ||
239 | return name[name.rfind(PRN_SEPARATOR)+1:] | |
240 | ||
241 | def resource_name_package(name): | |
242 | """ | |
243 | pkg/typeName -> pkg, typeName -> None | |
244 | ||
245 | @param name: package resource name, e.g. 'std_msgs/String' | |
246 | @type name: str | |
247 | @return: package name of resource | |
248 | @rtype: str | |
249 | """ | |
250 | ||
251 | if not PRN_SEPARATOR in name: | |
252 | return None | |
253 | return name[:name.find(PRN_SEPARATOR)] | |
254 | ||
255 | def package_resource_name(name): | |
256 | """ | |
257 | Split a name into its package and resource name parts, e.g. 'std_msgs/String -> std_msgs, String' | |
258 | ||
259 | @param name: package resource name, e.g. 'std_msgs/String' | |
260 | @type name: str | |
261 | @return: package name, resource name | |
262 | @rtype: str | |
263 | @raise ValueError: if name is invalid | |
264 | """ | |
265 | if PRN_SEPARATOR in name: | |
266 | val = tuple(name.split(PRN_SEPARATOR)) | |
267 | if len(val) != 2: | |
268 | raise ValueError("invalid name [%s]"%name) | |
269 | else: | |
270 | return val | |
271 | else: | |
272 | return '', name | |
273 | ||
274 | def _is_safe_name(name, type_name): | |
275 | #windows long-file name length is 255 | |
276 | if not isstring(name) or not name or len(name) > 255: | |
277 | return False | |
278 | return is_legal_resource_name(name) | |
279 | ||
280 | ################################################################################ | |
281 | # NAME VALIDATORS | |
282 | ||
283 | import re | |
284 | #ascii char followed by (alphanumeric, _, /) | |
285 | RESOURCE_NAME_LEGAL_CHARS_P = re.compile('^[A-Za-z][\w_\/]*$') | |
286 | def is_legal_resource_name(name): | |
287 | """ | |
288 | Check if name is a legal ROS name for filesystem resources | |
289 | (alphabetical character followed by alphanumeric, underscore, or | |
290 | forward slashes). This constraint is currently not being enforced, | |
291 | but may start getting enforced in later versions of ROS. | |
292 | ||
293 | @param name: Name | |
294 | @type name: str | |
295 | """ | |
296 | # resource names can be unicode due to filesystem | |
297 | if name is None: | |
298 | return False | |
299 | m = RESOURCE_NAME_LEGAL_CHARS_P.match(name) | |
300 | # '//' check makes sure there isn't double-slashes | |
301 | return m is not None and m.group(0) == name and not '//' in name | |
302 | ||
303 | #~,/, or ascii char followed by (alphanumeric, _, /) | |
304 | NAME_LEGAL_CHARS_P = re.compile('^[\~\/A-Za-z][\w_\/]*$') | |
305 | def is_legal_name(name): | |
306 | """ | |
307 | Check if name is a legal ROS name for graph resources | |
308 | (alphabetical character followed by alphanumeric, underscore, or | |
309 | forward slashes). This constraint is currently not being enforced, | |
310 | but may start getting enforced in later versions of ROS. | |
311 | ||
312 | @param name: Name | |
313 | @type name: str | |
314 | """ | |
315 | # should we enforce unicode checks? | |
316 | if name is None: | |
317 | return False | |
318 | # empty string is a legal name as it resolves to namespace | |
319 | if name == '': | |
320 | return True | |
321 | m = NAME_LEGAL_CHARS_P.match(name) | |
322 | return m is not None and m.group(0) == name and not '//' in name | |
323 | ||
324 | BASE_NAME_LEGAL_CHARS_P = re.compile('^[A-Za-z][\w_]*$') #ascii char followed by (alphanumeric, _) | |
325 | def is_legal_base_name(name): | |
326 | """ | |
327 | Validates that name is a legal base name for a graph resource. A base name has | |
328 | no namespace context, e.g. "node_name". | |
329 | """ | |
330 | if name is None: | |
331 | return False | |
332 | m = BASE_NAME_LEGAL_CHARS_P.match(name) | |
333 | return m is not None and m.group(0) == name | |
334 | ||
335 | BASE_RESOURCE_NAME_LEGAL_CHARS_P = re.compile('^[A-Za-z][\w_]*$') #ascii char followed by (alphanumeric, _) | |
336 | def is_legal_resource_base_name(name): | |
337 | """ | |
338 | Validates that name is a legal resource base name. A base name has | |
339 | no package context, e.g. "String". | |
340 | """ | |
341 | # resource names can be unicode due to filesystem | |
342 | if name is None: | |
343 | return False | |
344 | m = BASE_NAME_LEGAL_CHARS_P.match(name) | |
345 | return m is not None and m.group(0) == name | |
346 | ||
347 | def canonicalize_name(name): | |
348 | """ | |
349 | Put name in canonical form. Extra slashes '//' are removed and | |
350 | name is returned without any trailing slash, e.g. /foo/bar | |
351 | @param name: ROS name | |
352 | @type name: str | |
353 | """ | |
354 | if not name or name == SEP: | |
355 | return name | |
356 | elif name[0] == SEP: | |
357 | return '/' + '/'.join([x for x in name.split(SEP) if x]) | |
358 | else: | |
359 | return '/'.join([x for x in name.split(SEP) if x]) | |
360 | ||
361 | def resolve_name(name, namespace_, remappings=None): | |
362 | """ | |
363 | Resolve a ROS name to its global, canonical form. Private ~names | |
364 | are resolved relative to the node name. | |
365 | ||
366 | @param name: name to resolve. | |
367 | @type name: str | |
368 | @param namespace_: node name to resolve relative to. | |
369 | @type namespace_: str | |
370 | @param remappings: Map of resolved remappings. Use None to indicate no remapping. | |
371 | @return: Resolved name. If name is empty/None, resolve_name | |
372 | returns parent namespace_. If namespace_ is empty/None, | |
373 | @rtype: str | |
374 | """ | |
375 | if not name: #empty string resolves to parent of the namespace_ | |
376 | return namespace(namespace_) | |
377 | ||
378 | name = canonicalize_name(name) | |
379 | if name[0] == SEP: #global name | |
380 | resolved_name = name | |
381 | elif is_private(name): #~name | |
382 | # #3044: be careful not to accidentally make rest of name global | |
383 | resolved_name = canonicalize_name(namespace_ + SEP + name[1:]) | |
384 | else: #relative | |
385 | resolved_name = namespace(namespace_) + name | |
386 | ||
387 | #Mappings override general namespace-based resolution | |
388 | # - do this before canonicalization as remappings are meant to | |
389 | # match the name as specified in the code | |
390 | if remappings and resolved_name in remappings: | |
391 | return remappings[resolved_name] | |
392 | else: | |
393 | return resolved_name | |
394 | ||
395 | def anonymous_name(id): | |
396 | """ | |
397 | Generate a ROS-legal 'anonymous' name | |
398 | ||
399 | @param id: prefix for anonymous name | |
400 | @type id: str | |
401 | """ | |
402 | import socket, random | |
403 | name = "%s_%s_%s_%s"%(id, socket.gethostname(), os.getpid(), random.randint(0, sys.maxsize)) | |
404 | # RFC 952 allows hyphens, IP addrs can have '.'s, both | |
405 | # of which are illegal for ROS names. For good | |
406 | # measure, screen ipv6 ':'. | |
407 | name = name.replace('.', '_') | |
408 | name = name.replace('-', '_') | |
409 | return name.replace(':', '_') | |
410 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | """ | |
35 | Warning: do not use this library. It is unstable and most of the routines | |
36 | here have been superceded by other libraries (e.g. rosgraph). These | |
37 | routines will likely be *deleted* in future releases. | |
38 | """ | |
39 | ||
40 | import os | |
41 | import socket | |
42 | import struct | |
43 | import sys | |
44 | import platform | |
45 | ||
46 | try: | |
47 | from cStringIO import StringIO #Python 2.x | |
48 | python3 = 0 | |
49 | except ImportError: | |
50 | from io import BytesIO #Python 3.x | |
51 | python3 = 1 | |
52 | ||
53 | try: | |
54 | import urllib.parse as urlparse | |
55 | except ImportError: | |
56 | import urlparse | |
57 | ||
58 | #TODO: change this to rosgraph equivalents once we have ported this module | |
59 | ROS_IP = 'ROS_IP' | |
60 | ROS_HOSTNAME = 'ROS_HOSTNAME' | |
61 | ||
62 | SIOCGIFCONF = 0x8912 | |
63 | SIOCGIFADDR = 0x8915 | |
64 | if platform.system() == 'FreeBSD': | |
65 | SIOCGIFADDR = 0xc0206921 | |
66 | if platform.architecture()[0] == '64bit': | |
67 | SIOCGIFCONF = 0xc0106924 | |
68 | else: | |
69 | SIOCGIFCONF = 0xc0086924 | |
70 | ||
71 | if 0: | |
72 | # disabling netifaces as it accounts for 50% of startup latency | |
73 | try: | |
74 | import netifaces | |
75 | _use_netifaces = True | |
76 | except: | |
77 | # NOTE: in rare cases, I've seen Python fail to extract the egg | |
78 | # cache when launching multiple python nodes. Thus, we do | |
79 | # except-all instead of except ImportError (kwc). | |
80 | _use_netifaces = False | |
81 | else: | |
82 | _use_netifaces = False | |
83 | ||
84 | def _is_unix_like_platform(): | |
85 | """ | |
86 | @return: true if the platform conforms to UNIX/POSIX-style APIs | |
87 | @rtype: bool | |
88 | """ | |
89 | #return platform.system() in ['Linux', 'Mac OS X', 'Darwin'] | |
90 | return platform.system() in ['Linux', 'FreeBSD'] | |
91 | ||
92 | def get_address_override(): | |
93 | """ | |
94 | @return: ROS_IP/ROS_HOSTNAME override or None | |
95 | @rtype: str | |
96 | @raise ValueError: if ROS_IP/ROS_HOSTNAME/__ip/__hostname are invalidly specified | |
97 | """ | |
98 | # #998: check for command-line remappings first | |
99 | for arg in sys.argv: | |
100 | if arg.startswith('__hostname:=') or arg.startswith('__ip:='): | |
101 | try: | |
102 | _, val = arg.split(':=') | |
103 | return val | |
104 | except: #split didn't unpack properly | |
105 | raise ValueError("invalid ROS command-line remapping argument '%s'"%arg) | |
106 | ||
107 | # check ROS_HOSTNAME and ROS_IP environment variables, which are | |
108 | # aliases for each other | |
109 | if ROS_HOSTNAME in os.environ: | |
110 | return os.environ[ROS_HOSTNAME] | |
111 | elif ROS_IP in os.environ: | |
112 | return os.environ[ROS_IP] | |
113 | return None | |
114 | ||
115 | def is_local_address(hostname): | |
116 | """ | |
117 | @param hostname: host name/address | |
118 | @type hostname: str | |
119 | @return True: if hostname maps to a local address, False otherwise. False conditions include invalid hostnames. | |
120 | """ | |
121 | try: | |
122 | reverse_ip = socket.gethostbyname(hostname) | |
123 | except socket.error: | |
124 | return False | |
125 | # 127. check is due to #1260 | |
126 | if reverse_ip not in get_local_addresses() and not reverse_ip.startswith('127.'): | |
127 | return False | |
128 | return True | |
129 | ||
130 | def get_local_address(): | |
131 | """ | |
132 | @return: default local IP address (e.g. eth0). May be overriden by ROS_IP/ROS_HOSTNAME/__ip/__hostname | |
133 | @rtype: str | |
134 | """ | |
135 | override = get_address_override() | |
136 | if override: | |
137 | return override | |
138 | addrs = get_local_addresses() | |
139 | if len(addrs) == 1: | |
140 | return addrs[0] | |
141 | for addr in addrs: | |
142 | # pick first non 127/8 address | |
143 | if not addr.startswith('127.'): | |
144 | return addr | |
145 | else: # loopback | |
146 | return '127.0.0.1' | |
147 | ||
148 | # cache for performance reasons | |
149 | _local_addrs = None | |
150 | def get_local_addresses(): | |
151 | """ | |
152 | @return: known local addresses. Not affected by ROS_IP/ROS_HOSTNAME | |
153 | @rtype: [str] | |
154 | """ | |
155 | # cache address data as it can be slow to calculate | |
156 | global _local_addrs | |
157 | if _local_addrs is not None: | |
158 | return _local_addrs | |
159 | ||
160 | local_addrs = None | |
161 | if _use_netifaces: | |
162 | # #552: netifaces is a more robust package for looking up | |
163 | # #addresses on multiple platforms (OS X, Unix, Windows) | |
164 | local_addrs = [] | |
165 | # see http://alastairs-place.net/netifaces/ | |
166 | for i in netifaces.interfaces(): | |
167 | try: | |
168 | local_addrs.extend([d['addr'] for d in netifaces.ifaddresses(i)[netifaces.AF_INET]]) | |
169 | except KeyError: pass | |
170 | elif _is_unix_like_platform(): | |
171 | # unix-only branch | |
172 | # adapted from code from Rosen Diankov (rdiankov@cs.cmu.edu) | |
173 | # and from ActiveState recipe | |
174 | ||
175 | import fcntl | |
176 | import array | |
177 | ||
178 | ifsize = 32 | |
179 | if platform.system() == 'Linux' and platform.architecture()[0] == '64bit': | |
180 | ifsize = 40 # untested | |
181 | ||
182 | # 32 interfaces allowed, far more than ROS can sanely deal with | |
183 | ||
184 | max_bytes = 32 * ifsize | |
185 | # according to http://docs.python.org/library/fcntl.html, the buffer limit is 1024 bytes | |
186 | buff = array.array('B', '\0' * max_bytes) | |
187 | # serialize the buffer length and address to ioctl | |
188 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
189 | info = fcntl.ioctl(sock.fileno(), SIOCGIFCONF, | |
190 | struct.pack('iL', max_bytes, buff.buffer_info()[0])) | |
191 | retbytes = struct.unpack('iL', info)[0] | |
192 | buffstr = buff.tostring() | |
193 | if platform.system() == 'Linux': | |
194 | local_addrs = [socket.inet_ntoa(buffstr[i+20:i+24]) for i in range(0, retbytes, ifsize)] | |
195 | else: | |
196 | # in FreeBSD, ifsize is variable: 16 + (16 or 28 or 56) bytes | |
197 | # When ifsize is 32 bytes, it contains the interface name and address, | |
198 | # else it contains the interface name and other information | |
199 | # This means the buffer must be traversed in its entirety | |
200 | local_addrs = [] | |
201 | bufpos = 0 | |
202 | while bufpos < retbytes: | |
203 | bufpos += 16 | |
204 | ifreqsize = ord(buffstr[bufpos]) | |
205 | if ifreqsize == 16: | |
206 | local_addrs += [socket.inet_ntoa(buffstr[bufpos+4:bufpos+8])] | |
207 | bufpos += ifreqsize | |
208 | else: | |
209 | # cross-platform branch, can only resolve one address | |
210 | local_addrs = [socket.gethostbyname(socket.gethostname())] | |
211 | _local_addrs = local_addrs | |
212 | return local_addrs | |
213 | ||
214 | ||
215 | def get_bind_address(address=None): | |
216 | """ | |
217 | @param address: (optional) address to compare against | |
218 | @type address: str | |
219 | @return: address TCP/IP sockets should use for binding. This is | |
220 | generally 0.0.0.0, but if \a address or ROS_IP/ROS_HOSTNAME is set | |
221 | to localhost it will return 127.0.0.1 | |
222 | @rtype: str | |
223 | """ | |
224 | if address is None: | |
225 | address = get_address_override() | |
226 | if address and \ | |
227 | (address == 'localhost' or address.startswith('127.')): | |
228 | #localhost or 127/8 | |
229 | return '127.0.0.1' #loopback | |
230 | else: | |
231 | return '0.0.0.0' | |
232 | ||
233 | # #528: semi-complicated logic for determining XML-RPC URI | |
234 | def get_host_name(): | |
235 | """ | |
236 | Determine host-name for use in host-name-based addressing (e.g. XML-RPC URIs): | |
237 | - if ROS_IP/ROS_HOSTNAME is set, use that address | |
238 | - if the hostname returns a non-localhost value, use that | |
239 | - use whatever L{get_local_address()} returns | |
240 | """ | |
241 | hostname = get_address_override() | |
242 | if not hostname: | |
243 | try: | |
244 | hostname = socket.gethostname() | |
245 | except: | |
246 | pass | |
247 | if not hostname or hostname == 'localhost' or hostname.startswith('127.'): | |
248 | hostname = get_local_address() | |
249 | return hostname | |
250 | ||
251 | def create_local_xmlrpc_uri(port): | |
252 | """ | |
253 | Determine the XMLRPC URI for local servers. This handles the search | |
254 | logic of checking ROS environment variables, the known hostname, | |
255 | and local interface IP addresses to determine the best possible | |
256 | URI. | |
257 | ||
258 | @param port: port that server is running on | |
259 | @type port: int | |
260 | @return: XMLRPC URI | |
261 | @rtype: str | |
262 | """ | |
263 | #TODO: merge logic in roslib.xmlrpc with this routine | |
264 | # in the future we may not want to be locked to http protocol nor root path | |
265 | return 'http://%s:%s/'%(get_host_name(), port) | |
266 | ||
267 | ||
268 | ## handshake utils ########################################### | |
269 | ||
270 | class ROSHandshakeException(Exception): | |
271 | """ | |
272 | Exception to represent errors decoding handshake | |
273 | """ | |
274 | pass | |
275 | ||
276 | def decode_ros_handshake_header(header_str): | |
277 | """ | |
278 | Decode serialized ROS handshake header into a Python dictionary | |
279 | ||
280 | header is a list of string key=value pairs, each prefixed by a | |
281 | 4-byte length field. It is preceeded by a 4-byte length field for | |
282 | the entire header. | |
283 | ||
284 | @param header_str: encoded header string. May contain extra data at the end. | |
285 | @type header_str: str | |
286 | @return: key value pairs encoded in \a header_str | |
287 | @rtype: {str: str} | |
288 | """ | |
289 | (size, ) = struct.unpack('<I', header_str[0:4]) | |
290 | size += 4 # add in 4 to include size of size field | |
291 | header_len = len(header_str) | |
292 | if size > header_len: | |
293 | raise ROSHandshakeException("Incomplete header. Expected %s bytes but only have %s"%((size+4), header_len)) | |
294 | ||
295 | d = {} | |
296 | start = 4 | |
297 | while start < size: | |
298 | (field_size, ) = struct.unpack('<I', header_str[start:start+4]) | |
299 | if field_size == 0: | |
300 | raise ROSHandshakeException("Invalid 0-length handshake header field") | |
301 | start += field_size + 4 | |
302 | if start > size: | |
303 | raise ROSHandshakeException("Invalid line length in handshake header: %s"%size) | |
304 | line = header_str[start-field_size:start] | |
305 | ||
306 | #python3 compatibility | |
307 | if python3 == 1: | |
308 | line = line.decode() | |
309 | ||
310 | idx = line.find("=") | |
311 | if idx < 0: | |
312 | raise ROSHandshakeException("Invalid line in handshake header: [%s]"%line) | |
313 | key = line[:idx] | |
314 | value = line[idx+1:] | |
315 | d[key.strip()] = value | |
316 | return d | |
317 | ||
318 | def read_ros_handshake_header(sock, b, buff_size): | |
319 | """ | |
320 | Read in tcpros header off the socket \a sock using buffer \a b. | |
321 | ||
322 | @param sock: socket must be in blocking mode | |
323 | @type sock: socket | |
324 | @param b: buffer to use | |
325 | @type b: StringIO for Python2, BytesIO for Python 3 | |
326 | @param buff_size: incoming buffer size to use | |
327 | @type buff_size: int | |
328 | @return: key value pairs encoded in handshake | |
329 | @rtype: {str: str} | |
330 | @raise ROSHandshakeException: If header format does not match expected | |
331 | """ | |
332 | header_str = None | |
333 | while not header_str: | |
334 | d = sock.recv(buff_size) | |
335 | if not d: | |
336 | raise ROSHandshakeException("connection from sender terminated before handshake header received. %s bytes were received. Please check sender for additional details."%b.tell()) | |
337 | b.write(d) | |
338 | btell = b.tell() | |
339 | if btell > 4: | |
340 | # most likely we will get the full header in the first recv, so | |
341 | # not worth tiny optimizations possible here | |
342 | bval = b.getvalue() | |
343 | (size,) = struct.unpack('<I', bval[0:4]) | |
344 | if btell - 4 >= size: | |
345 | header_str = bval | |
346 | ||
347 | # memmove the remnants of the buffer back to the start | |
348 | leftovers = bval[size+4:] | |
349 | b.truncate(len(leftovers)) | |
350 | b.seek(0) | |
351 | b.write(leftovers) | |
352 | header_recvd = True | |
353 | ||
354 | # process the header | |
355 | return decode_ros_handshake_header(bval) | |
356 | ||
357 | def encode_ros_handshake_header(header): | |
358 | """ | |
359 | Encode ROS handshake header as a byte string. Each header | |
360 | field is a string key value pair. The encoded header is | |
361 | prefixed by a length field, as is each field key/value pair. | |
362 | key/value pairs a separated by a '=' equals sign. | |
363 | ||
364 | FORMAT: (4-byte length + [4-byte field length + field=value ]*) | |
365 | ||
366 | @param header: header field keys/values | |
367 | @type header: dict | |
368 | @return: header encoded as byte string | |
369 | @rtype: str | |
370 | """ | |
371 | fields = ["%s=%s"%(k,v) for k,v in header.items()] | |
372 | ||
373 | # in the usual configuration, the error 'TypeError: can't concat bytes to str' appears: | |
374 | if python3 == 0: | |
375 | #python 2 | |
376 | s = ''.join(["%s%s"%(struct.pack('<I', len(f)), f) for f in fields]) | |
377 | return struct.pack('<I', len(s)) + s | |
378 | else: | |
379 | #python 3 | |
380 | s = b''.join([(struct.pack('<I', len(f)) + f.encode("utf-8")) for f in fields]) | |
381 | return struct.pack('<I', len(s)) + s | |
382 | ||
383 | def write_ros_handshake_header(sock, header): | |
384 | """ | |
385 | Write ROS handshake header header to socket sock | |
386 | @param sock: socket to write to (must be in blocking mode) | |
387 | @type sock: socket.socket | |
388 | @param header: header field keys/values | |
389 | @type header: {str : str} | |
390 | @return: Number of bytes sent (for statistics) | |
391 | @rtype: int | |
392 | """ | |
393 | s = encode_ros_handshake_header(header) | |
394 | sock.sendall(s) | |
395 | return len(s) #STATS | |
396 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | # $Author$ | |
34 | ||
35 | """ | |
36 | Warning: do not use this library. It is unstable and most of the routines | |
37 | here have been superceded by other libraries (e.g. rospkg). These | |
38 | routines will likely be *deleted* in future releases. | |
39 | """ | |
40 | ||
41 | import os | |
42 | import sys | |
43 | import stat | |
44 | import string | |
45 | ||
46 | from subprocess import Popen, PIPE | |
47 | ||
48 | from catkin.find_in_workspaces import find_in_workspaces as catkin_find | |
49 | import rospkg | |
50 | ||
51 | import roslib.manifest | |
52 | ||
53 | SRC_DIR = 'src' | |
54 | ||
55 | # aliases | |
56 | ROS_PACKAGE_PATH = rospkg.environment.ROS_PACKAGE_PATH | |
57 | ROS_ROOT = rospkg.environment.ROS_ROOT | |
58 | ||
59 | class ROSPkgException(Exception): | |
60 | """ | |
61 | Base class of package-related errors. | |
62 | """ | |
63 | pass | |
64 | class InvalidROSPkgException(ROSPkgException): | |
65 | """ | |
66 | Exception that indicates that a ROS package does not exist | |
67 | """ | |
68 | pass | |
69 | class MultipleNodesException(ROSPkgException): | |
70 | """ | |
71 | Exception that indicates that multiple ROS nodes by the same name are in the same package. | |
72 | """ | |
73 | pass | |
74 | ||
75 | # TODO: go through the code and eliminate unused methods -- there's far too many combos here | |
76 | ||
77 | MANIFEST_FILE = 'manifest.xml' | |
78 | ||
79 | # | |
80 | # Map package/directory structure | |
81 | # | |
82 | ||
83 | def get_dir_pkg(d): | |
84 | """ | |
85 | Get the package that the directory is contained within. This is | |
86 | determined by finding the nearest parent manifest.xml file. This | |
87 | isn't 100% reliable, but symlinks can fool any heuristic that | |
88 | relies on ROS_ROOT. | |
89 | @param d: directory path | |
90 | @type d: str | |
91 | @return: (package_directory, package) of the specified directory, or None,None if not in a package | |
92 | @rtype: (str, str) | |
93 | """ | |
94 | #TODO: the realpath is going to create issues with symlinks, most likely | |
95 | ||
96 | parent = os.path.dirname(os.path.realpath(d)) | |
97 | #walk up until we hit ros root or ros/pkg | |
98 | while not os.path.exists(os.path.join(d, MANIFEST_FILE)) and parent != d: | |
99 | d = parent | |
100 | parent = os.path.dirname(d) | |
101 | if os.path.exists(os.path.join(d, MANIFEST_FILE)): | |
102 | pkg = os.path.basename(os.path.abspath(d)) | |
103 | return d, pkg | |
104 | return None, None | |
105 | ||
106 | _pkg_dir_cache = {} | |
107 | ||
108 | def get_pkg_dir(package, required=True, ros_root=None, ros_package_path=None): | |
109 | """ | |
110 | Locate directory package is stored in. This routine uses an | |
111 | internal cache. | |
112 | ||
113 | NOTE: cache does *not* rebuild if packages are relocated after | |
114 | this process is initiated. | |
115 | ||
116 | @param package: package name | |
117 | @type package: str | |
118 | @param required: if True, an exception will be raised if the | |
119 | package directory cannot be located. | |
120 | @type required: bool | |
121 | @param ros_root: if specified, override ROS_ROOT | |
122 | @type ros_root: str | |
123 | @param ros_package_path: if specified, override ROS_PACKAGE_PATH | |
124 | @type ros_package_path: str | |
125 | @return: directory containing package or None if package cannot be found and required is False. | |
126 | @rtype: str | |
127 | @raise InvalidROSPkgException: if required is True and package cannot be located | |
128 | """ | |
129 | ||
130 | #UNIXONLY | |
131 | #TODO: replace with non-rospack-based solution (e.g. os.walk()) | |
132 | try: | |
133 | penv = os.environ.copy() | |
134 | if ros_root: | |
135 | ros_root = rospkg.environment._resolve_path(ros_root) | |
136 | penv[ROS_ROOT] = ros_root | |
137 | elif ROS_ROOT in os.environ: | |
138 | # record setting for _pkg_dir_cache | |
139 | ros_root = os.environ[ROS_ROOT] | |
140 | ||
141 | # determine rospack exe name | |
142 | rospack = 'rospack' | |
143 | ||
144 | if ros_package_path is not None: | |
145 | ros_package_path = rospkg.environment._resolve_paths(ros_package_path) | |
146 | penv[ROS_PACKAGE_PATH] = ros_package_path | |
147 | elif ROS_PACKAGE_PATH in os.environ: | |
148 | # record setting for _pkg_dir_cache | |
149 | ros_package_path = os.environ[ROS_PACKAGE_PATH] | |
150 | ||
151 | # update cache if we haven't. NOTE: we only get one cache | |
152 | if not _pkg_dir_cache: | |
153 | _read_rospack_cache(_pkg_dir_cache, ros_root, ros_package_path) | |
154 | ||
155 | # now that we've resolved the args, check the cache | |
156 | if package in _pkg_dir_cache: | |
157 | dir_, rr, rpp = _pkg_dir_cache[package] | |
158 | if rr == ros_root and rpp == ros_package_path: | |
159 | if os.path.isfile(os.path.join(dir_, MANIFEST_FILE)): | |
160 | return dir_ | |
161 | else: | |
162 | # invalidate cache | |
163 | _invalidate_cache(_pkg_dir_cache) | |
164 | ||
165 | rpout, rperr = Popen([rospack, 'find', package], \ | |
166 | stdout=PIPE, stderr=PIPE, env=penv).communicate() | |
167 | ||
168 | pkg_dir = (rpout or '').strip() | |
169 | #python3.1 popen returns as bytes | |
170 | if (isinstance(pkg_dir, bytes)): | |
171 | pkg_dir = pkg_dir.decode() | |
172 | if not pkg_dir: | |
173 | raise InvalidROSPkgException("Cannot locate installation of package %s: %s. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(package, rperr.strip(), ros_root, ros_package_path)) | |
174 | ||
175 | if not os.path.exists(pkg_dir): | |
176 | raise InvalidROSPkgException("Cannot locate installation of package %s: [%s] is not a valid path. ROS_ROOT[%s] ROS_PACKAGE_PATH[%s]"%(package, pkg_dir, ros_root, ros_package_path)) | |
177 | elif not os.path.isdir(pkg_dir): | |
178 | raise InvalidROSPkgException("Package %s is invalid: file [%s] is in the way"%(package, pkg_dir)) | |
179 | # don't update cache: this should only be updated from | |
180 | # rospack_cache as it will corrupt package list otherwise. | |
181 | #_pkg_dir_cache[package] = (pkg_dir, ros_root, ros_package_path) | |
182 | return pkg_dir | |
183 | except OSError as e: | |
184 | if required: | |
185 | raise InvalidROSPkgException("Environment configuration is invalid: cannot locate rospack (%s)"%e) | |
186 | return None | |
187 | except Exception as e: | |
188 | if required: | |
189 | raise | |
190 | return None | |
191 | ||
192 | def _get_pkg_subdir_by_dir(package_dir, subdir, required=True, env=None): | |
193 | """ | |
194 | @param required: if True, will attempt to create the subdirectory | |
195 | if it does not exist. An exception will be raised if this fails. | |
196 | @type required: bool | |
197 | @param package_dir: directory of package | |
198 | @type package_dir: str | |
199 | @param subdir: name of subdirectory to locate | |
200 | @type subdir: str | |
201 | @param env: override os.environ dictionary | |
202 | @type env: dict | |
203 | @param required: if True, directory must exist | |
204 | @type required: bool | |
205 | @return: Package subdirectory if package exist, otherwise None. | |
206 | @rtype: str | |
207 | @raise InvalidROSPkgException: if required is True and directory does not exist | |
208 | """ | |
209 | if env is None: | |
210 | env = os.environ | |
211 | try: | |
212 | if not package_dir: | |
213 | raise Exception("Cannot create a '%(subdir)s' directory in %(package_dir)s: package %(package) cannot be located"%locals()) | |
214 | d = os.path.join(package_dir, subdir) | |
215 | if required and os.path.isfile(d): | |
216 | raise Exception("""Package '%(package)s' is improperly configured: | |
217 | file %(d)s is preventing the creation of a directory"""%locals()) | |
218 | elif required and not os.path.isdir(d): | |
219 | try: | |
220 | os.makedirs(d) #lazy create | |
221 | except error: | |
222 | raise Exception("""Package '%(package)s' is improperly configured: | |
223 | Cannot create a '%(subdir)s' directory in %(package_dir)s. | |
224 | Please check permissions and try again. | |
225 | """%locals()) | |
226 | return d | |
227 | except Exception as e: | |
228 | if required: | |
229 | raise | |
230 | return None | |
231 | ||
232 | def get_pkg_subdir(package, subdir, required=True, env=None): | |
233 | """ | |
234 | @param required: if True, will attempt to create the subdirectory | |
235 | if it does not exist. An exception will be raised if this fails. | |
236 | @type required: bool | |
237 | @param package: name of package | |
238 | @type package: str | |
239 | @param env: override os.environ dictionary | |
240 | @type env: dict | |
241 | @param required: if True, directory must exist | |
242 | @type required: bool | |
243 | @return: Package subdirectory if package exist, otherwise None. | |
244 | @rtype: str | |
245 | @raise InvalidROSPkgException: if required is True and directory does not exist | |
246 | """ | |
247 | if env is None: | |
248 | env = os.environ | |
249 | pkg_dir = get_pkg_dir(package, required, ros_root=env[ROS_ROOT]) | |
250 | return _get_pkg_subdir_by_dir(pkg_dir, subdir, required, env) | |
251 | ||
252 | # | |
253 | # Map ROS resources to files | |
254 | # | |
255 | ||
256 | def resource_file(package, subdir, resource_name): | |
257 | """ | |
258 | @param subdir: name of subdir -- these should be one of the | |
259 | string constants, e.g. MSG_DIR | |
260 | @type subdir: str | |
261 | @return: path to resource in the specified subdirectory of the | |
262 | package, or None if the package does not exists | |
263 | @rtype: str | |
264 | @raise roslib.packages.InvalidROSPkgException: If package does not exist | |
265 | """ | |
266 | d = get_pkg_subdir(package, subdir, False) | |
267 | if d is None: | |
268 | raise InvalidROSPkgException(package) | |
269 | return os.path.join(d, resource_name) | |
270 | ||
271 | def _update_rospack_cache(env=None): | |
272 | """ | |
273 | Internal routine to update global package directory cache | |
274 | ||
275 | @return: True if cache is valid | |
276 | @rtype: bool | |
277 | """ | |
278 | if env is None: | |
279 | env = os.environ | |
280 | cache = _pkg_dir_cache | |
281 | if cache: | |
282 | return True | |
283 | ros_root = env[ROS_ROOT] | |
284 | ros_package_path = env.get(ROS_PACKAGE_PATH, '') | |
285 | return _read_rospack_cache(cache, ros_root, ros_package_path) | |
286 | ||
287 | def _invalidate_cache(cache): | |
288 | # I've only made this a separate routine because roslib.packages should really be using | |
289 | # the roslib.stacks cache implementation instead with the separate cache marker | |
290 | cache.clear() | |
291 | ||
292 | def _read_rospack_cache(cache, ros_root, ros_package_path): | |
293 | """ | |
294 | Read in rospack_cache data into cache. On-disk cache specifies a | |
295 | ROS_ROOT and ROS_PACKAGE_PATH, which must match the requested | |
296 | environment. | |
297 | ||
298 | @param cache: empty dictionary to store package list in. | |
299 | If no cache argument provided, will use internal _pkg_dir_cache | |
300 | and will return cached answers if available. | |
301 | The format of the cache is {package_name: dir_path, ros_root, ros_package_path}. | |
302 | @type cache: {str: str, str, str} | |
303 | @param ros_package_path: ROS_ROOT value | |
304 | @type ros_root: str | |
305 | @param ros_package_path: ROS_PACKAGE_PATH value or '' if not specified | |
306 | @type ros_package_path: str | |
307 | @return: True if on-disk cache matches and was loaded, false otherwise | |
308 | @rtype: bool | |
309 | """ | |
310 | try: | |
311 | with open(os.path.join(rospkg.get_ros_home(), 'rospack_cache')) as f: | |
312 | for l in f.readlines(): | |
313 | l = l[:-1] | |
314 | if not len(l): | |
315 | continue | |
316 | if l[0] == '#': | |
317 | # check that the cache matches our env | |
318 | if l.startswith('#ROS_ROOT='): | |
319 | if not l[len('#ROS_ROOT='):] == ros_root: | |
320 | return False | |
321 | elif l.startswith('#ROS_PACKAGE_PATH='): | |
322 | if not l[len('#ROS_PACKAGE_PATH='):] == ros_package_path: | |
323 | return False | |
324 | else: | |
325 | cache[os.path.basename(l)] = l, ros_root, ros_package_path | |
326 | return True | |
327 | except: | |
328 | pass | |
329 | ||
330 | def list_pkgs_by_path(path, packages=None, cache=None, env=None): | |
331 | """ | |
332 | List ROS packages within the specified path. | |
333 | ||
334 | Optionally, a cache dictionary can be provided, which will be | |
335 | updated with the package->path mappings. list_pkgs_by_path() does | |
336 | NOT returned cached results -- it only updates the cache. | |
337 | ||
338 | @param path: path to list packages in | |
339 | @type path: str | |
340 | @param packages: list of packages to append to. If package is | |
341 | already present in packages, it will be ignored. | |
342 | @type packages: [str] | |
343 | @param cache: (optional) package path cache to update. Maps package name to directory path. | |
344 | @type cache: {str: str} | |
345 | @return: complete list of package names in ROS environment. Same as packages parameter. | |
346 | @rtype: [str] | |
347 | """ | |
348 | if packages is None: | |
349 | packages = [] | |
350 | if env is None: | |
351 | env = os.environ | |
352 | # record settings for cache | |
353 | ros_root = env[ROS_ROOT] | |
354 | ros_package_path = env.get(ROS_PACKAGE_PATH, '') | |
355 | ||
356 | path = os.path.abspath(path) | |
357 | for d, dirs, files in os.walk(path, topdown=True): | |
358 | if MANIFEST_FILE in files: | |
359 | package = os.path.basename(d) | |
360 | if package not in packages: | |
361 | packages.append(package) | |
362 | if cache is not None: | |
363 | cache[package] = d, ros_root, ros_package_path | |
364 | del dirs[:] | |
365 | continue #leaf | |
366 | elif 'rospack_nosubdirs' in files: | |
367 | del dirs[:] | |
368 | continue #leaf | |
369 | #small optimization | |
370 | elif '.svn' in dirs: | |
371 | dirs.remove('.svn') | |
372 | elif '.git' in dirs: | |
373 | dirs.remove('.git') | |
374 | ||
375 | for sub_d in dirs: | |
376 | # followlinks=True only available in Python 2.6, so we | |
377 | # have to implement manually | |
378 | sub_p = os.path.join(d, sub_d) | |
379 | if os.path.islink(sub_p): | |
380 | packages.extend(list_pkgs_by_path(sub_p, cache=cache)) | |
381 | ||
382 | return packages | |
383 | ||
384 | def find_node(pkg, node_type, rospack=None): | |
385 | """ | |
386 | Warning: unstable API due to catkin. | |
387 | ||
388 | Locate the executable that implements the node | |
389 | ||
390 | :param node_type: type of node, ``str`` | |
391 | :returns: path to node or None if node is not in the package ``str`` | |
392 | :raises: :exc:rospkg.ResourceNotFound` If package does not exist | |
393 | """ | |
394 | ||
395 | if rospack is None: | |
396 | rospack = rospkg.RosPack() | |
397 | return find_resource(pkg, node_type, filter_fn=_executable_filter, rospack=rospack) | |
398 | ||
399 | def _executable_filter(test_path): | |
400 | s = os.stat(test_path) | |
401 | return (s.st_mode & (stat.S_IRUSR | stat.S_IXUSR) == (stat.S_IRUSR | stat.S_IXUSR)) | |
402 | ||
403 | def _find_resource(d, resource_name, filter_fn=None): | |
404 | """ | |
405 | subroutine of find_resource | |
406 | """ | |
407 | matches = [] | |
408 | # TODO: figure out how to generalize find_resource to take multiple resource name options | |
409 | if sys.platform in ['win32', 'cygwin']: | |
410 | # Windows logic requires more file patterns to resolve and is | |
411 | # not case-sensitive, so leave it separate | |
412 | ||
413 | # in the near-term, just hack in support for .exe/.bat. In the long | |
414 | # term this needs to: | |
415 | # | |
416 | # * parse PATHEXT to generate matches | |
417 | # * perform case-insensitive compares against potential | |
418 | # matches, in path-ext order | |
419 | ||
420 | # - We still have to look for bare node_type as user may have | |
421 | # specified extension manually | |
422 | resource_name = resource_name.lower() | |
423 | patterns = [resource_name, resource_name+'.exe', resource_name+'.bat'] | |
424 | for p, dirs, files in os.walk(d): | |
425 | # case insensitive | |
426 | files = [f.lower() for f in files] | |
427 | for name in patterns: | |
428 | if name in files: | |
429 | test_path = os.path.join(p, name) | |
430 | if filter_fn is not None: | |
431 | if filter_fn(test_path): | |
432 | matches.append(test_path) | |
433 | else: | |
434 | matches.append(test_path) | |
435 | # remove .svn/.git/etc | |
436 | to_prune = [x for x in dirs if x.startswith('.')] | |
437 | for x in to_prune: | |
438 | dirs.remove(x) | |
439 | else: #UNIX | |
440 | for p, dirs, files in os.walk(d): | |
441 | if resource_name in files: | |
442 | test_path = os.path.join(p, resource_name) | |
443 | if filter_fn is not None: | |
444 | if filter_fn(test_path): | |
445 | matches.append(test_path) | |
446 | else: | |
447 | matches.append(test_path) | |
448 | # remove .svn/.git/etc | |
449 | to_prune = [x for x in dirs if x.startswith('.')] | |
450 | for x in to_prune: | |
451 | dirs.remove(x) | |
452 | return [os.path.abspath(m) for m in matches] | |
453 | ||
454 | # TODO: this routine really belongs in rospkg, but the catkin-isms really, really don't | |
455 | # belong in rospkg. With more thought, they can probably be abstracted out so as | |
456 | # to no longer be catkin-specific. | |
457 | def find_resource(pkg, resource_name, filter_fn=None, rospack=None): | |
458 | """ | |
459 | Warning: unstable API due to catkin. | |
460 | ||
461 | Locate the file named resource_name in package, optionally | |
462 | matching specified filter. find_resource() will return a list of | |
463 | matches, but only for a given scope. If the resource is found in | |
464 | the binary build directory, it will only return matches in that | |
465 | directory; it will not return matches from the ROS_PACKAGE_PATH as | |
466 | well in this case. | |
467 | ||
468 | :param filter: function that takes in a path argument and | |
469 | returns True if the it matches the desired resource, ``fn(str)`` | |
470 | :param rospack: `rospkg.RosPack` instance to use | |
471 | :returns: lists of matching paths for resource within a given scope, ``[str]`` | |
472 | :raises: :exc:`rospkg.ResourceNotFound` If package does not exist | |
473 | """ | |
474 | ||
475 | # New resource-location policy in Fuerte, induced by the new catkin | |
476 | # build system: | |
477 | # (1) Use catkin_find to find libexec and share locations, look | |
478 | # recursively there. If the resource is found, done. | |
479 | # Else continue: | |
480 | # (2) If ROS_PACKAGE_PATH is set, look recursively there. If the | |
481 | # resource is found, done. Else raise | |
482 | # | |
483 | # NOTE: package *must* exist on ROS_PACKAGE_PATH no matter what | |
484 | ||
485 | if rospack is None: | |
486 | rospack = rospkg.RosPack() | |
487 | ||
488 | # lookup package as it *must* exist | |
489 | pkg_path = rospack.get_path(pkg) | |
490 | ||
491 | # if found in binary dir, start with that. in any case, use matches | |
492 | # from ros_package_path | |
493 | matches = [] | |
494 | for search_dirs in ['libexec', 'share']: | |
495 | try: | |
496 | search_paths, _ = catkin_find(search_dirs=[search_dirs], project=pkg) | |
497 | for search_path in search_paths: | |
498 | matches.extend(_find_resource(search_path, resource_name, filter_fn=filter_fn)) | |
499 | except RuntimeError: | |
500 | pass | |
501 | matches.extend(_find_resource(pkg_path, resource_name, filter_fn=filter_fn)) | |
502 | # Uniquify the results, in case we found the same file twice | |
503 | return list(set(matches)) |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | ||
33 | """ | |
34 | Warning: do not use this library. It is unstable and most of the routines | |
35 | here have been superceded by other libraries (e.g. rospkg). These | |
36 | routines will likely be *deleted* in future releases. | |
37 | """ | |
38 | ||
39 | import os | |
40 | ||
41 | import roslib.manifest | |
42 | import roslib.names | |
43 | import roslib.packages | |
44 | ||
45 | def _get_manifest_by_dir(package_dir): | |
46 | """ | |
47 | Helper routine for loading Manifest instances | |
48 | @param package_dir: package directory location | |
49 | @type package_dir: str | |
50 | @return: manifest for package | |
51 | @rtype: Manifest | |
52 | """ | |
53 | f = os.path.join(package_dir, roslib.manifest.MANIFEST_FILE) | |
54 | if f: | |
55 | return roslib.manifest.parse_file(f) | |
56 | else: | |
57 | return None | |
58 | ||
59 | def list_package_resources_by_dir(package_dir, include_depends, subdir, rfilter=os.path.isfile): | |
60 | """ | |
61 | List resources in a package directory within a particular | |
62 | subdirectory. This is useful for listing messages, services, etc... | |
63 | @param package_dir: package directory location | |
64 | @type package_dir: str | |
65 | @param subdir: name of subdirectory | |
66 | @type subdir: str | |
67 | @param include_depends: if True, include resources in dependencies as well | |
68 | @type include_depends: bool | |
69 | @param rfilter: resource filter function that returns true if filename is the desired resource type | |
70 | @type rfilter: fn(filename)->bool | |
71 | """ | |
72 | package = os.path.basename(package_dir) | |
73 | resources = [] | |
74 | dir = roslib.packages._get_pkg_subdir_by_dir(package_dir, subdir, False) | |
75 | if os.path.isdir(dir): | |
76 | resources = [roslib.names.resource_name(package, f, my_pkg=package) \ | |
77 | for f in os.listdir(dir) if rfilter(os.path.join(dir, f))] | |
78 | else: | |
79 | resources = [] | |
80 | if include_depends: | |
81 | depends = _get_manifest_by_dir(package_dir).depends | |
82 | dirs = [roslib.packages.get_pkg_subdir(d.package, subdir, False) for d in depends] | |
83 | for (dep, dir_) in zip(depends, dirs): #py3k | |
84 | if not dir_ or not os.path.isdir(dir_): | |
85 | continue | |
86 | resources.extend(\ | |
87 | [roslib.names.resource_name(dep.package, f, my_pkg=package) \ | |
88 | for f in os.listdir(dir_) if rfilter(os.path.join(dir_, f))]) | |
89 | return resources | |
90 | ||
91 | def list_package_resources(package, include_depends, subdir, rfilter=os.path.isfile): | |
92 | """ | |
93 | List resources in a package within a particular subdirectory. This is useful for listing | |
94 | messages, services, etc... | |
95 | @param package: package name | |
96 | @type package: str | |
97 | @param subdir: name of subdirectory | |
98 | @type subdir: str | |
99 | @param include_depends: if True, include resources in dependencies as well | |
100 | @type include_depends: bool | |
101 | @param rfilter: resource filter function that returns true if filename is the desired resource type | |
102 | @type rfilter: fn(filename)->bool | |
103 | """ | |
104 | package_dir = roslib.packages.get_pkg_dir(package) | |
105 | return list_package_resources_by_dir(package_dir, include_depends, subdir, rfilter) | |
106 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | ||
35 | """ | |
36 | Warning: do not use this library. It is unstable and most of the routines | |
37 | here have been superceded by other libraries (e.g. rospkg). These | |
38 | routines will likely be *deleted* in future releases. | |
39 | """ | |
40 | ||
41 | import os | |
42 | import sys | |
43 | ||
44 | # Global, usually set in setup | |
45 | ROS_ROOT = "ROS_ROOT" | |
46 | ROS_MASTER_URI = "ROS_MASTER_URI" | |
47 | ROS_PACKAGE_PATH = "ROS_PACKAGE_PATH" | |
48 | ROS_HOME = "ROS_HOME" | |
49 | ||
50 | # Build-related | |
51 | ROS_BINDEPS_PATH = "ROS_BINDEPS_PATH" | |
52 | ROS_BOOST_ROOT = "ROS_BOOST_ROOT" | |
53 | ||
54 | # Per session | |
55 | ## hostname/address to bind XML-RPC services to. | |
56 | ROS_IP ="ROS_IP" | |
57 | ROS_HOSTNAME ="ROS_HOSTNAME" | |
58 | ROS_NAMESPACE ="ROS_NAMESPACE" | |
59 | ## directory in which log files are written | |
60 | ROS_LOG_DIR ="ROS_LOG_DIR" | |
61 | ## directory in which test result files are written | |
62 | CATKIN_TEST_RESULTS_DIR = "CATKIN_TEST_RESULTS_DIR" | |
63 | ||
64 | class ROSEnvException(Exception): | |
65 | """Base class of roslib.rosenv errors.""" | |
66 | pass | |
67 | ||
68 | import warnings | |
69 | warnings.warn("roslib.rosenv is deprecated, please use rospkg or rosgraph.rosenv", stacklevel=2) | |
70 | ||
71 | def get_ros_root(required=True, env=None): | |
72 | """ | |
73 | @param required: (default True). If True, ROS_ROOT must be set and point to a valid directory. | |
74 | @type required: bool | |
75 | @param env: override environment dictionary | |
76 | @type env: dict | |
77 | @raise ROSEnvException: if required is True and ROS_ROOT is not set | |
78 | """ | |
79 | if env is None: | |
80 | env = os.environ | |
81 | p = None | |
82 | try: | |
83 | if ROS_ROOT not in env: | |
84 | raise ROSEnvException(""" | |
85 | The %(ROS_ROOT)s environment variable has not been set. | |
86 | Please set to the location of your ROS installation | |
87 | before continuing. | |
88 | """%globals()) | |
89 | ||
90 | return env[ROS_ROOT] | |
91 | except Exception as e: | |
92 | if required: | |
93 | raise | |
94 | return p | |
95 | ||
96 | def get_ros_package_path(required=False, env=None): | |
97 | """ | |
98 | @param required: (default False) if True, ROS_PACKAGE_PATH must be | |
99 | set and point to a valid directory. | |
100 | @type required: bool | |
101 | @raise ROSEnvException: if ROS_PACKAGE_PATH is not set and \a | |
102 | required is True | |
103 | """ | |
104 | if env is None: | |
105 | env = os.environ | |
106 | try: | |
107 | return env[ROS_PACKAGE_PATH] | |
108 | except KeyError as e: | |
109 | if required: | |
110 | raise ROSEnvException("%s has not been configured"%ROS_PACKAGE_PATH) | |
111 | ||
112 | def get_master_uri(required=True, env=None, argv=None): | |
113 | """ | |
114 | Get the ROS_MASTER_URI setting from the command-line args or | |
115 | environment, command-line args takes precedence. | |
116 | @param required: if True, enables exception raising | |
117 | @type required: bool | |
118 | @param env: override environment dictionary | |
119 | @type env: dict | |
120 | @param argv: override sys.argv | |
121 | @type argv: [str] | |
122 | @raise ROSEnvException: if ROS_MASTER_URI value is invalidly | |
123 | specified or if required and ROS_MASTER_URI is not set | |
124 | """ | |
125 | if env is None: | |
126 | env = os.environ | |
127 | if argv is None: | |
128 | argv = sys.argv | |
129 | try: | |
130 | for arg in argv: | |
131 | if arg.startswith('__master:='): | |
132 | val = None | |
133 | try: | |
134 | _, val = arg.split(':=') | |
135 | except: | |
136 | pass | |
137 | ||
138 | # we ignore required here because there really is no | |
139 | # correct return value as the configuration is bad | |
140 | # rather than unspecified | |
141 | if not val: | |
142 | raise ROSEnvException("__master remapping argument '%s' improperly specified"%arg) | |
143 | return val | |
144 | return env[ROS_MASTER_URI] | |
145 | except KeyError as e: | |
146 | if required: | |
147 | raise ROSEnvException("%s has not been configured"%ROS_MASTER_URI) | |
148 | ||
149 | def get_ros_home(env=None): | |
150 | """ | |
151 | Get directory location of '.ros' directory (aka ROS home). | |
152 | possible locations for this. The ROS_LOG_DIR environment variable | |
153 | has priority. If that is not set, then ROS_HOME/log is used. If | |
154 | ROS_HOME is not set, $HOME/.ros/log is used. | |
155 | ||
156 | @param env: override os.environ dictionary | |
157 | @type env: dict | |
158 | @return: path to use use for log file directory | |
159 | @rtype: str | |
160 | """ | |
161 | if env is None: | |
162 | env = os.environ | |
163 | if ROS_HOME in env: | |
164 | return env[ROS_HOME] | |
165 | else: | |
166 | #slightly more robust than $HOME | |
167 | return os.path.join(os.path.expanduser('~'), '.ros') | |
168 | ||
169 | def get_log_dir(env=None): | |
170 | """ | |
171 | Get directory to use for writing log files. There are multiple | |
172 | possible locations for this. The ROS_LOG_DIR environment variable | |
173 | has priority. If that is not set, then ROS_HOME/log is used. If | |
174 | ROS_HOME is not set, $HOME/.ros/log is used. | |
175 | ||
176 | @param env: override os.environ dictionary | |
177 | @type env: dict | |
178 | @return: path to use use for log file directory | |
179 | @rtype: str | |
180 | """ | |
181 | if env is None: | |
182 | env = os.environ | |
183 | if ROS_LOG_DIR in env: | |
184 | return env[ROS_LOG_DIR] | |
185 | else: | |
186 | return os.path.join(get_ros_home(env), 'log') | |
187 | ||
188 | def get_test_results_dir(env=None): | |
189 | """ | |
190 | Get directory to use for writing test result files. There are multiple | |
191 | possible locations for this. The CATKIN_TEST_RESULTS_DIR environment variable | |
192 | has priority. If that is set, CATKIN_TEST_RESULTS_DIR is returned. | |
193 | If CATKIN_TEST_RESULTS_DIR is not set, then ROS_HOME/test_results is used. If | |
194 | ROS_HOME is not set, $HOME/.ros/test_results is used. | |
195 | ||
196 | @param env: environment dictionary (defaults to os.environ) | |
197 | @type env: dict | |
198 | @return: path to use use for log file directory | |
199 | @rtype: str | |
200 | """ | |
201 | if env is None: | |
202 | env = os.environ | |
203 | ||
204 | if CATKIN_TEST_RESULTS_DIR in env: | |
205 | return env[CATKIN_TEST_RESULTS_DIR] | |
206 | else: | |
207 | return os.path.join(get_ros_home(env), 'test_results') | |
208 | ||
209 | # this is a copy of the roslogging utility. it's been moved here as it is a common | |
210 | # routine for programs using accessing ROS directories | |
211 | def makedirs_with_parent_perms(p): | |
212 | """ | |
213 | Create the directory using the permissions of the nearest | |
214 | (existing) parent directory. This is useful for logging, where a | |
215 | root process sometimes has to log in the user's space. | |
216 | @param p: directory to create | |
217 | @type p: str | |
218 | """ | |
219 | p = os.path.abspath(p) | |
220 | parent = os.path.dirname(p) | |
221 | # recurse upwards, checking to make sure we haven't reached the | |
222 | # top | |
223 | if not os.path.exists(p) and p and parent != p: | |
224 | makedirs_with_parent_perms(parent) | |
225 | s = os.stat(parent) | |
226 | os.mkdir(p) | |
227 | ||
228 | # if perms of new dir don't match, set anew | |
229 | s2 = os.stat(p) | |
230 | if s.st_uid != s2.st_uid or s.st_gid != s2.st_gid: | |
231 | os.chown(p, s.st_uid, s.st_gid) | |
232 | if s.st_mode != s2.st_mode: | |
233 | os.chmod(p, s.st_mode) |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | # $Author$ | |
34 | ||
35 | """ | |
36 | Warning: do not use this library. It is unstable and most of the routines | |
37 | here have been superceded by other libraries (e.g. rospkg). These | |
38 | routines will likely be *deleted* in future releases. | |
39 | """ | |
40 | ||
41 | import os | |
42 | import sys | |
43 | import subprocess | |
44 | import roslib.exceptions | |
45 | import rospkg | |
46 | ||
47 | if sys.hexversion > 0x03000000: #Python3 | |
48 | python3 = True | |
49 | else: | |
50 | python3 = False | |
51 | ||
52 | import warnings | |
53 | warnings.warn("roslib.rospack is deprecated, please use rospkg", stacklevel=2) | |
54 | ||
55 | def rospackexec(args): | |
56 | """ | |
57 | @return: result of executing rospack command (via subprocess). string will be strip()ed. | |
58 | @rtype: str | |
59 | @raise roslib.exceptions.ROSLibException: if rospack command fails | |
60 | """ | |
61 | rospack_bin = 'rospack' | |
62 | if python3: | |
63 | val = subprocess.Popen([rospack_bin] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] | |
64 | val = val.decode().strip() | |
65 | else: | |
66 | val = (subprocess.Popen([rospack_bin] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] or '').strip() | |
67 | if val.startswith('rospack:'): #rospack error message | |
68 | raise roslib.exceptions.ROSLibException(val) | |
69 | return val | |
70 | ||
71 | def rospack_depends_on_1(pkg): | |
72 | """ | |
73 | @param pkg: package name | |
74 | @type pkg: str | |
75 | @return: A list of the names of the packages which depend directly on pkg | |
76 | @rtype: list | |
77 | """ | |
78 | return rospackexec(['depends-on1', pkg]).split() | |
79 | ||
80 | def rospack_depends_on(pkg): | |
81 | """ | |
82 | @param pkg: package name | |
83 | @type pkg: str | |
84 | @return: A list of the names of the packages which depend on pkg | |
85 | @rtype: list | |
86 | """ | |
87 | return rospackexec(['depends-on', pkg]).split() | |
88 | ||
89 | def rospack_depends_1(pkg): | |
90 | """ | |
91 | @param pkg: package name | |
92 | @type pkg: str | |
93 | @return: A list of the names of the packages which pkg directly depends on | |
94 | @rtype: list | |
95 | """ | |
96 | return rospackexec(['deps1', pkg]).split() | |
97 | ||
98 | def rospack_depends(pkg): | |
99 | """ | |
100 | @param pkg: package name | |
101 | @type pkg: str | |
102 | @return: A list of the names of the packages which pkg depends on | |
103 | @rtype: list | |
104 | """ | |
105 | return rospackexec(['deps', pkg]).split() | |
106 | ||
107 | def rospack_plugins(pkg): | |
108 | """ | |
109 | @param pkg: package name | |
110 | @type pkg: str | |
111 | @return: A list of the names of the packages which provide a plugin for pkg | |
112 | @rtype: list | |
113 | """ | |
114 | val = rospackexec(['plugins', '--attrib=plugin', pkg]) | |
115 | if val: | |
116 | return [tuple(x.split(' ')) for x in val.split('\n')] | |
117 | else: | |
118 | return [] | |
119 | ||
120 | def rosstackexec(args): | |
121 | """ | |
122 | @return: result of executing rosstack command (via subprocess). string will be strip()ed. | |
123 | @rtype: str | |
124 | @raise roslib.exceptions.ROSLibException: if rosstack command fails | |
125 | """ | |
126 | rosstack_bin = 'rosstack' | |
127 | if python3: | |
128 | val = subprocess.Popen([rosstack_bin] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] | |
129 | val = val.decode().strip() | |
130 | else: | |
131 | val = (subprocess.Popen([rosstack_bin] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0] or '').strip() | |
132 | if val.startswith('rosstack:'): #rospack error message | |
133 | raise roslib.exceptions.ROSLibException(val) | |
134 | return val | |
135 | ||
136 | def rosstack_depends_on(s): | |
137 | """ | |
138 | @param s: stack name | |
139 | @type s: str | |
140 | @return: A list of the names of the stacks which depend on s | |
141 | @rtype: list | |
142 | """ | |
143 | return rosstackexec(['depends-on', s]).split() | |
144 | ||
145 | def rosstack_depends_on_1(s): | |
146 | """ | |
147 | @param s: stack name | |
148 | @type s: str | |
149 | @return: A list of the names of the stacks which depend directly on s | |
150 | @rtype: list | |
151 | """ | |
152 | return rosstackexec(['depends-on1', s]).split() | |
153 | ||
154 | def rosstack_depends(s): | |
155 | """ | |
156 | @param s: stack name | |
157 | @type s: str | |
158 | @return: A list of the names of the stacks which s depends on | |
159 | @rtype: list | |
160 | """ | |
161 | return rosstackexec(['depends', s]).split() | |
162 | ||
163 | def rosstack_depends_1(s): | |
164 | """ | |
165 | @param s: stack name | |
166 | @type s: str | |
167 | @return: A list of the names of the stacks which s depends on directly | |
168 | @rtype: list | |
169 | """ | |
170 | return rosstackexec(['depends1', s]).split() |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | # $Author$ | |
34 | ||
35 | """ | |
36 | Warning: do not use this library. It is unstable and most of the routines | |
37 | here have been superceded by other libraries (e.g. rospkg). These | |
38 | routines will likely be *deleted* in future releases. | |
39 | """ | |
40 | ||
41 | import os | |
42 | import sys | |
43 | ||
44 | import roslib.names | |
45 | ||
46 | ## caller ID for master calls where caller ID is not vital | |
47 | _GLOBAL_CALLER_ID = '/script' | |
48 | ||
49 | ||
50 | import warnings | |
51 | def deprecated(func): | |
52 | """This is a decorator which can be used to mark functions | |
53 | as deprecated. It will result in a warning being emmitted | |
54 | when the function is used.""" | |
55 | def newFunc(*args, **kwargs): | |
56 | warnings.warn("Call to deprecated function %s." % func.__name__, | |
57 | category=DeprecationWarning, stacklevel=2) | |
58 | return func(*args, **kwargs) | |
59 | newFunc.__name__ = func.__name__ | |
60 | newFunc.__doc__ = func.__doc__ | |
61 | newFunc.__dict__.update(func.__dict__) | |
62 | return newFunc | |
63 | ||
64 | @deprecated | |
65 | def script_resolve_name(script_name, name): | |
66 | """ | |
67 | Name resolver for scripts. Supports ROS_NAMESPACE. Does not | |
68 | support remapping arguments. | |
69 | @param name: name to resolve | |
70 | @type name: str | |
71 | @param script_name: name of script. script_name must not | |
72 | contain a namespace. | |
73 | @type script_name: str | |
74 | @return: resolved name | |
75 | @rtype: str | |
76 | """ | |
77 | if not name: #empty string resolves to namespace | |
78 | return roslib.names.get_ros_namespace() | |
79 | #Check for global name: /foo/name resolves to /foo/name | |
80 | if roslib.names.is_global(name): | |
81 | return name | |
82 | #Check for private name: ~name resolves to /caller_id/name | |
83 | elif roslib.names.is_private(name): | |
84 | return ns_join(roslib.names.make_caller_id(script_name), name[1:]) | |
85 | return roslib.names.get_ros_namespace() + name | |
86 | ||
87 | @deprecated | |
88 | def get_master(): | |
89 | """ | |
90 | Get an XMLRPC handle to the Master. It is recommended to use the | |
91 | `rosgraph.masterapi` library instead, as it provides many | |
92 | conveniences. | |
93 | ||
94 | @return: XML-RPC proxy to ROS master | |
95 | @rtype: xmlrpclib.ServerProxy | |
96 | @raises ValueError if master URI is invalid | |
97 | """ | |
98 | try: | |
99 | import xmlrpc.client as xmlrpcclient #Python 3.x | |
100 | except ImportError: | |
101 | import xmlrpclib as xmlrpcclient #Python 2.x | |
102 | ||
103 | # changed this to not look as sys args and remove dependency on roslib.rosenv for cleaner cleanup | |
104 | uri = os.environ['ROS_MASTER_URI'] | |
105 | return xmlrpcclient.ServerProxy(uri) | |
106 | ||
107 | @deprecated | |
108 | def get_param_server(): | |
109 | """ | |
110 | @return: ServerProxy XML-RPC proxy to ROS parameter server | |
111 | @rtype: xmlrpclib.ServerProxy | |
112 | """ | |
113 | return get_master() |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | # $Author$ | |
34 | """ | |
35 | ROS Service Description Language Spec | |
36 | Implements U{http://ros.org/wiki/srv} | |
37 | """ | |
38 | ||
39 | import os | |
40 | import sys | |
41 | import re | |
42 | ||
43 | try: | |
44 | from cStringIO import StringIO # Python 2.x | |
45 | except ImportError: | |
46 | from io import StringIO # Python 3.x | |
47 | ||
48 | import roslib.msgs | |
49 | import roslib.names | |
50 | import roslib.packages | |
51 | import roslib.resources | |
52 | ||
53 | # don't directly use code from this, though we do depend on the | |
54 | # manifest.Depend data type | |
55 | import roslib.manifest | |
56 | ||
57 | ## file extension | |
58 | EXT = '.srv' #alias | |
59 | SEP = '/' #e.g. std_msgs/String | |
60 | ## input/output deliminator | |
61 | IODELIM = '---' | |
62 | COMMENTCHAR = roslib.msgs.COMMENTCHAR | |
63 | ||
64 | VERBOSE = False | |
65 | ## @return: True if msg-related scripts should print verbose output | |
66 | def is_verbose(): | |
67 | return VERBOSE | |
68 | ||
69 | ## set whether msg-related scripts should print verbose output | |
70 | def set_verbose(v): | |
71 | global VERBOSE | |
72 | VERBOSE = v | |
73 | ||
74 | class SrvSpecException(Exception): pass | |
75 | ||
76 | # msg spec representation ########################################## | |
77 | ||
78 | class SrvSpec(object): | |
79 | ||
80 | def __init__(self, request, response, text, full_name = '', short_name = '', package = ''): | |
81 | self.request = request | |
82 | self.response = response | |
83 | self.text = text | |
84 | self.full_name = full_name | |
85 | self.short_name = short_name | |
86 | self.package = package | |
87 | ||
88 | def __eq__(self, other): | |
89 | if not other or not isinstance(other, SrvSpec): | |
90 | return False | |
91 | return self.request == other.request and \ | |
92 | self.response == other.response and \ | |
93 | self.text == other.text and \ | |
94 | self.full_name == other.full_name and \ | |
95 | self.short_name == other.short_name and \ | |
96 | self.package == other.package | |
97 | ||
98 | def __ne__(self, other): | |
99 | if not other or not isinstance(other, SrvSpec): | |
100 | return True | |
101 | return not self.__eq__(other) | |
102 | ||
103 | def __repr__(self): | |
104 | return "SrvSpec[%s, %s]"%(repr(self.request), repr(self.response)) | |
105 | ||
106 | # srv spec loading utilities ########################################## | |
107 | ||
108 | ## @internal | |
109 | ## predicate for filtering directory list. matches message files | |
110 | def _srv_filter(f): | |
111 | return os.path.isfile(f) and f.endswith(EXT) | |
112 | ||
113 | # also used by doxymaker | |
114 | def list_srv_types(package, include_depends): | |
115 | """ | |
116 | list all services in the specified package | |
117 | @param package: name of package to search | |
118 | @type package: str | |
119 | @param include_depends: if True, will also list services in package dependencies | |
120 | @type include_depends: bool | |
121 | @return: service type names | |
122 | @rtype: [str] | |
123 | """ | |
124 | types = roslib.resources.list_package_resources(package, include_depends, 'srv', _srv_filter) | |
125 | return [x[:-len(EXT)] for x in types] | |
126 | ||
127 | def srv_file(package, type_): | |
128 | """ | |
129 | @param package: name of package .srv file is in | |
130 | @type package: str | |
131 | @param type_: type name of service | |
132 | @type type_: str | |
133 | @return: file path of .srv file in specified package | |
134 | @rtype: str | |
135 | """ | |
136 | return roslib.packages.resource_file(package, 'srv', type_+EXT) | |
137 | ||
138 | def get_pkg_srv_specs(package): | |
139 | """ | |
140 | List all messages that a package contains | |
141 | @param depend: roslib.manifest.Depend object representing package | |
142 | to load messages from | |
143 | @type depend: Depend | |
144 | @return: list of message type names and specs for package, as well as a list | |
145 | of message names that could not be processed. | |
146 | @rtype: [(str,roslib.MsgSpec), [str]] | |
147 | """ | |
148 | #almost identical to roslib.msgs.get_pkg_msg_specs | |
149 | types = list_srv_types(package, False) | |
150 | specs = [] #no fancy list comprehension as we want to show errors | |
151 | failures = [] | |
152 | for t in types: | |
153 | try: | |
154 | spec = load_from_file(srv_file(package, t), package) | |
155 | specs.append(spec) | |
156 | except Exception as e: | |
157 | failures.append(t) | |
158 | sys.stderr.write("ERROR: unable to load %s\n"%(t)) | |
159 | return specs, failures | |
160 | ||
161 | def load_from_string(text, package_context='', full_name='', short_name=''): | |
162 | """ | |
163 | @param text: .msg text | |
164 | @type text: str | |
165 | @param package_context: context to use for msgTypeName, i.e. the package name, | |
166 | or '' to use local naming convention. | |
167 | @type package_context: str | |
168 | @return: Message type name and message specification | |
169 | @rtype: roslib.MsgSpec | |
170 | @raise roslib.MsgSpecException: if syntax errors or other problems are detected in file | |
171 | """ | |
172 | text_in = StringIO() | |
173 | text_out = StringIO() | |
174 | accum = text_in | |
175 | for l in text.split('\n'): | |
176 | l = l.split(COMMENTCHAR)[0].strip() #strip comments | |
177 | if l.startswith(IODELIM): #lenient, by request | |
178 | accum = text_out | |
179 | else: | |
180 | accum.write(l+'\n') | |
181 | # create separate roslib.msgs objects for each half of file | |
182 | ||
183 | msg_in = roslib.msgs.load_from_string(text_in.getvalue(), package_context, '%sRequest'%(full_name), '%sRequest'%(short_name)) | |
184 | msg_out = roslib.msgs.load_from_string(text_out.getvalue(), package_context, '%sResponse'%(full_name), '%sResponse'%(short_name)) | |
185 | return SrvSpec(msg_in, msg_out, text, full_name, short_name, package_context) | |
186 | ||
187 | def load_from_file(file_name, package_context=''): | |
188 | """ | |
189 | Convert the .srv representation in the file to a SrvSpec instance. | |
190 | @param file_name: name of file to load from | |
191 | @type file_name: str | |
192 | @param package_context: context to use for type name, i.e. the package name, | |
193 | or '' to use local naming convention. | |
194 | @type package_context: str | |
195 | @return: Message type name and message specification | |
196 | @rtype: (str, L{SrvSpec}) | |
197 | @raise SrvSpecException: if syntax errors or other problems are detected in file | |
198 | """ | |
199 | if VERBOSE: | |
200 | if package_context: | |
201 | sys.stdout.write("Load spec from %s into namespace [%s]\n"%(file_name, package_context)) | |
202 | else: | |
203 | sys.stdout.write("Load spec from %s\n"%(file_name)) | |
204 | base_file_name = os.path.basename(file_name) | |
205 | type_ = base_file_name[:-len(EXT)] | |
206 | base_type_ = type_ | |
207 | # determine the type name | |
208 | if package_context: | |
209 | while package_context.endswith(SEP): | |
210 | package_context = package_context[:-1] #strip message separators | |
211 | type_ = "%s%s%s"%(package_context, SEP, type_) | |
212 | if not roslib.names.is_legal_resource_name(type_): | |
213 | raise SrvSpecException("%s: %s is not a legal service type name"%(file_name, type_)) | |
214 | ||
215 | f = open(file_name, 'r') | |
216 | try: | |
217 | text = f.read() | |
218 | return (type_, load_from_string(text, package_context, type_, base_type_)) | |
219 | finally: | |
220 | f.close() | |
221 | ||
222 | ||
223 | ||
224 |
0 | #! /usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | # $Author$ | |
35 | ||
36 | """ | |
37 | Warning: do not use this library. It is unstable and most of the routines | |
38 | here have been superceded by other libraries (e.g. rospkg). These | |
39 | routines will likely be *deleted* in future releases. | |
40 | """ | |
41 | ||
42 | import sys | |
43 | import os | |
44 | import getopt | |
45 | ||
46 | STACK_FILE = 'stack.xml' | |
47 | ||
48 | import roslib.manifestlib | |
49 | # re-export symbols so that external code does not have to import manifestlib as well | |
50 | from roslib.manifestlib import ManifestException, StackDepend | |
51 | ||
52 | class StackManifest(roslib.manifestlib._Manifest): | |
53 | """ | |
54 | Object representation of a ROS manifest file | |
55 | """ | |
56 | __slots__ = [] | |
57 | def __init__(self): | |
58 | """ | |
59 | Create an empty stack manifest instance. | |
60 | """ | |
61 | super(StackManifest, self).__init__('stack') | |
62 | ||
63 | def _stack_file_by_dir(stack_dir, required=True): | |
64 | """ | |
65 | @param stack_dir: path to stack directory | |
66 | @type stack_dir: str | |
67 | @param required: require that the directory exist | |
68 | @type required: bool | |
69 | @return: path to manifest file of stack | |
70 | @rtype: str | |
71 | @raise InvalidROSPkgException: if required is True and manifest file cannot be located | |
72 | """ | |
73 | try: | |
74 | p = os.path.join(stack_dir, STACK_FILE) | |
75 | if not required and not os.path.exists(p): | |
76 | return p | |
77 | if not os.path.isfile(p): | |
78 | raise roslib.stacks.InvalidROSStackException(""" | |
79 | Stack '%(stack_dir)s' is improperly configured: no manifest file is present. | |
80 | """%locals()) | |
81 | return p | |
82 | except roslib.stacks.InvalidROSStackException as e: | |
83 | if required: | |
84 | raise | |
85 | ||
86 | def stack_file(stack, required=True): | |
87 | """ | |
88 | @param stack: stack name | |
89 | @type stack: str | |
90 | @param required: require that the directory exist | |
91 | @type required: bool | |
92 | @return: path to manifest file of stack | |
93 | @rtype: str | |
94 | @raise InvalidROSPkgException: if required is True and manifest file cannot be located | |
95 | """ | |
96 | d = roslib.stacks.get_stack_dir(stack) | |
97 | return _stack_file_by_dir(d, required) | |
98 | ||
99 | def parse_file(file): | |
100 | """ | |
101 | Parse stack.xml file | |
102 | @param file: stack.xml file path | |
103 | @param file: str | |
104 | @return: StackManifest instance | |
105 | @rtype: L{StackManifest} | |
106 | """ | |
107 | return roslib.manifestlib.parse_file(StackManifest(), file) | |
108 | ||
109 | def parse(string, filename='string'): | |
110 | """ | |
111 | Parse stack.xml string contents | |
112 | @param string: stack.xml contents | |
113 | @type string: str | |
114 | @return: StackManifest instance | |
115 | @rtype: L{StackManifest} | |
116 | """ | |
117 | s = roslib.manifestlib.parse(StackManifest(), string, filename) | |
118 | #TODO: validate | |
119 | return s |
0 | #! /usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | ||
35 | """ | |
36 | Warning: do not use this library. It is unstable and most of the routines | |
37 | here have been superceded by other libraries (e.g. rospkg). These | |
38 | routines will likely be *deleted* in future releases. | |
39 | """ | |
40 | ||
41 | import os | |
42 | import sys | |
43 | import re | |
44 | ||
45 | import roslib.packages | |
46 | import roslib.stack_manifest | |
47 | ||
48 | import rospkg | |
49 | ||
50 | ROS_ROOT=rospkg.environment.ROS_ROOT | |
51 | ROS_PACKAGE_PATH=rospkg.environment.ROS_PACKAGE_PATH | |
52 | ||
53 | STACK_FILE = 'stack.xml' | |
54 | ROS_STACK = 'ros' | |
55 | ||
56 | class ROSStackException(Exception): pass | |
57 | class InvalidROSStackException(ROSStackException): pass | |
58 | ||
59 | def stack_of(pkg, env=None): | |
60 | """ | |
61 | @param env: override environment variables | |
62 | @type env: {str: str} | |
63 | @return: name of stack that pkg is in, or None if pkg is not part of a stack | |
64 | @rtype: str | |
65 | @raise roslib.packages.InvalidROSPkgException: if pkg cannot be located | |
66 | """ | |
67 | if env is None: | |
68 | env = os.environ | |
69 | pkg_dir = roslib.packages.get_pkg_dir(pkg, ros_root=env[ROS_ROOT], ros_package_path=env.get(ROS_PACKAGE_PATH, None)) | |
70 | d = pkg_dir | |
71 | while d and os.path.dirname(d) != d: | |
72 | stack_file = os.path.join(d, STACK_FILE) | |
73 | if os.path.exists(stack_file): | |
74 | #TODO: need to resolve issues regarding whether the | |
75 | #stack.xml or the directory defines the stack name | |
76 | return os.path.basename(d) | |
77 | d = os.path.dirname(d) | |
78 | ||
79 | def get_stack_dir(stack, env=None): | |
80 | """ | |
81 | Get the directory of a ROS stack. This will initialize an internal | |
82 | cache and return cached results if possible. | |
83 | ||
84 | This routine is not thread-safe to os.environ changes. | |
85 | ||
86 | @param env: override environment variables | |
87 | @type env: {str: str} | |
88 | @param stack: name of ROS stack to locate on disk | |
89 | @type stack: str | |
90 | @return: directory of stack. | |
91 | @rtype: str | |
92 | @raise InvalidROSStackException: if stack cannot be located. | |
93 | """ | |
94 | _init_rosstack(env=env) | |
95 | try: | |
96 | return _rosstack.get_path(stack) | |
97 | except rospkg.ResourceNotFound: | |
98 | # preserve old signature | |
99 | raise InvalidROSStackException(stack) | |
100 | ||
101 | _rosstack = None | |
102 | _ros_paths = None | |
103 | ||
104 | def _init_rosstack(env=None): | |
105 | global _rosstack, _ros_paths | |
106 | if env is None: | |
107 | env = os.environ | |
108 | ros_paths = rospkg.get_ros_paths(env) | |
109 | if ros_paths != _ros_paths: | |
110 | _ros_paths = ros_paths | |
111 | _rosstack = rospkg.RosStack(ros_paths) | |
112 | ||
113 | def list_stacks(env=None): | |
114 | """ | |
115 | Get list of all ROS stacks. This uses an internal cache. | |
116 | ||
117 | This routine is not thread-safe to os.environ changes. | |
118 | ||
119 | @param env: override environment variables | |
120 | @type env: {str: str} | |
121 | @return: complete list of stacks names in ROS environment | |
122 | @rtype: [str] | |
123 | """ | |
124 | _init_rosstack(env=env) | |
125 | return _rosstack.list() | |
126 | ||
127 | def list_stacks_by_path(path, stacks=None, cache=None): | |
128 | """ | |
129 | List ROS stacks within the specified path. | |
130 | ||
131 | Optionally, a cache dictionary can be provided, which will be | |
132 | updated with the stack->path mappings. list_stacks_by_path() does | |
133 | NOT returned cached results -- it only updates the cache. | |
134 | ||
135 | @param path: path to list stacks in | |
136 | @type path: str | |
137 | @param stacks: list of stacks to append to. If stack is | |
138 | already present in stacks, it will be ignored. | |
139 | @type stacks: [str] | |
140 | @param cache: (optional) stack path cache to update. Maps stack name to directory path. | |
141 | @type cache: {str: str} | |
142 | @return: complete list of stack names in ROS environment. Same as stacks parameter. | |
143 | @rtype: [str] | |
144 | """ | |
145 | if stacks is None: | |
146 | stacks = [] | |
147 | MANIFEST_FILE = rospkg.MANIFEST_FILE | |
148 | basename = os.path.basename | |
149 | for d, dirs, files in os.walk(path, topdown=True): | |
150 | if STACK_FILE in files: | |
151 | stack = basename(d) | |
152 | if stack not in stacks: | |
153 | stacks.append(stack) | |
154 | if cache is not None: | |
155 | cache[stack] = d | |
156 | del dirs[:] | |
157 | continue #leaf | |
158 | elif MANIFEST_FILE in files: | |
159 | del dirs[:] | |
160 | continue #leaf | |
161 | elif 'rospack_nosubdirs' in files: | |
162 | del dirs[:] | |
163 | continue #leaf | |
164 | # remove hidden dirs (esp. .svn/.git) | |
165 | [dirs.remove(di) for di in dirs if di[0] == '.'] | |
166 | for sub_d in dirs: | |
167 | # followlinks=True only available in Python 2.6, so we | |
168 | # have to implement manually | |
169 | sub_p = os.path.join(d, sub_d) | |
170 | if os.path.islink(sub_p): | |
171 | stacks.extend(list_stacks_by_path(sub_p, cache=cache)) | |
172 | return stacks | |
173 | ||
174 | # #2022 | |
175 | def expand_to_packages(names, env=None): | |
176 | """ | |
177 | Expand names into a list of packages. Names can either be of packages or stacks. | |
178 | ||
179 | @param names: names of stacks or packages | |
180 | @type names: [str] | |
181 | @return: ([packages], [not_found]). expand_packages() returns two | |
182 | lists. The first is of packages names. The second is a list of | |
183 | names for which no matching stack or package was found. Lists may have duplicates. | |
184 | @rtype: ([str], [str]) | |
185 | """ | |
186 | if env is None: | |
187 | env = os.environ | |
188 | ros_paths = rospkg.get_ros_paths(env) | |
189 | rospack = rospkg.RosPack(ros_paths) | |
190 | rosstack = rospkg.RosStack(ros_paths) | |
191 | return rospkg.expand_to_packages(names, rospack, rosstack) | |
192 | ||
193 | def get_stack_version(stack, env=None): | |
194 | """ | |
195 | @param env: override environment variables | |
196 | @type env: {str: str} | |
197 | ||
198 | @return: version number of stack, or None if stack is unversioned. | |
199 | @rtype: str | |
200 | """ | |
201 | _init_rosstack(env=env) | |
202 | return _rosstack.get_stack_version(stack) | |
203 | ||
204 | def get_stack_version_by_dir(stack_dir): | |
205 | """ | |
206 | Get stack version where stack_dir points to root directory of stack. | |
207 | ||
208 | @param env: override environment variables | |
209 | @type env: {str: str} | |
210 | ||
211 | @return: version number of stack, or None if stack is unversioned. | |
212 | @rtype: str | |
213 | """ | |
214 | # REP 109: check for <version> tag first, then CMakeLists.txt | |
215 | manifest_filename = os.path.join(stack_dir, STACK_FILE) | |
216 | if os.path.isfile(manifest_filename): | |
217 | m = roslib.stack_manifest.parse_file(manifest_filename) | |
218 | if m.version: | |
219 | return m.version | |
220 | ||
221 | cmake_filename = os.path.join(stack_dir, 'CMakeLists.txt') | |
222 | if os.path.isfile(cmake_filename): | |
223 | with open(cmake_filename) as f: | |
224 | return _get_cmake_version(f.read()) | |
225 | else: | |
226 | return None | |
227 | ||
228 | def _get_cmake_version(text): | |
229 | for l in text.split('\n'): | |
230 | if l.strip().startswith('rosbuild_make_distribution'): | |
231 | x_re = re.compile(r'[()]') | |
232 | lsplit = x_re.split(l.strip()) | |
233 | if len(lsplit) < 2: | |
234 | raise ReleaseException("couldn't find version number in CMakeLists.txt:\n\n%s"%l) | |
235 | return lsplit[1] |
0 | <package> | |
1 | <description brief="a brief description">Line 1 | |
2 | Line 2 | |
3 | </description> | |
4 | <version>1.2.4</version> <!-- bad --> | |
5 | <author>The authors | |
6 | go here</author> | |
7 | <license>Public Domain | |
8 | with other stuff</license> | |
9 | <url>http://pr.willowgarage.com/package/</url> | |
10 | <logo>http://www.willowgarage.com/files/willowgarage/robot10.jpg</logo> | |
11 | <depend package="pkgname" /> | |
12 | <depend package="common"/> | |
13 | <export> | |
14 | <cpp cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lros"/> | |
15 | <cpp os="osx" cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lrosthread -framework CoreServices"/> | |
16 | </export> | |
17 | <rosdep name="python" /> | |
18 | <rosdep name="bar" /> | |
19 | <rosdep name="baz" /> | |
20 | </package> |
0 | not xml |
0 | <notpackage> | |
1 | <description brief="a brief description">Line 1 | |
2 | Line 2 | |
3 | </description> | |
4 | <author>The authors | |
5 | go here</author> | |
6 | <license>Public Domain | |
7 | with other stuff</license> | |
8 | <url>http://pr.willowgarage.com/package/</url> | |
9 | <logo>http://www.willowgarage.com/files/willowgarage/robot10.jpg</logo> | |
10 | <depend package="pkgname" /> | |
11 | <depend package="common"/> | |
12 | <export> | |
13 | <cpp cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lros"/> | |
14 | <cpp os="osx" cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lrosthread -framework CoreServices"/> | |
15 | </export> | |
16 | <rosdep name="python" /> | |
17 | <rosdep name="bar" /> | |
18 | <rosdep name="baz" /> | |
19 | </notpackage> |
0 | <package> | |
1 | <description brief="a brief description">Line 1 | |
2 | </description> | |
3 | <description brief="a brief second description">Second description | |
4 | </description> | |
5 | ||
6 | <author>The authors | |
7 | go here</author> | |
8 | <license>Public Domain | |
9 | with other stuff</license> | |
10 | </package> |
0 | <package> | |
1 | <description brief="a brief description">Line 1 | |
2 | Line 2 | |
3 | </description> | |
4 | <author>The authors | |
5 | go here</author> | |
6 | <license>Public Domain | |
7 | with other stuff</license> | |
8 | <url>http://pr.willowgarage.com/package/</url> | |
9 | <logo>http://www.willowgarage.com/files/willowgarage/robot10.jpg</logo> | |
10 | <depend package="pkgname" /> | |
11 | <depend package="common"/> | |
12 | <export> | |
13 | <cpp cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lros"/> | |
14 | <cpp os="osx" cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lrosthread -framework CoreServices"/> | |
15 | </export> | |
16 | <rosdep name="python" /> | |
17 | <rosdep name="bar" /> | |
18 | <rosdep name="baz" /> | |
19 | </package> |
0 | <stack> | |
1 | <description brief="a brief description">Line 1 | |
2 | Line 2 | |
3 | </description> | |
4 | <author>The authors | |
5 | go here</author> | |
6 | <license>Public Domain | |
7 | with other stuff</license> | |
8 | <url>http://ros.org/stack/</url> | |
9 | <logo>http://www.willowgarage.com/files/willowgarage/robot10.jpg</logo> | |
10 | <depend stack="stackname" /> | |
11 | <depend stack="common"/> | |
12 | </stack> |
0 | <stack> | |
1 | <description brief="a brief description">Line 1 | |
2 | Line 2 | |
3 | </description> | |
4 | <author>The authors | |
5 | go here</author> | |
6 | <license>Public Domain | |
7 | with other stuff</license> | |
8 | <url>http://ros.org/stack/</url> | |
9 | <version>1.2.3</version> | |
10 | <logo>http://www.willowgarage.com/files/willowgarage/robot10.jpg</logo> | |
11 | <depend stack="stackname" /> | |
12 | <depend stack="common"/> | |
13 | </stack> |
0 | /* | |
1 | * Copyright (c) 2008, Willow Garage, Inc. | |
2 | * All rights reserved. | |
3 | * | |
4 | * Redistribution and use in source and binary forms, with or without | |
5 | * modification, are permitted provided that the following conditions are met: | |
6 | * | |
7 | * * Redistributions of source code must retain the above copyright | |
8 | * notice, this list of conditions and the following disclaimer. | |
9 | * * Redistributions in binary form must reproduce the above copyright | |
10 | * notice, this list of conditions and the following disclaimer in the | |
11 | * documentation and/or other materials provided with the distribution. | |
12 | * * Neither the name of Willow Garage, Inc. nor the names of its | |
13 | * contributors may be used to endorse or promote products derived from | |
14 | * this software without specific prior written permission. | |
15 | * | |
16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
26 | * POSSIBILITY OF SUCH DAMAGE. | |
27 | */ | |
28 | ||
29 | #include <vector> | |
30 | ||
31 | #include <gtest/gtest.h> | |
32 | #include <ros/package.h> | |
33 | #include <sys/time.h> | |
34 | ||
35 | using namespace ros; | |
36 | ||
37 | TEST(Package, getPath) | |
38 | { | |
39 | std::string path = package::getPath("roslib"); | |
40 | printf("Path: %s\n", path.c_str()); | |
41 | ASSERT_FALSE(path.empty()); | |
42 | } | |
43 | ||
44 | TEST(Package, getAll) | |
45 | { | |
46 | using package::V_string; | |
47 | V_string packages; | |
48 | ASSERT_TRUE(package::getAll(packages)); | |
49 | ASSERT_FALSE(packages.empty()); | |
50 | ||
51 | V_string::iterator it = packages.begin(); | |
52 | V_string::iterator end = packages.end(); | |
53 | for (; it != end; ++it) | |
54 | { | |
55 | printf("Package: %s\n", it->c_str()); | |
56 | } | |
57 | } | |
58 | ||
59 | int main(int argc, char **argv){ | |
60 | testing::InitGoogleTest(&argc, argv); | |
61 | return RUN_ALL_TESTS(); | |
62 | } | |
63 |
0 | <package> | |
1 | <description brief="bar"> | |
2 | ||
3 | bar | |
4 | ||
5 | </description> | |
6 | <author>Ken Conley</author> | |
7 | <license>BSD</license> | |
8 | <review status="unreviewed" notes=""/> | |
9 | <url>http://ros.org/wiki/bar</url> | |
10 | ||
11 | </package> | |
12 | ||
13 |
0 | <package> | |
1 | <description brief="foo"> | |
2 | ||
3 | foo | |
4 | ||
5 | </description> | |
6 | <author>Ken Conley</author> | |
7 | <license>BSD</license> | |
8 | <review status="unreviewed" notes=""/> | |
9 | <url>http://ros.org/wiki/foo</url> | |
10 | ||
11 | </package> | |
12 | ||
13 |
0 | <package> | |
1 | <description brief="foo"> | |
2 | ||
3 | foo | |
4 | ||
5 | </description> | |
6 | <author>Ken Conley</author> | |
7 | <license>BSD</license> | |
8 | <review status="unreviewed" notes=""/> | |
9 | <url>http://ros.org/wiki/foo</url> | |
10 | ||
11 | </package> | |
12 | ||
13 |
0 | <package> | |
1 | <description brief="roslib tests"> | |
2 | Unit tests verifying that roslib is operating as expected. | |
3 | </description> | |
4 | <author>none</author> | |
5 | <license>BSD</license> | |
6 | <platform os="test_os" version="test_version"/> | |
7 | </package> |
0 | [nosetests] | |
1 | with-xunit=1 | |
2 | with-coverage=1 | |
3 | cover-package=roslib | |
4 | tests=test_roslib_manifest.py,test_roslib_names.py,test_roslib_packages.py,test_roslib.py, test_roslib_rosenv.py, test_roslib_stack_manifest.py, test_roslib_stacks.py, test_roslib_exceptions.py, test_roslib_manifestlib.py | |
5 |
0 | cmake_minimum_required(VERSION 2.4.6) | |
1 | include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake) | |
2 | set(ROSPACK_MAKEDIST true) | |
3 | rosbuild_make_distribution(1.5.0-cmake) |
0 | <stack> | |
1 | <description brief="bar"> | |
2 | bar | |
3 | </description> | |
4 | <author>Ken Conley</author> | |
5 | <license>BSD</license> | |
6 | </stack> | |
7 | ||
8 |
0 | cmake_minimum_required(VERSION 2.4.6) | |
1 | include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake) | |
2 | set(ROSPACK_MAKEDIST true) | |
3 | rosbuild_make_distribution(1.5.0-cmake) |
0 | <package> | |
1 | <description brief="foo_pkg"> | |
2 | ||
3 | foo_pkg | |
4 | ||
5 | </description> | |
6 | <author>Ken Conley</author> | |
7 | <license>BSD</license> | |
8 | <review status="unreviewed" notes=""/> | |
9 | <url>http://ros.org/wiki/foo_pkg</url> | |
10 | ||
11 | </package> | |
12 | ||
13 |
0 | <package> | |
1 | <description brief="foo_pkg_2"> | |
2 | ||
3 | foo_pkg_2 | |
4 | ||
5 | </description> | |
6 | <author>Ken Conley</author> | |
7 | <license>BSD</license> | |
8 | <review status="unreviewed" notes=""/> | |
9 | <url>http://ros.org/wiki/foo_pkg_2</url> | |
10 | ||
11 | </package> | |
12 | ||
13 |
0 | <stack> | |
1 | <name>foo</name> | |
2 | <description brief="foo"> | |
3 | foo | |
4 | </description> | |
5 | <author>Ken Conley</author> | |
6 | <version> | |
7 | 1.6.0-manifest | |
8 | </version> | |
9 | <license>BSD</license> | |
10 | <copyright/> | |
11 | </stack> | |
12 | ||
13 |
0 | <package> | |
1 | <description brief="foo"> | |
2 | foo | |
3 | </description> | |
4 | <author>Ken Conley</author> | |
5 | <license>BSD</license> | |
6 | </package> | |
7 | ||
8 |
0 | ../stack_tests/s1⏎ |
0 | <package> | |
1 | <description brief="roslib tests"> | |
2 | Fake | |
3 | </description> | |
4 | <author>No one</author> | |
5 | <license>BSD</license> | |
6 | <review status="test" notes=""/> | |
7 | ||
8 | <depend package="roslib" /> | |
9 | <depend package="rosunit" /> | |
10 | </package> |
0 | <stack> | |
1 | <description brief="bar"> | |
2 | bar | |
3 | </description> | |
4 | <author>Ken Conley</author> | |
5 | <license>BSD</license> | |
6 | </stack> | |
7 | ||
8 |
0 | <stack> | |
1 | <description brief="bar"> | |
2 | bar | |
3 | </description> | |
4 | <author>Ken Conley</author> | |
5 | <license>BSD</license> | |
6 | </stack> | |
7 | ||
8 |
0 | <package> | |
1 | <description brief="roslib tests"> | |
2 | Fake | |
3 | </description> | |
4 | <author>No one</author> | |
5 | <license>BSD</license> | |
6 | <review status="test" notes=""/> | |
7 | ||
8 | <depend package="roslib" /> | |
9 | <depend package="rosunit" /> | |
10 | </package> |
0 | <stack> | |
1 | <description brief="foo"> | |
2 | foo | |
3 | </description> | |
4 | <author>Ken Conley</author> | |
5 | <license>BSD</license> | |
6 | </stack> | |
7 | ||
8 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import sys | |
34 | ||
35 | def test_load_manifest(): | |
36 | # this is a bit of a noop as it's a prerequisite of running with rosunit | |
37 | import roslib | |
38 | roslib.load_manifest('roslib') | |
39 | ||
40 | def test_interactive(): | |
41 | import roslib | |
42 | ||
43 | # make sure that it's part of high-level API | |
44 | assert not roslib.is_interactive(), "interactive should be false by default" | |
45 | for v in [True, False]: | |
46 | roslib.set_interactive(v) | |
47 | assert v == roslib.is_interactive() | |
48 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import sys | |
34 | ||
35 | def test_exceptions(): | |
36 | from roslib.exceptions import ROSLibException | |
37 | assert isinstance(ROSLibException(), Exception) |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import sys | |
34 | import unittest | |
35 | ||
36 | import roslib | |
37 | ||
38 | def get_test_path(): | |
39 | return os.path.abspath(os.path.dirname(__file__)) | |
40 | ||
41 | class RoslibManifestTest(unittest.TestCase): | |
42 | ||
43 | def test_ManifestException(self): | |
44 | from roslib.manifest import ManifestException | |
45 | self.assert_(isinstance(ManifestException(), Exception)) | |
46 | ||
47 | def test_Depend(self): | |
48 | from roslib.manifestlib import Depend, ManifestException | |
49 | for bad in [None, '']: | |
50 | try: | |
51 | Depend(bad) | |
52 | self.fail("should have failed on [%s]"%bad) | |
53 | except ValueError: pass | |
54 | ||
55 | d = Depend('roslib') | |
56 | self.assertEquals('roslib', str(d)) | |
57 | self.assertEquals('roslib', repr(d)) | |
58 | ||
59 | self.assertEquals('<depend package="roslib" />',d.xml()) | |
60 | self.assertEquals(d, Depend('roslib')) | |
61 | self.assertNotEquals(d, Depend('roslib2')) | |
62 | self.assertNotEquals(d, 1) | |
63 | ||
64 | def test_ROSDep(self): | |
65 | from roslib.manifest import ROSDep, ManifestException | |
66 | for bad in [None, '']: | |
67 | try: | |
68 | rd = ROSDep(bad) | |
69 | self.fail("should have failed on [%s]"%bad) | |
70 | except ValueError: pass | |
71 | ||
72 | rd = ROSDep('python') | |
73 | self.assertEquals('<rosdep name="python" />',rd.xml()) | |
74 | ||
75 | def test_VersionControl(self): | |
76 | from roslib.manifest import VersionControl, ManifestException | |
77 | ros_svn = 'https://ros.svn.sf.net/svnroot' | |
78 | ||
79 | bad = [ | |
80 | (None, ros_svn), | |
81 | ] | |
82 | for type_, url in bad: | |
83 | try: | |
84 | VersionControl(type_,url) | |
85 | self.fail("should have failed on [%s] [%s]"%(type_, url)) | |
86 | except ValueError: pass | |
87 | ||
88 | tests = [ | |
89 | ('svn', ros_svn, '<versioncontrol type="svn" url="%s" />'%ros_svn), | |
90 | ('cvs', None, '<versioncontrol type="cvs" />'), | |
91 | ] | |
92 | for type_, url, xml in tests: | |
93 | vc = VersionControl(type_, url) | |
94 | self.assertEquals(type_, vc.type) | |
95 | self.assertEquals(url, vc.url) | |
96 | self.assertEquals(xml, vc.xml()) | |
97 | ||
98 | def _subtest_parse_example1(self, m): | |
99 | from roslib.manifest import Manifest | |
100 | self.assert_(isinstance(m, Manifest)) | |
101 | self.assertEquals("a brief description", m.brief) | |
102 | self.assertEquals("Line 1\nLine 2", m.description.strip()) | |
103 | self.assertEquals("The authors\ngo here", m.author.strip()) | |
104 | self.assertEquals("Public Domain\nwith other stuff", m.license.strip()) | |
105 | self.assertEquals("http://pr.willowgarage.com/package/", m.url) | |
106 | self.assertEquals("http://www.willowgarage.com/files/willowgarage/robot10.jpg", m.logo) | |
107 | dpkgs = [d.package for d in m.depends] | |
108 | self.assertEquals(set(['pkgname', 'common']), set(dpkgs)) | |
109 | rdpkgs = [d.name for d in m.rosdeps] | |
110 | self.assertEquals(set(['python', 'bar', 'baz']), set(rdpkgs)) | |
111 | ||
112 | def test_parse_example1_file(self): | |
113 | from roslib.manifest import parse_file, Manifest | |
114 | p = os.path.join(get_test_path(), 'manifest_tests', 'example1.xml') | |
115 | self._subtest_parse_example1(parse_file(p)) | |
116 | ||
117 | def test_parse_example1_string(self): | |
118 | from roslib.manifest import parse, Manifest | |
119 | self._subtest_parse_example1(parse(EXAMPLE1)) | |
120 | ||
121 | def test_Manifest_str(self): | |
122 | # just make sure it doesn't crash | |
123 | from roslib.manifest import parse | |
124 | str(parse(EXAMPLE1)) | |
125 | ||
126 | def test_Manifest_xml(self): | |
127 | from roslib.manifest import parse | |
128 | m = parse(EXAMPLE1) | |
129 | self._subtest_parse_example1(m) | |
130 | # verify roundtrip | |
131 | m2 = parse(m.xml()) | |
132 | self._subtest_parse_example1(m2) | |
133 | ||
134 | ||
135 | def test_parse_bad_file(self): | |
136 | from roslib.manifest import parse_file, Manifest | |
137 | # have to import from ManifestException due to weirdness when run in --cov mode | |
138 | from roslib.manifestlib import ManifestException | |
139 | base_p = os.path.join(get_test_path(), 'manifest_tests') | |
140 | for b in ['bad1.xml', 'bad2.xml', 'bad3.xml']: | |
141 | p = os.path.join(base_p, b) | |
142 | try: | |
143 | parse_file(p) | |
144 | self.fail("parse should have failed on bad manifest") | |
145 | except ManifestException, e: | |
146 | print str(e) | |
147 | self.assert_(b in str(e), "file name should be in error message: %s"%(str(e))) | |
148 | ||
149 | EXAMPLE1 = """<package> | |
150 | <description brief="a brief description">Line 1 | |
151 | Line 2 | |
152 | </description> | |
153 | <author>The authors | |
154 | go here</author> | |
155 | <license>Public Domain | |
156 | with other stuff</license> | |
157 | <url>http://pr.willowgarage.com/package/</url> | |
158 | <logo>http://www.willowgarage.com/files/willowgarage/robot10.jpg</logo> | |
159 | <depend package="pkgname" /> | |
160 | <depend package="common"/> | |
161 | <export> | |
162 | <cpp cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lros"/> | |
163 | <cpp os="osx" cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lrosthread -framework CoreServices"/> | |
164 | </export> | |
165 | <rosdep name="python" /> | |
166 | <rosdep name="bar" /> | |
167 | <rosdep name="baz" /> | |
168 | <rosbuild2> | |
169 | <depend thirdparty="thisshouldbeokay"/> | |
170 | </rosbuild2> | |
171 | </package>""" |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import struct | |
34 | import sys | |
35 | import unittest | |
36 | ||
37 | import roslib | |
38 | ||
39 | class RoslibManifestlibTest(unittest.TestCase): | |
40 | ||
41 | def test_ManifestException(self): | |
42 | from roslib.manifestlib import ManifestException | |
43 | self.assert_(isinstance(ManifestException(), Exception)) | |
44 | ||
45 | def test_Platform(self): | |
46 | from roslib.manifestlib import Platform, ManifestException | |
47 | for bad in [None, '']: | |
48 | try: | |
49 | Platform(bad, '1') | |
50 | self.fail("should have failed on [%s]"%bad) | |
51 | except ValueError: pass | |
52 | try: | |
53 | Platform('ubuntu', bad) | |
54 | self.fail("should have failed on [%s]"%bad) | |
55 | except ValueError: pass | |
56 | ||
57 | p = Platform('ubuntu', '8.04') | |
58 | self.assertEquals('ubuntu 8.04', str(p)) | |
59 | self.assertEquals('ubuntu 8.04', repr(p)) | |
60 | ||
61 | self.assertEquals('<platform os="ubuntu" version="8.04"/>',p.xml()) | |
62 | self.assertEquals(p, Platform('ubuntu', '8.04')) | |
63 | self.assertEquals(p, Platform('ubuntu', '8.04', notes=None)) | |
64 | self.assertNotEquals(p, Platform('ubuntu', '8.04', 'some notes')) | |
65 | self.assertNotEquals(p, 'foo') | |
66 | self.assertNotEquals(p, 1) | |
67 | ||
68 | # note: probably actually "osx" | |
69 | p = Platform('OS X', '10.6', 'macports') | |
70 | self.assertEquals('OS X 10.6', str(p)) | |
71 | self.assertEquals('OS X 10.6', repr(p)) | |
72 | ||
73 | self.assertEquals('<platform os="OS X" version="10.6" notes="macports"/>',p.xml()) | |
74 | self.assertEquals(p, p) | |
75 | self.assertEquals(p, Platform('OS X', '10.6', 'macports')) | |
76 | self.assertNotEquals(p, Platform('OS X', '10.6')) | |
77 | self.assertNotEquals(p, 'foo') | |
78 | self.assertNotEquals(p, 1) | |
79 | ||
80 | ||
81 | def test_Depend(self): | |
82 | from roslib.manifestlib import Depend, StackDepend, ManifestException | |
83 | for bad in [None, '']: | |
84 | try: | |
85 | Depend(bad) | |
86 | self.fail("should have failed on [%s]"%bad) | |
87 | except ValueError: pass | |
88 | ||
89 | d = Depend('roslib') | |
90 | self.assertEquals('roslib', str(d)) | |
91 | self.assertEquals('roslib', repr(d)) | |
92 | ||
93 | self.assertEquals('<depend package="roslib" />',d.xml()) | |
94 | self.assertEquals(d, Depend('roslib')) | |
95 | self.assertNotEquals(d, StackDepend('roslib')) | |
96 | self.assertNotEquals(d, Depend('roslib2')) | |
97 | self.assertNotEquals(d, 1) | |
98 | ||
99 | def test_StackDepend(self): | |
100 | from roslib.manifestlib import Depend, StackDepend, ManifestException | |
101 | for bad in [None, '']: | |
102 | try: | |
103 | StackDepend(bad) | |
104 | self.fail("should have failed on [%s]"%bad) | |
105 | except ValueError: pass | |
106 | ||
107 | d = StackDepend('common') | |
108 | self.assertEquals('common', str(d)) | |
109 | self.assertEquals('common', repr(d)) | |
110 | ||
111 | self.assertEquals('<depend stack="common" />',d.xml()) | |
112 | self.assertEquals(d, StackDepend('common')) | |
113 | self.assertNotEquals(d, Depend('common')) | |
114 | self.assertNotEquals(d, StackDepend('common2')) | |
115 | self.assertNotEquals(d, 1) | |
116 | ||
117 | def test_ROSDep(self): | |
118 | from roslib.manifestlib import ROSDep, ManifestException | |
119 | for bad in [None, '']: | |
120 | try: | |
121 | rd = ROSDep(bad) | |
122 | self.fail("should have failed on [%s]"%bad) | |
123 | except ValueError: pass | |
124 | ||
125 | rd = ROSDep('python') | |
126 | self.assertEquals('<rosdep name="python" />',rd.xml()) | |
127 | ||
128 | def test_VersionControl(self): | |
129 | from roslib.manifestlib import VersionControl, ManifestException | |
130 | ros_svn = 'https://ros.svn.sf.net/svnroot' | |
131 | ||
132 | bad = [ | |
133 | (None, ros_svn), | |
134 | ] | |
135 | for type_, url in bad: | |
136 | try: | |
137 | VersionControl(type_,url) | |
138 | self.fail("should have failed on [%s] [%s]"%(type_, url)) | |
139 | except ValueError: pass | |
140 | ||
141 | tests = [ | |
142 | ('svn', ros_svn, '<versioncontrol type="svn" url="%s" />'%ros_svn), | |
143 | ('cvs', None, '<versioncontrol type="cvs" />'), | |
144 | ] | |
145 | for type_, url, xml in tests: | |
146 | vc = VersionControl(type_, url) | |
147 | self.assertEquals(type_, vc.type) | |
148 | self.assertEquals(url, vc.url) | |
149 | self.assertEquals(xml, vc.xml()) | |
150 | ||
151 | def _subtest_parse_example1(self, m): | |
152 | from roslib.manifestlib import _Manifest | |
153 | self.assert_(isinstance(m, _Manifest)) | |
154 | self.assertEquals("a brief description", m.brief) | |
155 | self.assertEquals("Line 1\nLine 2", m.description.strip()) | |
156 | self.assertEquals("The authors\ngo here", m.author.strip()) | |
157 | self.assertEquals("Public Domain\nwith other stuff", m.license.strip()) | |
158 | self.assertEquals("http://pr.willowgarage.com/package/", m.url) | |
159 | self.assertEquals("http://www.willowgarage.com/files/willowgarage/robot10.jpg", m.logo) | |
160 | dpkgs = [d.package for d in m.depends] | |
161 | self.assertEquals(set(['pkgname', 'common']), set(dpkgs)) | |
162 | rdpkgs = [d.name for d in m.rosdeps] | |
163 | self.assertEquals(set(['python', 'bar', 'baz']), set(rdpkgs)) | |
164 | for p in m.platforms: | |
165 | if p.os == 'ubuntu': | |
166 | self.assertEquals("8.04", p.version) | |
167 | self.assertEquals('', p.notes) | |
168 | elif p.os == 'OS X': | |
169 | self.assertEquals("10.6", p.version) | |
170 | self.assertEquals("macports", p.notes) | |
171 | else: | |
172 | self.fail("unknown platform "+str(p)) | |
173 | ||
174 | def _subtest_parse_stack_example1(self, m): | |
175 | from roslib.manifestlib import _Manifest | |
176 | self.assert_(isinstance(m, _Manifest)) | |
177 | self.assertEquals('stack', m._type) | |
178 | self.assertEquals("a brief description", m.brief) | |
179 | self.assertEquals("Line 1\nLine 2", m.description.strip()) | |
180 | self.assertEquals("The authors\ngo here", m.author.strip()) | |
181 | self.assertEquals("Public Domain\nwith other stuff", m.license.strip()) | |
182 | self.assertEquals("http://ros.org/stack/", m.url) | |
183 | self.assertEquals("http://www.willowgarage.com/files/willowgarage/robot10.jpg", m.logo) | |
184 | dpkgs = [d.stack for d in m.depends] | |
185 | self.assertEquals(set(['stackname', 'common']), set(dpkgs)) | |
186 | self.assertEquals([], m.rosdeps) | |
187 | self.assertEquals([], m.exports) | |
188 | ||
189 | def _subtest_parse_stack_version(self, m): | |
190 | self.assertEquals("1.2.3", m.version) | |
191 | ||
192 | def test_parse_example1_file(self): | |
193 | from roslib.manifestlib import parse_file, _Manifest | |
194 | p = os.path.join(get_test_path(), 'manifest_tests', 'example1.xml') | |
195 | self._subtest_parse_example1(parse_file(_Manifest(), p)) | |
196 | ||
197 | p = os.path.join(get_test_path(), 'manifest_tests', 'stack_example1.xml') | |
198 | self._subtest_parse_stack_example1(parse_file(_Manifest('stack'), p)) | |
199 | ||
200 | p = os.path.join(get_test_path(), 'manifest_tests', 'stack_version.xml') | |
201 | self._subtest_parse_stack_version(parse_file(_Manifest('stack'), p)) | |
202 | ||
203 | def test_parse_example1_string(self): | |
204 | from roslib.manifestlib import parse, _Manifest | |
205 | self._subtest_parse_example1(parse(_Manifest(), EXAMPLE1)) | |
206 | self._subtest_parse_stack_example1(parse(_Manifest('stack'), STACK_EXAMPLE1)) | |
207 | ||
208 | def test__Manifest(self): | |
209 | from roslib.manifestlib import _Manifest | |
210 | m = _Manifest() | |
211 | # check defaults | |
212 | self.assertEquals('package', m._type) | |
213 | m = _Manifest('stack') | |
214 | self.assertEquals('stack', m._type) | |
215 | ||
216 | def test_Manifest_str(self): | |
217 | # just make sure it doesn't crash | |
218 | from roslib.manifestlib import parse, _Manifest | |
219 | str(parse(_Manifest(), EXAMPLE1)) | |
220 | ||
221 | def test_Manifest_xml(self): | |
222 | from roslib.manifestlib import parse, _Manifest | |
223 | m = _Manifest() | |
224 | parse(m, EXAMPLE1) | |
225 | self._subtest_parse_example1(m) | |
226 | # verify roundtrip | |
227 | m2 = _Manifest() | |
228 | parse(m2, m.xml()) | |
229 | self._subtest_parse_example1(m2) | |
230 | ||
231 | # bad file examples should be more like the roslaunch tests where there is just 1 thing wrong | |
232 | def test_parse_bad_file(self): | |
233 | from roslib.manifestlib import parse_file, _Manifest, ManifestException | |
234 | base_p = os.path.join(get_test_path(), 'manifest_tests') | |
235 | m = _Manifest() | |
236 | for b in ['bad1.xml', 'bad2.xml', 'bad3.xml']: | |
237 | p = os.path.join(base_p, b) | |
238 | try: | |
239 | parse_file(m, p) | |
240 | self.fail("parse should have failed on bad manifest") | |
241 | except ManifestException, e: | |
242 | print str(e) | |
243 | self.assert_(b in str(e), "file name should be in error message [%s]"%(str(e))) | |
244 | ||
245 | EXAMPLE1 = """<package> | |
246 | <description brief="a brief description">Line 1 | |
247 | Line 2 | |
248 | </description> | |
249 | <author>The authors | |
250 | go here</author> | |
251 | <license>Public Domain | |
252 | with other stuff</license> | |
253 | <url>http://pr.willowgarage.com/package/</url> | |
254 | <logo>http://www.willowgarage.com/files/willowgarage/robot10.jpg</logo> | |
255 | <depend package="pkgname" /> | |
256 | <depend package="common"/> | |
257 | <export> | |
258 | <cpp cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lros"/> | |
259 | <cpp os="osx" cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lrosthread -framework CoreServices"/> | |
260 | </export> | |
261 | <rosdep name="python" /> | |
262 | <rosdep name="bar" /> | |
263 | <rosdep name="baz" /> | |
264 | <platform os="ubuntu" version="8.04" /> | |
265 | <platform os="OS X" version="10.6" notes="macports" /> | |
266 | <rosbuild2> | |
267 | <depend thirdparty="thisshouldbeokay"/> | |
268 | </rosbuild2> | |
269 | </package>""" | |
270 | ||
271 | STACK_EXAMPLE1 = """<stack> | |
272 | <description brief="a brief description">Line 1 | |
273 | Line 2 | |
274 | </description> | |
275 | <author>The authors | |
276 | go here</author> | |
277 | <license>Public Domain | |
278 | with other stuff</license> | |
279 | <url>http://ros.org/stack/</url> | |
280 | <logo>http://www.willowgarage.com/files/willowgarage/robot10.jpg</logo> | |
281 | <depend stack="stackname" /> | |
282 | <depend stack="common"/> | |
283 | </stack>""" | |
284 | ||
285 | STACK_INVALID1 = """<stack> | |
286 | <description brief="a brief description">Line 1</description> | |
287 | <author>The authors</author> | |
288 | <license>Public Domain</license> | |
289 | <rosdep name="python" /> | |
290 | </stack>""" | |
291 | ||
292 | STACK_INVALID2 = """<stack> | |
293 | <description brief="a brief description">Line 1</description> | |
294 | <author>The authors</author> | |
295 | <license>Public Domain</license> | |
296 | <export> | |
297 | <cpp cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lros"/> | |
298 | <cpp os="osx" cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lrosthread -framework CoreServices"/> | |
299 | </export> | |
300 | </stack>""" | |
301 | ||
302 | ||
303 | def get_test_path(): | |
304 | return os.path.abspath(os.path.dirname(__file__)) |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import sys | |
34 | import unittest | |
35 | ||
36 | import roslib.names | |
37 | ||
38 | class NamesTest(unittest.TestCase): | |
39 | ||
40 | def test_get_ros_namespace(self): | |
41 | if 'ROS_NAMESPACE' in os.environ: | |
42 | rosns = os.environ['ROS_NAMESPACE'] | |
43 | del os.environ['ROS_NAMESPACE'] | |
44 | else: | |
45 | rosns = None | |
46 | sysargv = sys.argv | |
47 | ||
48 | try: | |
49 | sys.argv = [] | |
50 | self.assertEquals('/', roslib.names.get_ros_namespace()) | |
51 | self.assertEquals('/', roslib.names.get_ros_namespace(argv=[])) | |
52 | self.assertEquals('/', roslib.names.get_ros_namespace(env={})) | |
53 | self.assertEquals('/', roslib.names.get_ros_namespace(env={}, argv=[])) | |
54 | ||
55 | os.environ['ROS_NAMESPACE'] = 'unresolved' | |
56 | self.assertEquals('/unresolved/', roslib.names.get_ros_namespace()) | |
57 | self.assertEquals('/unresolved/', roslib.names.get_ros_namespace(env={'ROS_NAMESPACE': 'unresolved'})) | |
58 | sys.argv = ['foo', '__ns:=unresolved_override'] | |
59 | self.assertEquals('/unresolved_override/', roslib.names.get_ros_namespace(env={'ROS_NAMESPACE': 'unresolved'})) | |
60 | self.assertEquals('/override2/', roslib.names.get_ros_namespace(env={'ROS_NAMESPACE': 'unresolved'}, argv=['foo', '__ns:=override2'])) | |
61 | ||
62 | sys.argv = [] | |
63 | os.environ['ROS_NAMESPACE'] = '/resolved/' | |
64 | self.assertEquals('/resolved/', roslib.names.get_ros_namespace()) | |
65 | self.assertEquals('/resolved/', roslib.names.get_ros_namespace(env={'ROS_NAMESPACE': '/resolved'})) | |
66 | ||
67 | del os.environ['ROS_NAMESPACE'] | |
68 | ||
69 | sys.argv = ['foo', '__ns:=unresolved_ns'] | |
70 | self.assertEquals('/unresolved_ns/', roslib.names.get_ros_namespace()) | |
71 | self.assertEquals('/unresolved_ns2/', roslib.names.get_ros_namespace(argv=['foo', '__ns:=unresolved_ns2'])) | |
72 | sys.argv = ['foo', '__ns:=/resolved_ns/'] | |
73 | self.assertEquals('/resolved_ns/', roslib.names.get_ros_namespace()) | |
74 | self.assertEquals('/resolved_ns2/', roslib.names.get_ros_namespace(argv=['foo', '__ns:=resolved_ns2'])) | |
75 | finally: | |
76 | sys.argv = sysargv | |
77 | ||
78 | # restore | |
79 | if rosns: | |
80 | os.environ['ROS_NAMESPACE'] = rosns | |
81 | ||
82 | def test_make_global_ns(self): | |
83 | from roslib.names import make_global_ns | |
84 | ||
85 | for n in ['~foo']: | |
86 | try: | |
87 | make_global_ns(n) | |
88 | self.fail("make_global_ns should fail on %s"%n) | |
89 | except ValueError: pass | |
90 | ||
91 | self.assertEquals('/foo/', make_global_ns('foo')) | |
92 | self.assertEquals('/', make_global_ns('')) | |
93 | self.assertEquals('/foo/', make_global_ns('/foo')) | |
94 | self.assertEquals('/foo/', make_global_ns('/foo/')) | |
95 | self.assertEquals('/foo/bar/', make_global_ns('/foo/bar')) | |
96 | self.assertEquals('/foo/bar/', make_global_ns('/foo/bar/')) | |
97 | ||
98 | def test_is_global(self): | |
99 | try: | |
100 | roslib.names.is_global(None) | |
101 | self.fail("is_global should raise exception on invalid param") | |
102 | except: pass | |
103 | tests = ['/', '/global', '/global2'] | |
104 | for t in tests: | |
105 | self.assert_(roslib.names.is_global(t)) | |
106 | fails = ['', 'not_global', 'not/global'] | |
107 | for t in fails: | |
108 | self.failIf(roslib.names.is_global(t)) | |
109 | ||
110 | def test_is_private(self): | |
111 | try: | |
112 | roslib.names.is_private(None) | |
113 | self.fail("is_private should raise exception on invalid param") | |
114 | except: pass | |
115 | tests = ['~name', '~name/sub'] | |
116 | for t in tests: | |
117 | self.assert_(roslib.names.is_private(t)) | |
118 | fails = ['', 'not_private', 'not/private', 'not/~private', '/not/~private'] | |
119 | for t in fails: | |
120 | self.failIf(roslib.names.is_private(t)) | |
121 | ||
122 | def test_namespace(self): | |
123 | from roslib.names import namespace | |
124 | try: | |
125 | namespace(1) | |
126 | self.fail("1") | |
127 | except TypeError: pass | |
128 | try: | |
129 | namespace(None) | |
130 | self.fail("None") | |
131 | except ValueError: pass | |
132 | self.assertEquals('/', namespace('')) | |
133 | self.assertEquals('/', namespace('/')) | |
134 | self.assertEquals('/', namespace('/foo')) | |
135 | self.assertEquals('/', namespace('/foo/')) | |
136 | self.assertEquals('/foo/', namespace('/foo/bar')) | |
137 | self.assertEquals('/foo/', namespace('/foo/bar/')) | |
138 | self.assertEquals('/foo/bar/', namespace('/foo/bar/baz')) | |
139 | self.assertEquals('/foo/bar/', namespace('/foo/bar/baz/')) | |
140 | ||
141 | # unicode tests | |
142 | self.assertEquals(u'/', namespace(u'')) | |
143 | self.assertEquals(u'/', namespace(u'/')) | |
144 | self.assertEquals(u'/foo/bar/', namespace(u'/foo/bar/baz/')) | |
145 | ||
146 | def test_nsjoin(self): | |
147 | from roslib.names import ns_join | |
148 | ||
149 | # private and global names cannot be joined | |
150 | self.assertEquals('~name', ns_join('/foo', '~name')) | |
151 | self.assertEquals('/name', ns_join('/foo', '/name')) | |
152 | self.assertEquals('~name', ns_join('~', '~name')) | |
153 | self.assertEquals('/name', ns_join('/', '/name')) | |
154 | ||
155 | # ns can be '~' or '/' | |
156 | self.assertEquals('~name', ns_join('~', 'name')) | |
157 | self.assertEquals('/name', ns_join('/', 'name')) | |
158 | ||
159 | self.assertEquals('/ns/name', ns_join('/ns', 'name')) | |
160 | self.assertEquals('/ns/name', ns_join('/ns/', 'name')) | |
161 | self.assertEquals('/ns/ns2/name', ns_join('/ns', 'ns2/name')) | |
162 | self.assertEquals('/ns/ns2/name', ns_join('/ns/', 'ns2/name')) | |
163 | ||
164 | # allow ns to be empty | |
165 | self.assertEquals('name', ns_join('', 'name')) | |
166 | ||
167 | ||
168 | def test_load_mappings(self): | |
169 | from roslib.names import load_mappings | |
170 | self.assertEquals({}, load_mappings([])) | |
171 | self.assertEquals({}, load_mappings(['foo'])) | |
172 | self.assertEquals({}, load_mappings([':='])) | |
173 | self.assertEquals({}, load_mappings([':=:='])) | |
174 | self.assertEquals({}, load_mappings(['f:='])) | |
175 | self.assertEquals({}, load_mappings([':=b'])) | |
176 | self.assertEquals({}, load_mappings(['foo:=bar:=baz'])) | |
177 | # should ignore node param assignments | |
178 | self.assertEquals({}, load_mappings(['_foo:=bar'])) | |
179 | ||
180 | self.assertEquals({'foo': 'bar'}, load_mappings(['foo:=bar'])) | |
181 | # should allow double-underscore names | |
182 | self.assertEquals({'__foo': 'bar'}, load_mappings(['__foo:=bar'])) | |
183 | self.assertEquals({'foo': 'bar'}, load_mappings(['./f', '-x', '--blah', 'foo:=bar'])) | |
184 | self.assertEquals({'a': '1', 'b': '2', 'c': '3'}, load_mappings(['c:=3', 'c:=', ':=3', 'a:=1', 'b:=2'])) | |
185 | ||
186 | def test_resource_name(self): | |
187 | from roslib.names import resource_name | |
188 | self.assertEquals('foo/bar', resource_name('foo', 'bar')) | |
189 | self.assertEquals('bar', resource_name('foo', 'bar', my_pkg='foo')) | |
190 | self.assertEquals('foo/bar', resource_name('foo', 'bar', my_pkg='bar')) | |
191 | self.assertEquals('foo/bar', resource_name('foo', 'bar', my_pkg='')) | |
192 | self.assertEquals('foo/bar', resource_name('foo', 'bar', my_pkg=None)) | |
193 | ||
194 | def test_resource_name_base(self): | |
195 | from roslib.names import resource_name_base | |
196 | self.assertEquals('', resource_name_base('')) | |
197 | self.assertEquals('bar', resource_name_base('bar')) | |
198 | self.assertEquals('bar', resource_name_base('foo/bar')) | |
199 | self.assertEquals('bar', resource_name_base('/bar')) | |
200 | self.assertEquals('', resource_name_base('foo/')) | |
201 | ||
202 | def test_resource_name_package(self): | |
203 | from roslib.names import resource_name_package | |
204 | self.assertEquals(None, resource_name_package('')) | |
205 | self.assertEquals(None, resource_name_package('foo')) | |
206 | self.assertEquals('foo', resource_name_package('foo/')) | |
207 | self.assertEquals('foo', resource_name_package('foo/bar')) | |
208 | ||
209 | def test_package_resource_name(self): | |
210 | from roslib.names import package_resource_name | |
211 | self.assertEquals(('', ''), package_resource_name('')) | |
212 | self.assertEquals(('', 'foo'), package_resource_name('foo')) | |
213 | self.assertEquals(('foo', 'bar'), package_resource_name('foo/bar')) | |
214 | self.assertEquals(('foo', ''), package_resource_name('foo/')) | |
215 | try: | |
216 | # only allowed single separator | |
217 | package_resource_name("foo/bar/baz") | |
218 | self.fail("should have raised ValueError") | |
219 | except ValueError: | |
220 | pass | |
221 | ||
222 | ||
223 | def test_is_legal_resource_name(self): | |
224 | from roslib.names import is_legal_resource_name | |
225 | failures = [None, '', 'hello\n', '\t', 'foo++', 'foo-bar', '#foo', | |
226 | ' name', 'name ', | |
227 | '~name', '/name', | |
228 | '1name', 'foo\\'] | |
229 | for f in failures: | |
230 | self.failIf(is_legal_resource_name(f), f) | |
231 | tests = ['f', 'f1', 'f_', 'foo', 'foo_bar', 'foo/bar', 'roslib/Log'] | |
232 | for t in tests: | |
233 | self.assert_(is_legal_resource_name(t), t) | |
234 | ||
235 | def test_is_legal_name(self): | |
236 | from roslib.names import is_legal_name | |
237 | failures = [None, | |
238 | 'foo++', 'foo-bar', '#foo', | |
239 | 'hello\n', '\t', ' name', 'name ', | |
240 | 'f//b', | |
241 | '1name', 'foo\\'] | |
242 | for f in failures: | |
243 | self.failIf(is_legal_name(f), f) | |
244 | tests = ['', | |
245 | 'f', 'f1', 'f_', 'f/', 'foo', 'foo_bar', 'foo/bar', 'foo/bar/baz', | |
246 | '~f', '~a/b/c', | |
247 | '~/f', | |
248 | '/a/b/c/d', '/'] | |
249 | for t in tests: | |
250 | self.assert_(is_legal_name(t), "[%s]"%t) | |
251 | ||
252 | def test_is_legal_base_name(self): | |
253 | from roslib.names import is_legal_base_name | |
254 | failures = [None, '', 'hello\n', '\t', 'foo++', 'foo-bar', '#foo', | |
255 | 'f/', 'foo/bar', '/', '/a', | |
256 | 'f//b', | |
257 | '~f', '~a/b/c', | |
258 | ' name', 'name ', | |
259 | '1name', 'foo\\'] | |
260 | for f in failures: | |
261 | self.failIf(is_legal_base_name(f), f) | |
262 | tests = ['f', 'f1', 'f_', 'foo', 'foo_bar'] | |
263 | for t in tests: | |
264 | self.assert_(is_legal_base_name(t), "[%s]"%t) | |
265 | ||
266 | def test_is_legal_resource_base_name(self): | |
267 | from roslib.names import is_legal_resource_base_name | |
268 | failures = [None, '', 'hello\n', '\t', 'foo++', 'foo-bar', '#foo', | |
269 | 'f/', 'foo/bar', '/', '/a', | |
270 | 'f//b', | |
271 | '~f', '~a/b/c', | |
272 | '~/f', | |
273 | ' name', 'name ', | |
274 | '1name', 'foo\\'] | |
275 | for f in failures: | |
276 | self.failIf(is_legal_resource_base_name(f), f) | |
277 | tests = ['f', 'f1', 'f_', 'foo', 'foo_bar'] | |
278 | for t in tests: | |
279 | self.assert_(is_legal_resource_base_name(t), "[%s]"%t) | |
280 | ||
281 | def test_resolve_name(self): | |
282 | from roslib.names import resolve_name | |
283 | # TODO: test with remappings | |
284 | tests = [ | |
285 | ('', '/', '/'), | |
286 | ('', '/node', '/'), | |
287 | ('', '/ns1/node', '/ns1/'), | |
288 | ||
289 | ('foo', '', '/foo'), | |
290 | ('foo/', '', '/foo'), | |
291 | ('/foo', '', '/foo'), | |
292 | ('/foo/', '', '/foo'), | |
293 | ('/foo', '/', '/foo'), | |
294 | ('/foo/', '/', '/foo'), | |
295 | ('/foo', '/bar', '/foo'), | |
296 | ('/foo/', '/bar', '/foo'), | |
297 | ||
298 | ('foo', '/ns1/ns2', '/ns1/foo'), | |
299 | ('foo', '/ns1/ns2/', '/ns1/foo'), | |
300 | ('foo', '/ns1/ns2/ns3/', '/ns1/ns2/foo'), | |
301 | ('foo/', '/ns1/ns2', '/ns1/foo'), | |
302 | ('/foo', '/ns1/ns2', '/foo'), | |
303 | ('foo/bar', '/ns1/ns2', '/ns1/foo/bar'), | |
304 | ('foo//bar', '/ns1/ns2', '/ns1/foo/bar'), | |
305 | ('foo/bar', '/ns1/ns2/ns3', '/ns1/ns2/foo/bar'), | |
306 | ('foo//bar//', '/ns1/ns2/ns3', '/ns1/ns2/foo/bar'), | |
307 | ||
308 | ('~foo', '/', '/foo'), | |
309 | ('~foo', '/node', '/node/foo'), | |
310 | ('~foo', '/ns1/ns2', '/ns1/ns2/foo'), | |
311 | ('~foo/', '/ns1/ns2', '/ns1/ns2/foo'), | |
312 | ('~foo/bar', '/ns1/ns2', '/ns1/ns2/foo/bar'), | |
313 | ||
314 | # #3044 | |
315 | ('~/foo', '/', '/foo'), | |
316 | ('~/foo', '/node', '/node/foo'), | |
317 | ('~/foo', '/ns1/ns2', '/ns1/ns2/foo'), | |
318 | ('~/foo/', '/ns1/ns2', '/ns1/ns2/foo'), | |
319 | ('~/foo/bar', '/ns1/ns2', '/ns1/ns2/foo/bar'), | |
320 | ||
321 | ] | |
322 | for name, node_name, v in tests: | |
323 | self.assertEquals(v, resolve_name(name, node_name)) |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import struct | |
34 | import sys | |
35 | import unittest | |
36 | ||
37 | import roslib.packages | |
38 | ||
39 | class RoslibPackagesTest(unittest.TestCase): | |
40 | ||
41 | def test_find_node(self): | |
42 | import roslib.packages | |
43 | d = roslib.packages.get_pkg_dir('roslib') | |
44 | p = os.path.join(d, 'test', 'fake_node.py') | |
45 | self.assertEquals([p], roslib.packages.find_node('roslib', 'fake_node.py')) | |
46 | ||
47 | self.assertEquals([], roslib.packages.find_node('roslib', 'not_a_node')) | |
48 | ||
49 | def test_get_pkg_dir(self): | |
50 | import roslib.packages | |
51 | import roslib.rospack | |
52 | path = roslib.rospack.rospackexec(['find', 'roslib']) | |
53 | self.assertEquals(path, roslib.packages.get_pkg_dir('roslib')) | |
54 | try: | |
55 | self.assertEquals(path, roslib.packages.get_pkg_dir('fake_roslib')) | |
56 | self.fail("should have raised") | |
57 | except roslib.packages.InvalidROSPkgException: pass | |
58 | ||
59 | def test_get_dir_pkg(self): | |
60 | import roslib.packages | |
61 | path = get_roslib_path() | |
62 | ||
63 | res = roslib.packages.get_dir_pkg(path) | |
64 | res = (os.path.realpath(res[0]), res[1]) | |
65 | self.assertEquals((path, 'roslib'), res) | |
66 | res = roslib.packages.get_dir_pkg(os.path.join(path, 'test')) | |
67 | res = (os.path.realpath(res[0]), res[1]) | |
68 | self.assertEquals((path, 'roslib'), res) | |
69 | ||
70 | # must fail on parent of roslib | |
71 | self.assertEquals((None, None), roslib.packages.get_dir_pkg(os.path.dirname(path))) | |
72 | ||
73 | def get_roslib_path(): | |
74 | return os.path.realpath(os.path.abspath(os.path.join(get_test_path(), '..'))) | |
75 | ||
76 | def get_test_path(): | |
77 | return os.path.abspath(os.path.dirname(__file__)) |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import sys | |
34 | import unittest | |
35 | ||
36 | import roslib.rosenv | |
37 | ||
38 | class EnvTest(unittest.TestCase): | |
39 | ||
40 | def test_get_ros_root(self): | |
41 | from roslib.rosenv import get_ros_root | |
42 | self.assertEquals(None, get_ros_root(required=False, env={})) | |
43 | self.assertEquals(None, get_ros_root(False, {})) | |
44 | try: | |
45 | get_ros_root(required=True, env={}) | |
46 | self.fail("get_ros_root should have failed") | |
47 | except: pass | |
48 | ||
49 | env = {'ROS_ROOT': '/fake/path'} | |
50 | self.assertEquals('/fake/path', get_ros_root(required=False, env=env)) | |
51 | try: | |
52 | get_ros_root(required=True, env=env) | |
53 | self.fail("get_ros_root should have failed") | |
54 | except: pass | |
55 | ||
56 | def test_get_ros_package_path(self): | |
57 | from roslib.rosenv import get_ros_package_path | |
58 | self.assertEquals(None, get_ros_package_path(required=False, env={})) | |
59 | self.assertEquals(None, get_ros_package_path(False, {})) | |
60 | try: | |
61 | get_ros_package_path(required=True, env={}) | |
62 | self.fail("get_ros_package_path should have raised") | |
63 | except: pass | |
64 | env = {'ROS_PACKAGE_PATH': ':'} | |
65 | self.assertEquals(':', get_ros_package_path(True, env=env)) | |
66 | self.assertEquals(':', get_ros_package_path(False, env=env)) | |
67 | ||
68 | # trip-wire tests. Cannot guarantee that ROS_PACKAGE_PATH is set | |
69 | # to valid value on test machine, just make sure logic doesn't crash | |
70 | self.assertEquals(os.environ.get('ROS_PACKAGE_PATH', None), get_ros_package_path(required=False)) | |
71 | ||
72 | def test_get_ros_master_uri(self): | |
73 | from roslib.rosenv import get_master_uri | |
74 | self.assertEquals(None, get_master_uri(required=False, env={})) | |
75 | self.assertEquals(None, get_master_uri(False, {})) | |
76 | try: | |
77 | get_master_uri(required=True, env={}) | |
78 | self.fail("get_ros_package_path should have raised") | |
79 | except: pass | |
80 | env = {'ROS_MASTER_URI': 'http://localhost:1234'} | |
81 | self.assertEquals('http://localhost:1234', get_master_uri(True, env=env)) | |
82 | self.assertEquals('http://localhost:1234', get_master_uri(False, env=env)) | |
83 | ||
84 | argv = ['__master:=http://localhost:5678'] | |
85 | self.assertEquals('http://localhost:5678', get_master_uri(False, env=env, argv=argv)) | |
86 | ||
87 | try: | |
88 | argv = ['__master:=http://localhost:5678:=http://localhost:1234'] | |
89 | get_master_uri(required=False, env=env, argv=argv) | |
90 | self.fail("should have thrown") | |
91 | except roslib.rosenv.ROSEnvException: pass | |
92 | ||
93 | try: | |
94 | argv = ['__master:='] | |
95 | get_master_uri(False, env=env, argv=argv) | |
96 | self.fail("should have thrown") | |
97 | except roslib.rosenv.ROSEnvException: pass | |
98 | ||
99 | # make sure test works with os.environ | |
100 | self.assertEquals(os.environ.get('ROS_MASTER_URI', None), get_master_uri(required=False)) | |
101 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import sys | |
34 | import unittest | |
35 | ||
36 | import roslib | |
37 | ||
38 | def get_test_path(): | |
39 | return os.path.abspath(os.path.dirname(__file__)) | |
40 | ||
41 | class RoslibStackManifestTest(unittest.TestCase): | |
42 | ||
43 | def _subtest_parse_stack_example1(self, m): | |
44 | from roslib.manifestlib import _Manifest | |
45 | self.assert_(isinstance(m, _Manifest)) | |
46 | self.assertEquals('stack', m._type) | |
47 | self.assertEquals("a brief description", m.brief) | |
48 | self.assertEquals("Line 1\nLine 2", m.description.strip()) | |
49 | self.assertEquals("The authors\ngo here", m.author.strip()) | |
50 | self.assertEquals("Public Domain\nwith other stuff", m.license.strip()) | |
51 | self.assertEquals("http://ros.org/stack/", m.url) | |
52 | self.assertEquals("http://www.willowgarage.com/files/willowgarage/robot10.jpg", m.logo) | |
53 | dpkgs = [d.stack for d in m.depends] | |
54 | self.assertEquals(set(['stackname', 'common']), set(dpkgs)) | |
55 | self.assertEquals([], m.rosdeps) | |
56 | self.assertEquals([], m.exports) | |
57 | ||
58 | def _subtest_parse_stack_version(self, m): | |
59 | self.assertEquals("1.2.3", m.version) | |
60 | ||
61 | def test_parse_example1_file(self): | |
62 | from roslib.stack_manifest import parse_file, StackManifest | |
63 | ||
64 | p = os.path.join(get_test_path(), 'manifest_tests', 'stack_example1.xml') | |
65 | self._subtest_parse_stack_example1(parse_file(p)) | |
66 | ||
67 | p = os.path.join(get_test_path(), 'manifest_tests', 'stack_version.xml') | |
68 | self._subtest_parse_stack_version(parse_file(p)) | |
69 | ||
70 | def test_parse_example1_string(self): | |
71 | from roslib.manifestlib import parse, _Manifest | |
72 | self._subtest_parse_stack_example1(parse(_Manifest('stack'), STACK_EXAMPLE1)) | |
73 | ||
74 | def test_StackManifest(self): | |
75 | from roslib.stack_manifest import StackManifest | |
76 | m = StackManifest() | |
77 | self.assertEquals('stack', m._type) | |
78 | ||
79 | def test_StackManifest_str(self): | |
80 | # just make sure it doesn't crash | |
81 | from roslib.stack_manifest import parse | |
82 | str(parse(STACK_EXAMPLE1)) | |
83 | ||
84 | def test_StackManifest_xml(self): | |
85 | from roslib.stack_manifest import parse, StackManifest | |
86 | m = parse(STACK_EXAMPLE1) | |
87 | self._subtest_parse_stack_example1(m) | |
88 | # verify roundtrip | |
89 | m2 = parse(m.xml()) | |
90 | self._subtest_parse_stack_example1(m2) | |
91 | ||
92 | # bad file examples should be more like the roslaunch tests where there is just 1 thing wrong | |
93 | STACK_EXAMPLE1 = """<stack> | |
94 | <description brief="a brief description">Line 1 | |
95 | Line 2 | |
96 | </description> | |
97 | <author>The authors | |
98 | go here</author> | |
99 | <license>Public Domain | |
100 | with other stuff</license> | |
101 | <url>http://ros.org/stack/</url> | |
102 | <logo>http://www.willowgarage.com/files/willowgarage/robot10.jpg</logo> | |
103 | <depend stack="stackname" /> | |
104 | <depend stack="common"/> | |
105 | </stack>""" | |
106 | ||
107 | STACK_INVALID1 = """<stack> | |
108 | <description brief="a brief description">Line 1</description> | |
109 | <author>The authors</author> | |
110 | <license>Public Domain</license> | |
111 | <rosdep name="python" /> | |
112 | </stack>""" | |
113 | ||
114 | STACK_INVALID2 = """<stack> | |
115 | <description brief="a brief description">Line 1</description> | |
116 | <author>The authors</author> | |
117 | <license>Public Domain</license> | |
118 | <export> | |
119 | <cpp cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lros"/> | |
120 | <cpp os="osx" cflags="-I${prefix}/include" lflags="-L${prefix}/lib -lrosthread -framework CoreServices"/> | |
121 | </export> | |
122 | </stack>""" |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import sys | |
34 | import unittest | |
35 | ||
36 | import roslib | |
37 | import rospkg | |
38 | ||
39 | class RoslibStacksTest(unittest.TestCase): | |
40 | ||
41 | def test_list_stacks(self): | |
42 | from roslib.stacks import list_stacks | |
43 | l = list_stacks() | |
44 | self.assert_('ros' in l) | |
45 | ||
46 | # test with env | |
47 | test_dir = os.path.join(roslib.packages.get_pkg_dir('roslib'), 'test', 'stack_tests', 's1') | |
48 | env = os.environ.copy() | |
49 | env['ROS_PACKAGE_PATH'] = test_dir | |
50 | val = set(list_stacks(env=env)) | |
51 | # ros stack not guaranteed to list anymore as ROS_ROOT may not be set | |
52 | if 'ros' in val: | |
53 | val.remove('ros') | |
54 | self.assertEquals(set(['foo', 'bar']), val) | |
55 | ||
56 | ||
57 | def test_list_stacks_by_path(self): | |
58 | from roslib.stacks import list_stacks_by_path | |
59 | ||
60 | # test with synthetic stacks | |
61 | test_dir = os.path.join(roslib.packages.get_pkg_dir('roslib'), 'test', 'stack_tests') | |
62 | self.assertEquals(set(['bar', 'foo']), set(list_stacks_by_path(test_dir))) | |
63 | ||
64 | test_dir = os.path.join(roslib.packages.get_pkg_dir('roslib'), 'test', 'stack_tests', 's1') | |
65 | self.assertEquals(set(['bar', 'foo']), set(list_stacks_by_path(test_dir))) | |
66 | ||
67 | test_dir = os.path.join(roslib.packages.get_pkg_dir('roslib'), 'test', 'stack_tests', 's1', 'bar') | |
68 | self.assertEquals(['bar'], list_stacks_by_path(test_dir)) | |
69 | ||
70 | # test symlink following | |
71 | ||
72 | test_dir = os.path.join(roslib.packages.get_pkg_dir('roslib'), 'test', 'stack_tests2') | |
73 | self.assertEquals(set(['foo', 'bar']), set(list_stacks_by_path(test_dir))) | |
74 | ||
75 | def test_list_stacks_by_path_unary(self): | |
76 | from roslib.stacks import list_stacks_by_path | |
77 | # test with synthetic stacks | |
78 | test_dir = os.path.join(roslib.packages.get_pkg_dir('roslib'), 'test', 'stack_tests_unary') | |
79 | self.assertEquals(set(['bar', 'foo', 'baz']), set(list_stacks_by_path(test_dir))) | |
80 | ||
81 | def test_get_stack_dir_unary(self): | |
82 | # now manipulate the environment to test precedence | |
83 | # - save original RPP as we popen rosstack in other tests | |
84 | d = roslib.packages.get_pkg_dir('roslib') | |
85 | d = os.path.join(d, 'test', 'stack_tests_unary') | |
86 | s1_d = os.path.join(d, 's1') | |
87 | rpp = rospkg.get_ros_package_path() | |
88 | try: | |
89 | paths = [d] | |
90 | os.environ[rospkg.environment.ROS_PACKAGE_PATH] = os.pathsep.join(paths) | |
91 | self.assertEquals(os.path.join(s1_d, 'foo'), roslib.stacks.get_stack_dir('foo')) | |
92 | self.assertEquals(os.path.join(s1_d, 'bar'), roslib.stacks.get_stack_dir('bar')) | |
93 | self.assertEquals(os.path.join(s1_d, 'baz'), roslib.stacks.get_stack_dir('baz')) | |
94 | finally: | |
95 | #restore rpp | |
96 | if rpp is not None: | |
97 | os.environ[rospkg.environment.ROS_PACKAGE_PATH] = rpp | |
98 | else: | |
99 | del os.environ[rospkg.environment.ROS_PACKAGE_PATH] | |
100 | ||
101 | def test_get_stack_dir(self): | |
102 | import roslib.packages | |
103 | from roslib.stacks import get_stack_dir, InvalidROSStackException, list_stacks | |
104 | try: | |
105 | get_stack_dir('non_existent') | |
106 | self.fail("should have raised") | |
107 | except roslib.stacks.InvalidROSStackException: | |
108 | pass | |
109 | ||
110 | # now manipulate the environment to test precedence | |
111 | # - save original RPP as we popen rosstack in other tests | |
112 | rpp = os.environ.get(rospkg.environment.ROS_PACKAGE_PATH, None) | |
113 | try: | |
114 | d = roslib.packages.get_pkg_dir('roslib') | |
115 | d = os.path.join(d, 'test', 'stack_tests') | |
116 | ||
117 | # - s1/s2/s3 | |
118 | print "s1/s2/s3" | |
119 | paths = [os.path.join(d, p) for p in ['s1', 's2', 's3']] | |
120 | os.environ[rospkg.environment.ROS_PACKAGE_PATH] = os.pathsep.join(paths) | |
121 | # - run multiple times to test caching | |
122 | for i in xrange(2): | |
123 | stacks = roslib.stacks.list_stacks() | |
124 | self.assert_('foo' in stacks) | |
125 | self.assert_('bar' in stacks) | |
126 | ||
127 | foo_p = os.path.join(d, 's1', 'foo') | |
128 | bar_p = os.path.join(d, 's1', 'bar') | |
129 | self.assertEquals(foo_p, roslib.stacks.get_stack_dir('foo')) | |
130 | self.assertEquals(bar_p, roslib.stacks.get_stack_dir('bar')) | |
131 | ||
132 | # - s2/s3/s1 | |
133 | print "s2/s3/s1" | |
134 | ||
135 | paths = [os.path.join(d, p) for p in ['s2', 's3', 's1']] | |
136 | os.environ[rospkg.environment.ROS_PACKAGE_PATH] = os.pathsep.join(paths) | |
137 | stacks = roslib.stacks.list_stacks() | |
138 | self.assert_('foo' in stacks) | |
139 | self.assert_('bar' in stacks) | |
140 | ||
141 | foo_p = os.path.join(d, 's2', 'foo') | |
142 | bar_p = os.path.join(d, 's1', 'bar') | |
143 | self.assertEquals(foo_p, roslib.stacks.get_stack_dir('foo')) | |
144 | self.assertEquals(bar_p, roslib.stacks.get_stack_dir('bar')) | |
145 | finally: | |
146 | #restore rpp | |
147 | if rpp is not None: | |
148 | os.environ[rospkg.environment.ROS_PACKAGE_PATH] = rpp | |
149 | else: | |
150 | del os.environ[rospkg.environment.ROS_PACKAGE_PATH] | |
151 | ||
152 | def test_expand_to_packages_unary(self): | |
153 | # test unary | |
154 | test_dir = os.path.join(roslib.packages.get_pkg_dir('roslib'), 'test', 'stack_tests_unary') | |
155 | ||
156 | env = os.environ.copy() | |
157 | env[rospkg.environment.ROS_PACKAGE_PATH] = test_dir | |
158 | ||
159 | from roslib.stacks import expand_to_packages | |
160 | self.assertEquals((['foo'], []), expand_to_packages(['foo'], env=env)) | |
161 | self.assertEquals((['foo', 'bar'], []), expand_to_packages(['foo', 'bar'], env=env)) | |
162 | ||
163 | def test_expand_to_packages(self): | |
164 | from roslib.stacks import expand_to_packages | |
165 | try: | |
166 | # it's possible to accidentally pass in a sequence type | |
167 | # like a string and get weird results, so check that we | |
168 | # don't | |
169 | self.assertEquals(([], []), expand_to_packages('ros')) | |
170 | self.fail("expand_to_packages should only take in a list of strings") | |
171 | except ValueError: pass | |
172 | ||
173 | self.assertEquals(([], []), expand_to_packages([])) | |
174 | self.assertEquals((['rosmake', 'roslib', 'roslib'], []), expand_to_packages(['rosmake', 'roslib', 'roslib'])) | |
175 | self.assertEquals(([], ['bogus_one', 'bogus_two']), expand_to_packages(['bogus_one', 'bogus_two'])) | |
176 | ||
177 | self.assertEquals(([], ['bogus_one', 'bogus_two']), expand_to_packages(['bogus_one', 'bogus_two'])) | |
178 | ||
179 | # TODO: setup directory tree so that this can be more precisely calculated | |
180 | valid, invalid = expand_to_packages(['ros', 'bogus_one']) | |
181 | self.assertEquals(['bogus_one'], invalid) | |
182 | check = ['rosbuild', 'rosunit', 'roslib'] | |
183 | print valid | |
184 | for c in check: | |
185 | self.assert_(c in valid, "expected [%s] to be in ros expansion"%c) | |
186 | ||
187 | def test_get_stack_version(self): | |
188 | from roslib.stacks import get_stack_version | |
189 | ||
190 | test_dir = os.path.join(get_test_path(), 'stack_tests', 's1') | |
191 | env = os.environ.copy() | |
192 | env[rospkg.environment.ROS_PACKAGE_PATH] = test_dir | |
193 | ||
194 | # REP 109: stack.xml has precedence over CMakeLists.txt, version is whitespace stripped | |
195 | self.assertEquals('1.6.0-manifest', roslib.stacks.get_stack_version('foo', env=env)) | |
196 | # REP 109: test fallback to CMakeLists.txt version | |
197 | self.assertEquals('1.5.0-cmake', roslib.stacks.get_stack_version('bar', env=env)) | |
198 | ||
199 | if 0: | |
200 | test_dir = os.path.join(roslib.packages.get_pkg_dir('roslib'), 'test', 'stack_tests_unary') | |
201 | env = os.environ.copy() | |
202 | env[rospkg.environment.ROS_PACKAGE_PATH] = test_dir | |
203 | ||
204 | def get_test_path(): | |
205 | return os.path.abspath(os.path.dirname(__file__)) |
0 | /* | |
1 | * Copyright (c) 2008, Willow Garage, Inc. | |
2 | * All rights reserved. | |
3 | * | |
4 | * Redistribution and use in source and binary forms, with or without | |
5 | * modification, are permitted provided that the following conditions are met: | |
6 | * | |
7 | * * Redistributions of source code must retain the above copyright | |
8 | * notice, this list of conditions and the following disclaimer. | |
9 | * * Redistributions in binary form must reproduce the above copyright | |
10 | * notice, this list of conditions and the following disclaimer in the | |
11 | * documentation and/or other materials provided with the distribution. | |
12 | * * Neither the name of Willow Garage, Inc. nor the names of its | |
13 | * contributors may be used to endorse or promote products derived from | |
14 | * this software without specific prior written permission. | |
15 | * | |
16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
26 | * POSSIBILITY OF SUCH DAMAGE. | |
27 | */ | |
28 | ||
29 | /* Author: Brian Gerkey */ | |
30 | ||
31 | #include <string> | |
32 | #include <vector> | |
33 | #include <stdlib.h> | |
34 | #include <unistd.h> | |
35 | #include <boost/thread.hpp> | |
36 | #include <gtest/gtest.h> | |
37 | #include "ros/package.h" | |
38 | ||
39 | void string_split(const std::string &s, std::vector<std::string> &t, const std::string &d) | |
40 | { t.clear(); | |
41 | size_t start = 0, end; | |
42 | while ((end = s.find_first_of(d, start)) != std::string::npos) | |
43 | { | |
44 | if((end-start) > 0) | |
45 | t.push_back(s.substr(start, end-start)); | |
46 | start = end + 1; | |
47 | } | |
48 | if(start < s.size()) | |
49 | t.push_back(s.substr(start)); | |
50 | } | |
51 | ||
52 | char g_rr_buf[1024]; | |
53 | void set_env_vars(void) | |
54 | { | |
55 | // Point ROS_PACKAGE_PATH at the roslib directory, and point | |
56 | // ROS_ROOT into an empty directory. | |
57 | getcwd(g_rr_buf, sizeof(g_rr_buf)); | |
58 | setenv("ROS_PACKAGE_PATH", g_rr_buf, 1); | |
59 | strncpy(g_rr_buf+strlen(g_rr_buf), "/tmp.XXXXXX", sizeof(g_rr_buf)-strlen(g_rr_buf)-1); | |
60 | g_rr_buf[sizeof(g_rr_buf)-1] = '\0'; | |
61 | mkdtemp(g_rr_buf); | |
62 | setenv("ROS_ROOT", g_rr_buf, 1); | |
63 | } | |
64 | void cleanup_env_vars(void) | |
65 | { | |
66 | // Remove the empty directory that we created in set_env_vars(). | |
67 | rmdir(g_rr_buf); | |
68 | memset(g_rr_buf, 0, sizeof(g_rr_buf)); | |
69 | } | |
70 | ||
71 | TEST(roslib, commandListNames) | |
72 | { | |
73 | set_env_vars(); | |
74 | ||
75 | std::string output = ros::package::command("list-names"); | |
76 | std::vector<std::string> output_list; | |
77 | string_split(output, output_list, "\n"); | |
78 | ASSERT_EQ((int)output_list.size(), 1); | |
79 | ASSERT_STREQ(output_list[0].c_str(), "roslib"); | |
80 | ||
81 | cleanup_env_vars(); | |
82 | } | |
83 | ||
84 | TEST(roslib, commandList) | |
85 | { | |
86 | set_env_vars(); | |
87 | ||
88 | std::string output = ros::package::command("list"); | |
89 | std::vector<std::string> output_list; | |
90 | std::vector<std::string> name_path; | |
91 | string_split(output, output_list, "\n"); | |
92 | ASSERT_EQ((int)output_list.size(), 1); | |
93 | string_split(output_list[0], name_path, " "); | |
94 | ASSERT_EQ((int)name_path.size(), 2); | |
95 | ASSERT_STREQ(name_path[0].c_str(), "roslib"); | |
96 | ||
97 | cleanup_env_vars(); | |
98 | } | |
99 | ||
100 | TEST(roslib, getAll) | |
101 | { | |
102 | set_env_vars(); | |
103 | ||
104 | std::vector<std::string> output_list; | |
105 | ASSERT_TRUE(ros::package::getAll(output_list)); | |
106 | ASSERT_EQ((int)output_list.size(), 1); | |
107 | ASSERT_STREQ(output_list[0].c_str(), "roslib"); | |
108 | ||
109 | cleanup_env_vars(); | |
110 | } | |
111 | ||
112 | void | |
113 | roslib_caller() | |
114 | { | |
115 | struct timespec ts = {0, 100000}; | |
116 | std::string output; | |
117 | for(int i=0;i<100;i++) | |
118 | { | |
119 | output = ros::package::command("plugins --attrib=foo roslib"); | |
120 | nanosleep(&ts, NULL); | |
121 | } | |
122 | } | |
123 | ||
124 | TEST(roslib, concurrent_access) | |
125 | { | |
126 | set_env_vars(); | |
127 | const int size = 10; | |
128 | boost::thread t[size]; | |
129 | for(int i=0;i<size; i++) | |
130 | t[i] = boost::thread(boost::bind(roslib_caller)); | |
131 | for(int i=0;i<size; i++) | |
132 | t[i].join(); | |
133 | cleanup_env_vars(); | |
134 | } | |
135 | ||
136 | int main(int argc, char **argv) | |
137 | { | |
138 | testing::InitGoogleTest(&argc, argv); | |
139 | return RUN_ALL_TESTS(); | |
140 | } | |
141 |
0 | REM generated from ros/env-hooks/10.ros.bat.in | |
1 | ||
2 | REM scrub old ROS bin dirs, to avoid accidentally finding the wrong executables | |
3 | set PATH=`python -c "import os; print(os.pathsep.join([x for x in \"$PATH\".split(os.pathsep) if not any([d for d in ['cturtle', 'diamondback', 'electric', 'fuerte'] if d in x])]))"` | |
4 | ||
5 | set ROS_DISTRO=groovy | |
6 | if [%ROS_MASTER_URI]==[] ( | |
7 | set ROS_MASTER_URI=http://localhost:11311 | |
8 | ) | |
9 | ||
10 | REM python function to generate ROS package path based on all parent workspaces (prepends the separator if necessary) | |
11 | REM do not use EnableDelayedExpansion here, it messes with the != symbols | |
12 | echo from __future__ import print_function > _parent_package_path.py | |
13 | echo import os >> _parent_package_path.py | |
14 | echo env_name = 'CATKIN_WORKSPACES' >> _parent_package_path.py | |
15 | echo items = os.environ[env_name].split(';') if env_name in os.environ and os.environ[env_name] != '' else [] >> _parent_package_path.py | |
16 | echo path = '' >> _parent_package_path.py | |
17 | echo for item in items: >> _parent_package_path.py | |
18 | echo path += ':' + (os.path.join(item, 'share') if item.find(':') == -1 else item.split(':')[1]) >> _parent_package_path.py | |
19 | echo print(path) >> _parent_package_path.py | |
20 | ||
21 | setlocal EnableDelayedExpansion | |
22 | ||
23 | set ROS_PACKAGE_PATH_PARENTS= | |
24 | for /f %%a in ('python _parent_package_path.py') do set ROS_PACKAGE_PATH_PARENTS=!ROS_PACKAGE_PATH_PARENTS!%%a | |
25 | ||
26 | if [@BUILDSPACE@]==[true] ( | |
27 | set ROS_PACKAGE_PATH=@CMAKE_SOURCE_DIR@$ROS_PACKAGE_PATH_PARENTS | |
28 | set ROS_ROOT=@CMAKE_CURRENT_SOURCE_DIR@ | |
29 | set ROS_ETC_DIR=@CATKIN_BUILD_PREFIX@/@CATKIN_PACKAGE_ETC_DESTINATION@ | |
30 | ) | |
31 | if [@INSTALLSPACE@]==[true] ( | |
32 | set ROS_PACKAGE_PATH=@CMAKE_INSTALL_PREFIX@/share:@CMAKE_INSTALL_PREFIX@/stacks$ROS_PACKAGE_PATH_PARENTS | |
33 | set ROS_ROOT=@CMAKE_INSTALL_PREFIX@/@CATKIN_PACKAGE_SHARE_DESTINATION@ | |
34 | set ROS_ETC_DIR=@CMAKE_INSTALL_PREFIX@/@CATKIN_PACKAGE_ETC_DESTINATION@ | |
35 | ) | |
36 | ||
37 | del _parent_package_path.py | |
38 | ||
39 | endlocal |
0 | # generated from ros/env-hooks/10.ros.sh.in | |
1 | ||
2 | # scrub old ROS bin dirs, to avoid accidentally finding the wrong executables | |
3 | export PATH=`python -c "import os; print(os.pathsep.join([x for x in \"$PATH\".split(os.pathsep) if not any([d for d in ['cturtle', 'diamondback', 'electric', 'fuerte'] if d in x])]))"` | |
4 | ||
5 | export ROS_DISTRO=groovy | |
6 | if [ ! "$ROS_MASTER_URI" ] ; then | |
7 | export ROS_MASTER_URI=http://localhost:11311 | |
8 | fi | |
9 | ||
10 | # python function to generate ROS package path based on all workspaces | |
11 | PYTHON_CODE_BUILD_ROS_PACKAGE_PATH=$(cat <<EOF | |
12 | from __future__ import print_function | |
13 | import os | |
14 | env_name = 'CMAKE_PREFIX_PATH' | |
15 | paths = [path for path in os.environ[env_name].split(os.pathsep)] if env_name in os.environ and os.environ[env_name] != '' else [] | |
16 | workspaces = [path for path in paths if os.path.exists(os.path.join(path, '.CATKIN_WORKSPACE'))] | |
17 | paths = [] | |
18 | for workspace in workspaces: | |
19 | filename = os.path.join(workspace, '.CATKIN_WORKSPACE') | |
20 | data = '' | |
21 | with open(filename) as f: | |
22 | data = f.read() | |
23 | if data == '': | |
24 | paths.append(os.path.join(workspace, 'share')) | |
25 | paths.append(os.path.join(workspace, 'stacks')) | |
26 | else: | |
27 | for source_path in data.split(';'): | |
28 | paths.append(source_path) | |
29 | print(os.pathsep.join(paths)) | |
30 | EOF | |
31 | ) | |
32 | export ROS_PACKAGE_PATH=`python -c "$PYTHON_CODE_BUILD_ROS_PACKAGE_PATH"` | |
33 | ||
34 | if @BUILDSPACE@; then | |
35 | export ROS_ROOT=@CMAKE_CURRENT_SOURCE_DIR@ | |
36 | export ROS_ETC_DIR=@CATKIN_BUILD_PREFIX@/@CATKIN_PACKAGE_ETC_DESTINATION@ | |
37 | fi | |
38 | if @INSTALLSPACE@; then | |
39 | export ROS_ROOT=@CMAKE_INSTALL_PREFIX@/@CATKIN_PACKAGE_SHARE_DESTINATION@ | |
40 | export ROS_ETC_DIR=@CMAKE_INSTALL_PREFIX@/@CATKIN_PACKAGE_ETC_DESTINATION@ | |
41 | fi |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(ros) | |
2 | find_package(catkin REQUIRED) | |
3 | catkin_package() |
0 | <package> | |
1 | <name>ros</name> | |
2 | <version>1.9.11</version> | |
3 | <description>ROS packaging system</description> | |
4 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
5 | <license>BSD</license> | |
6 | ||
7 | <url type="website">http://www.ros.org/wiki/ROS</url> | |
8 | <url type="bugtracker">https://code.ros.org/trac/ros/query?status=assigned&status=new&status=reopened&component=ros&order=priority</url> | |
9 | <url type="repository">https://code.ros.org/svn/ros/stacks/ros/trunk/</url> | |
10 | ||
11 | <author>Eric Berger</author> | |
12 | <author>Ken Conley</author> | |
13 | <author>Josh Faust</author> | |
14 | <author>Tully Foote</author> | |
15 | <author>Brian Gerkey</author> | |
16 | <author>Jeremy Leibs</author> | |
17 | <author>Morgan Quigley</author> | |
18 | <author>Rob Wheeler</author> | |
19 | ||
20 | <!-- This is a metapackage, so it just run-depends on all its child | |
21 | packages. --> | |
22 | <run_depend>mk</run_depend> | |
23 | <run_depend>rosbuild</run_depend> | |
24 | <run_depend>roslang</run_depend> | |
25 | <run_depend>roslib</run_depend> | |
26 | <run_depend>rosbash</run_depend> | |
27 | <run_depend>rosboost_cfg</run_depend> | |
28 | <run_depend>rosclean</run_depend> | |
29 | <run_depend>roscreate</run_depend> | |
30 | <run_depend>rosmake</run_depend> | |
31 | <run_depend>rosunit</run_depend> | |
32 | ||
33 | <export> | |
34 | <metapackage/> | |
35 | </export> | |
36 | </package> |
0 | #!/usr/bin/env python | |
1 | ||
2 | from __future__ import print_function | |
3 | from setuptools import setup | |
4 | import sys | |
5 | from xml.etree.ElementTree import ElementTree | |
6 | ||
7 | try: | |
8 | root = ElementTree(None, 'stack.xml') | |
9 | version = root.findtext('version') | |
10 | except Exception as e: | |
11 | print('Could not extract version from your stack.xml:\n%s' % e, file=sys.stderr) | |
12 | sys.exit(-1) | |
13 | ||
14 | sys.path.insert(0, 'src') | |
15 | ||
16 | setup(name = 'roslib', | |
17 | version = version, | |
18 | packages = ['ros', 'roslib', 'rosunit', 'rosmake', 'rosclean', 'roscreate', 'rosboost_cfg'], | |
19 | package_dir = {'ros':'core/roslib/src/ros', | |
20 | 'roslib':'core/roslib/src/roslib', | |
21 | 'rosunit':'tools/rosunit/src/rosunit', | |
22 | 'rosmake':'tools/rosmake/src/rosmake', | |
23 | 'rosclean':'tools/rosclean/src/rosclean', | |
24 | 'roscreate':'tools/roscreate/src/roscreate', | |
25 | 'rosboost_cfg':'tools/rosboost_cfg/src/rosboost_cfg', | |
26 | }, | |
27 | package_data = { | |
28 | 'roscreate': ['*.tmpl'], | |
29 | }, | |
30 | install_requires=['rospkg'], | |
31 | scripts = ['tools/rosclean/scripts/rosclean', | |
32 | 'tools/rosmake/scripts/rosmake', | |
33 | 'tools/rosunit/scripts/rosunit', | |
34 | 'tools/rosboost_cfg/scripts/rosboost-cfg', | |
35 | 'tools/roscreate/scripts/roscreate-pkg', | |
36 | ], | |
37 | author = "Maintained by Ken Conley", | |
38 | author_email = "kwc@willowgarage.com", | |
39 | url = "http://www.ros.org/wiki/roslib", | |
40 | download_url = "http://pr.willowgarage.com/downloads/roslib/", | |
41 | keywords = ["ROS"], | |
42 | classifiers = [ | |
43 | "Programming Language :: Python", | |
44 | "License :: OSI Approved :: BSD License" ], | |
45 | description = "roslib", | |
46 | long_description = """\ | |
47 | Internal Python libraries for low-level 'ros' stack. This also installs the rosmake and rosclean tools. | |
48 | This does *not* include the ROS communication libraries. | |
49 | """, | |
50 | license = "BSD" | |
51 | ) |
0 | <stack> | |
1 | <name>ros</name> | |
2 | <version>1.9.9</version> | |
3 | <description brief="ROS core">ROS packaging system</description> | |
4 | <author>Eric Berger</author> | |
5 | <author>Ken Conley</author> | |
6 | <author>Josh Faust</author> | |
7 | <author>Tully Foote</author> | |
8 | <author>Brian Gerkey</author> | |
9 | <author>Jeremy Leibs</author> | |
10 | <author>Morgan Quigley</author> | |
11 | <author>Rob Wheeler</author> | |
12 | <maintainer email="tfoote@willowgarage.com">Tully Foote</maintainer> | |
13 | <license>BSD</license> | |
14 | <copyright>Willow Garage</copyright> | |
15 | <url>http://www.ros.org/wiki/ROS</url> | |
16 | <review status="doc reviewed" notes=""/> | |
17 | ||
18 | <build_depends>boost</build_depends> | |
19 | <build_depends>bzip2</build_depends> | |
20 | <build_depends>catkin</build_depends> | |
21 | <build_depends>gtest</build_depends> | |
22 | <build_depends>pkg-config</build_depends> | |
23 | <build_depends>python</build_depends> | |
24 | <build_depends>python-nose</build_depends> | |
25 | <build_depends>python-rospkg</build_depends> | |
26 | <build_depends>rospack</build_depends> | |
27 | ||
28 | <depends>boost</depends> | |
29 | <depends>bzip2</depends> | |
30 | <depends>catkin</depends> | |
31 | <depends>gtest</depends> | |
32 | <depends>python</depends> | |
33 | <depends>python-nose</depends> | |
34 | <depends>python-rospkg</depends> | |
35 | <depends>rospack</depends> | |
36 | </stack> |
0 | if @BUILDSPACE@; then | |
1 | . @CMAKE_CURRENT_SOURCE_DIR@/rosbash | |
2 | fi | |
3 | if @INSTALLSPACE@; then | |
4 | . @CMAKE_INSTALL_PREFIX@/share/rosbash/rosbash | |
5 | fi |
0 | if @BUILDSPACE@; then | |
1 | . @CMAKE_CURRENT_SOURCE_DIR@/rostcsh | |
2 | fi | |
3 | if @INSTALLSPACE@; then | |
4 | . @CMAKE_INSTALL_PREFIX@/share/rosbash/rostcsh | |
5 | fi |
0 | if @BUILDSPACE@; then | |
1 | . @CMAKE_CURRENT_SOURCE_DIR@/roszsh | |
2 | fi | |
3 | if @INSTALLSPACE@; then | |
4 | . @CMAKE_INSTALL_PREFIX@/share/rosbash/roszsh | |
5 | fi |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(rosbash) | |
2 | find_package(catkin) | |
3 | catkin_package() | |
4 | catkin_package_export() | |
5 | ||
6 | install(FILES rosbash rostcsh roszsh | |
7 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) | |
8 | install(PROGRAMS scripts/rosrun | |
9 | DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}) | |
10 | ||
11 | message(STATUS " Making toplevel forward script for bash script rosrun") | |
12 | set(BASH_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/scripts/rosrun) | |
13 | configure_file(${catkin_EXTRAS_DIR}/templates/script.bash.in | |
14 | ${CATKIN_BUILD_PREFIX}/bin/rosrun | |
15 | @ONLY) | |
16 | ||
17 | catkin_add_env_hooks(15.rosbash SHELLS bash tcsh zsh) |
0 | <package> | |
1 | <description brief="ros bash tools"> | |
2 | ||
3 | Assorted shell commands for using ros with bash. | |
4 | ||
5 | </description> | |
6 | <author>Jeremy Leibs, Thibault Kruse</author> | |
7 | <license>BSD</license> | |
8 | <url>http://pr.willowgarage.com/wiki/rosbash</url> | |
9 | <review status="doc reviewed"/> | |
10 | <platform os="ubuntu" version="9.04"/> | |
11 | <platform os="ubuntu" version="9.10"/> | |
12 | <platform os="ubuntu" version="10.04"/> | |
13 | <platform os="macports" version="macports"/> | |
14 | </package> |
0 | <package> | |
1 | <name>rosbash</name> | |
2 | <version>1.9.11</version> | |
3 | <description> | |
4 | ||
5 | Assorted shell commands for using ros with bash. | |
6 | ||
7 | </description> | |
8 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
9 | <license>BSD</license> | |
10 | ||
11 | <url>http://www.ros.org/wiki/rosbash</url> | |
12 | <author>Jeremy Leibs</author> | |
13 | <author>Thibault Kruse</author> | |
14 | ||
15 | <build_depend>catkin</build_depend> | |
16 | ||
17 | <export> | |
18 | <platform os="ubuntu" version="9.04"/> | |
19 | <platform os="ubuntu" version="9.10"/> | |
20 | <platform os="ubuntu" version="10.04"/> | |
21 | <platform os="macports" version="macports"/> | |
22 | </export> | |
23 | </package> |
0 | # rosbash - functions to support ROS users | |
1 | # Useful things to know: | |
2 | # 'local' variables get unset after function, all others stay forever | |
3 | # COMPREPLY is the var used by bash complete builtin | |
4 | ||
5 | function _rossed { | |
6 | if [[ `uname` == Darwin || `uname` == FreeBSD ]]; then | |
7 | sed -E "$@" | |
8 | else | |
9 | sed -r "$@" | |
10 | fi | |
11 | } | |
12 | ||
13 | function _rosfind { | |
14 | if [[ `uname` == Darwin || `uname` == FreeBSD ]]; then | |
15 | # BSD find needs -E for extended regexp | |
16 | find -E "$@" | |
17 | else | |
18 | find "$@" | |
19 | fi | |
20 | } | |
21 | ||
22 | # _ros_location_find | |
23 | # based on $ROS_LOCATIONS, which the user can set to any colon | |
24 | # separated of key=folder pairs also resolves keys 'log' and | |
25 | # 'test_results' to ROS defaults finally resolves to package, then | |
26 | # stack echoes its result | |
27 | function _ros_location_find { | |
28 | local homedir ROS_LOCATION_KEYS_ARR ROS_LOCATIONS_ARR loc | |
29 | homedir=`echo $HOME | sed -e "s/\//\t\//g" -e "s/\t/\\\\\/g"` | |
30 | ROS_LOCATION_KEYS_ARR=(`echo $ROS_LOCATIONS | _rossed -e 's/([^:=]*)=([^:=]*)(:*[^=])*(:|$)/\1 /g'`) | |
31 | ROS_LOCATIONS_ARR=(`echo $ROS_LOCATIONS | _rossed -e 's/([^:=]*)=([^:=]*)(:*[^=])*(:|$)/\2 /g' -e "s/~/${homedir}/g"`) | |
32 | ||
33 | for (( i = 0 ; i < ${#ROS_LOCATION_KEYS_ARR[@]} ; i++ )); do | |
34 | if [[ $1 == ${ROS_LOCATION_KEYS_ARR[$i]} ]]; then | |
35 | echo ${ROS_LOCATIONS_ARR[i]} | |
36 | return 0 | |
37 | fi | |
38 | done | |
39 | ||
40 | if [[ $1 == log ]]; then | |
41 | echo `roslaunch-logs` | |
42 | return 0 | |
43 | elif [[ $1 == test_results ]]; then | |
44 | echo `rosrun rosunit test_results_dir.py` | |
45 | return 0 | |
46 | fi | |
47 | ||
48 | loc=`export ROS_CACHE_TIMEOUT=-1.0 && rospack find $1 2> /dev/null` | |
49 | if [[ $? != 0 ]]; then | |
50 | loc=`export ROS_CACHE_TIMEOUT=-1.0 && rosstack find $1 2> /dev/null` | |
51 | if [[ $? != 0 ]]; then | |
52 | return 1 | |
53 | fi | |
54 | echo $loc | |
55 | return 0 | |
56 | fi | |
57 | echo $loc | |
58 | return 0 | |
59 | } | |
60 | ||
61 | function _ros_list_locations { | |
62 | local ROS_LOCATION_KEYS packages stacks | |
63 | ROS_LOCATION_KEYS=`echo $ROS_LOCATIONS | _rossed -e 's/([^:=]*)=([^:=]*)(:*[^=])*(:|$)/\1 /g'` | |
64 | packages=`export ROS_CACHE_TIMEOUT=-1.0 && rospack list-names` | |
65 | stacks=`export ROS_CACHE_TIMEOUT=-1.0 && rosstack list-names` | |
66 | echo $packages $stacks log test_results $ROS_LOCATION_KEYS | tr ' ' '\n' | |
67 | return 0 | |
68 | } | |
69 | ||
70 | function _ros_package_find { | |
71 | local loc | |
72 | loc=`export ROS_CACHE_TIMEOUT=-1.0 && rospack find $1 2> /dev/null` | |
73 | if [[ $? != 0 ]]; then | |
74 | return 1 | |
75 | fi | |
76 | echo $loc | |
77 | return 0 | |
78 | } | |
79 | ||
80 | function _ros_list_packages { | |
81 | local packages | |
82 | packages=`export ROS_CACHE_TIMEOUT=-1.0 && rospack list-names` | |
83 | echo $packages | tr ' ' '\n' | |
84 | return 0 | |
85 | } | |
86 | ||
87 | function _ros_list_stacks { | |
88 | local stacks | |
89 | stacks=`export ROS_CACHE_TIMEOUT=-1.0 && rosstack list-names` | |
90 | echo $stacks | tr ' ' '\n' | |
91 | return 0 | |
92 | } | |
93 | ||
94 | # takes as argument either just a package-path or just a pkgname | |
95 | # returns 0 for no argument or if package (+ path) exist, 1 else | |
96 | # on success with arguments returns [pkgname, abspath, relpath basename] | |
97 | function _ros_decode_path { | |
98 | local rosname rosdir reldir last | |
99 | rosvals=() | |
100 | if [[ -z $1 ]]; then | |
101 | return 0 | |
102 | fi | |
103 | ||
104 | if [[ $1 =~ .+/.* ]]; then | |
105 | rosname=${1%%/*} | |
106 | reldir=/${1#*/} | |
107 | last=${reldir##*/} | |
108 | reldir=${reldir%/*}/ | |
109 | else | |
110 | rosname=$1 | |
111 | if [[ -z $2 || $2 != "forceeval" ]]; then | |
112 | rosvals=(${rosname}) | |
113 | return 1 | |
114 | fi | |
115 | fi | |
116 | ||
117 | rosdir=`_ros_location_find $rosname` | |
118 | if [[ $? != 0 ]]; then | |
119 | rosvals=(${rosname}) | |
120 | return 1 | |
121 | fi | |
122 | ||
123 | rosvals=(${rosname} ${rosdir} ${reldir} ${last}) | |
124 | } | |
125 | ||
126 | function rospython { | |
127 | local pkgname | |
128 | if [[ $1 = "--help" ]]; then | |
129 | echo -e "usage: rospython [package] \n\nRun python loading package manifest first." | |
130 | return 0 | |
131 | fi | |
132 | if [[ -z $1 ]]; then | |
133 | if [[ -f ./manifest.xml ]]; then | |
134 | pkgname=`basename \`pwd\`` | |
135 | python -i -c "import roslib; roslib.load_manifest('$pkgname')" | |
136 | else | |
137 | python | |
138 | fi | |
139 | else | |
140 | python -i -c "import roslib; roslib.load_manifest('$1')" | |
141 | fi | |
142 | } | |
143 | ||
144 | function roscd { | |
145 | local rosvals | |
146 | if [[ $1 = "--help" ]] | [[ $# -gt 1 ]]; then | |
147 | echo -e "usage: roscd package\n\nJump to target package." | |
148 | return 0 | |
149 | fi | |
150 | if [ -z $1 ]; then | |
151 | if [ -z $ROS_WORKSPACE ]; then | |
152 | echo -e "No ROS_WORKSPACE set. Please set ROS_WORKSPACE to use roscd with no arguments." | |
153 | return 1 | |
154 | fi | |
155 | cd ${ROS_WORKSPACE} | |
156 | return 0 | |
157 | fi | |
158 | ||
159 | _ros_decode_path $1 forceeval | |
160 | if [ $? != 0 ]; then | |
161 | echo "roscd: No such package/stack '$1'" | |
162 | return 1 | |
163 | elif [ -z $rosvals ]; then | |
164 | if [ -z $ROS_WORKSPACE ]; then | |
165 | echo -e "No ROS_WORKSPACE set. Please set ROS_WORKSPACE to use roscd with no arguments." | |
166 | return 1 | |
167 | fi | |
168 | cd ${ROS_WORKSPACE} | |
169 | return 0 | |
170 | else | |
171 | cd ${rosvals[1]}${rosvals[2]}${rosvals[3]} | |
172 | return 0 | |
173 | fi | |
174 | } | |
175 | ||
176 | function _is_integer { | |
177 | [ "$1" -eq "$1" ] > /dev/null 2>&1 | |
178 | return $? | |
179 | } | |
180 | ||
181 | function rosd { | |
182 | if [[ $1 = "--help" ]]; then | |
183 | echo -e "usage: rosd\n\nDisplays the list of currently remembered directories with indexes." | |
184 | return 0 | |
185 | fi | |
186 | let count=0; | |
187 | for items in `dirs`; | |
188 | do | |
189 | echo $count $items; | |
190 | let count=$((count+1)); | |
191 | done | |
192 | } | |
193 | ||
194 | function rospd { | |
195 | if [[ $1 = "--help" ]]; then | |
196 | echo -e "usage: rospd\n\nLike pushd, also accepts indexes from rosd." | |
197 | return 0 | |
198 | fi | |
199 | if _is_integer $1; then | |
200 | pushd +$1 > /dev/null ; | |
201 | else | |
202 | local rosvals | |
203 | _ros_decode_path $1 forceeval | |
204 | pushd ${rosvals[1]}${rosvals[2]}${rosvals[3]} > /dev/null ; | |
205 | fi | |
206 | rosd | |
207 | } | |
208 | ||
209 | function rosls { | |
210 | local rosvals | |
211 | if [[ $1 = "--help" ]]; then | |
212 | echo -e "usage: rosls [package]\n\nLists contents of a package directory." | |
213 | return 0 | |
214 | fi | |
215 | _ros_decode_path $1 forceeval | |
216 | ls ${rosvals[1]}${rosvals[2]}${rosvals[3]} $2 | |
217 | } | |
218 | ||
219 | # sets arg as return value | |
220 | function _roscmd { | |
221 | local pkgdir exepath opt catkin_project_libexec_dir opts | |
222 | if [[ -n $CMAKE_PREFIX_PATH ]]; then | |
223 | catkin_project_libexec_dir=`catkin_find --first-only --libexec $1 2> /dev/null` | |
224 | fi | |
225 | pkgdir=`_ros_package_find $1` | |
226 | if [[ -z $catkin_project_libexec_dir && -z $pkgdir ]]; then | |
227 | echo "Couldn't find package [$1]" | |
228 | return 1 | |
229 | fi | |
230 | exepath=(`find -L $catkin_project_libexec_dir $pkgdir -name $2 -type f ! -regex .*/[.].* ! -regex .*$pkgdir\/build\/.* | uniq`) | |
231 | if [[ ${#exepath[@]} == 0 ]] ; then | |
232 | echo "That file does not exist in that package." | |
233 | return 1 | |
234 | elif [[ ${#exepath[@]} -gt 1 ]] ; then | |
235 | echo "You have chosen a non-unique filename, please pick one of the following:" | |
236 | select opt in ${exepath[@]}; do | |
237 | echo $opt | |
238 | break | |
239 | done | |
240 | else | |
241 | opt=${exepath[0]} | |
242 | fi | |
243 | arg=${opt} | |
244 | } | |
245 | ||
246 | function rosed { | |
247 | local arg | |
248 | if [[ $1 = "--help" ]]; then | |
249 | echo -e "usage: rossed [package] [file]\n\nEdit a file within a package." | |
250 | return 0 | |
251 | fi | |
252 | _roscmd ${1} ${2} | |
253 | if [[ -n ${arg} ]]; then | |
254 | if [[ -z $EDITOR ]]; then | |
255 | vim ${arg} | |
256 | else | |
257 | $EDITOR ${arg} | |
258 | fi | |
259 | fi | |
260 | } | |
261 | ||
262 | function roscp { | |
263 | local arg | |
264 | if [[ $1 = "--help" ]] | [[ $# -ne 3 ]]; then | |
265 | echo -e "usage: roscp package filename target\n\nCopy a file from a package to target location." | |
266 | return 0 | |
267 | fi | |
268 | _roscmd ${1} ${2} | |
269 | cp ${arg} ${3} | |
270 | } | |
271 | ||
272 | function rosawesome { | |
273 | alias megamaid=rosrecord | |
274 | alias suck2blow=rosplay | |
275 | alias botherder=roscore | |
276 | } | |
277 | ||
278 | function _roscomplete { | |
279 | local arg opts | |
280 | COMPREPLY=() | |
281 | arg="${COMP_WORDS[COMP_CWORD]}" | |
282 | opts="`_ros_list_packages` `_ros_list_stacks`" | |
283 | IFS=$'\n' | |
284 | COMPREPLY=($(compgen -W "${opts}" -- ${arg})) | |
285 | unset IFS | |
286 | } | |
287 | ||
288 | function _roscomplete_rosmake { | |
289 | local arg | |
290 | COMPREPLY=() | |
291 | arg="${COMP_WORDS[COMP_CWORD]}" | |
292 | _roscomplete | |
293 | if [[ ${arg} =~ \-\-.* ]]; then | |
294 | COMPREPLY=(${COMPREPLY[@]} $(compgen -W "--test-only --all --mark-installed --unmark-installed --robust --build-everything --specified-only --buildtest --buildtest1 --output --pre-clean --bootstrap --disable-logging --target --pjobs= --threads --profile --skip-blacklist --status-rate" -- ${arg})) | |
295 | fi | |
296 | } | |
297 | ||
298 | function _roscomplete_sub_dir { | |
299 | local arg opts rosvals | |
300 | COMPREPLY=() | |
301 | arg="${COMP_WORDS[COMP_CWORD]}" | |
302 | _ros_decode_path ${arg} | |
303 | if [[ -z ${rosvals[2]} ]]; then | |
304 | opts=`_ros_list_locations` | |
305 | IFS=$'\n' | |
306 | COMPREPLY=($(compgen -W "${opts}" -S / -- ${rosvals[0]})) | |
307 | unset IFS | |
308 | else | |
309 | if [ -e ${rosvals[1]}${rosvals[2]} ]; then | |
310 | opts=`find -L ${rosvals[1]}${rosvals[2]} -maxdepth 1 -mindepth 1 -type d ! -regex ".*/[.].*" -print0 | tr '\000' '\n' | sed -e "s/.*\/\(.*\)/\1\//g"` | |
311 | else | |
312 | opts='' | |
313 | fi | |
314 | IFS=$'\n' | |
315 | COMPREPLY=($(compgen -P ${rosvals[0]}${rosvals[2]} -W "${opts}" -- ${rosvals[3]})) | |
316 | unset IFS | |
317 | fi | |
318 | } | |
319 | ||
320 | function _msg_opts { | |
321 | local arg pkgs pkgname msgname searchmsg path | |
322 | ||
323 | if [[ $1 =~ .+/.* ]]; then | |
324 | pkgname=${1%%/*} | |
325 | msgname=${1#*/} | |
326 | searchmsg=1 | |
327 | else | |
328 | pkgname=${1} | |
329 | fi | |
330 | ||
331 | if [[ -z ${searchmsg} ]]; then | |
332 | pkgs=(`rospack list`) | |
333 | ||
334 | for (( i = 0 ; i < ${#pkgs[@]} ; i=i+2 )); do | |
335 | if [[ -d ${pkgs[i+1]}/msg ]]; then | |
336 | echo ${pkgs[i]}/ | |
337 | fi | |
338 | done | |
339 | else | |
340 | path=`rospack find ${pkgname}` | |
341 | if [ -d ${path}/msg ]; then | |
342 | echo `find -L ${path}/msg -maxdepth 1 -mindepth 1 -name *.msg ! -regex ".*/[.].*" -print0 | tr '\000' '\n' | sed -e "s/.*\/\(.*\)\.msg/${pkgname}\/\1/g"` | |
343 | fi | |
344 | fi | |
345 | } | |
346 | ||
347 | function _srv_opts { | |
348 | local arg pkgs pkgname srvname searchsrv path count opts | |
349 | ||
350 | if [[ $1 =~ .+/.* ]]; then | |
351 | pkgname=${1%%/*} | |
352 | srvname=${1#*/} | |
353 | searchsrv=1 | |
354 | else | |
355 | pkgname=${1} | |
356 | fi | |
357 | ||
358 | if [[ -z ${searchsrv} ]]; then | |
359 | pkgs=(`rospack list | grep "^${pkgname}"`) | |
360 | count=0 | |
361 | ||
362 | opts="" | |
363 | ||
364 | for (( i = 0 ; i < ${#pkgs[@]} ; i=i+2 )); do | |
365 | if [[ -d ${pkgs[i+1]}/srv ]]; then | |
366 | opts="$opts ${pkgs[i]}/" | |
367 | pkgname=${pkgs[i]} | |
368 | count=$((count+1)) | |
369 | fi | |
370 | done | |
371 | ||
372 | if [[ $count -gt 1 ]]; then | |
373 | echo $opts | |
374 | return 0 | |
375 | fi | |
376 | fi | |
377 | ||
378 | path=`rospack find ${pkgname} 2> /dev/null` | |
379 | ||
380 | if [ $? -eq 0 ] && [ -d ${path}/srv ]; then | |
381 | echo `find -L ${path}/srv -maxdepth 1 -mindepth 1 -name *.srv ! -regex ".*/[.].*" -print0 | tr '\000' '\n' | sed -e "s/.*\/\(.*\)\.srv/${pkgname}\/\1/g"` | |
382 | fi | |
383 | } | |
384 | ||
385 | function _roscomplete_rossrv { | |
386 | COMPREPLY=() | |
387 | arg="${COMP_WORDS[COMP_CWORD]}" | |
388 | ||
389 | if [[ $COMP_CWORD == 1 ]]; then | |
390 | opts="show list md5 package packages" | |
391 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
392 | elif [[ $COMP_CWORD == 2 ]]; then | |
393 | case ${COMP_WORDS[1]} in | |
394 | show|users|md5) | |
395 | opts=`_srv_opts ${COMP_WORDS[$COMP_CWORD]}` | |
396 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
397 | ;; | |
398 | package) | |
399 | opts=`rospack list-names` | |
400 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
401 | ;; | |
402 | packages|list) | |
403 | # This shouldn't really have a completion rule | |
404 | ;; | |
405 | esac | |
406 | fi | |
407 | } | |
408 | ||
409 | function _roscomplete_search_dir { | |
410 | local arg opts pkgdir catkin_project_libexec_dir | |
411 | COMPREPLY=() | |
412 | arg="${COMP_WORDS[COMP_CWORD]}" | |
413 | if [[ $COMP_CWORD == 1 ]]; then | |
414 | opts=`_ros_list_packages` | |
415 | IFS=$'\n' | |
416 | COMPREPLY=($(compgen -W "${opts}" -- ${arg})) | |
417 | unset IFS | |
418 | elif [[ $COMP_CWORD == 2 ]]; then | |
419 | if [[ -n $CMAKE_PREFIX_PATH ]]; then | |
420 | catkin_project_libexec_dir=`catkin_find --first-only --libexec ${COMP_WORDS[1]} 2> /dev/null` | |
421 | fi | |
422 | pkgdir=`_ros_package_find ${COMP_WORDS[1]}` | |
423 | if [[ -n "$catkin_project_libexec_dir_dir" || -n "$pkgdir" ]]; then | |
424 | opts=`_rosfind -L $catkin_project_libexec_dir $pkgdir ${1} ! -regex ".*/[.].*" ! -regex ".*$pkgdir\/build\/.*" -print0 | tr '\000' '\n' | sed -e "s/.*\/\(.*\)/\1/g"` | |
425 | else | |
426 | opts="" | |
427 | fi | |
428 | IFS=$'\n' | |
429 | COMPREPLY=($(compgen -W "${opts}" -- ${arg})) | |
430 | unset IFS | |
431 | else | |
432 | homedir=`echo $HOME | sed -e "s/\//\t\//g" -e "s/\t/\\\\\/g"` | |
433 | arg=`echo ${arg} | sed -e "s/~/${homedir}/g"` | |
434 | if [[ $arg =~ ^/*.+/.* ]]; then | |
435 | path=${arg%/*} | |
436 | else | |
437 | path=. | |
438 | fi | |
439 | if [[ -e ${path} ]]; then | |
440 | opts=`find -L $path -maxdepth 1 -type d ! -regex ".*/[.].*" ! -regex "^[.]/" -print0 | tr '\000' '\n' | sed -e "s/$/\//g" -e "s/^[.]\///g" -e "s/'/\\\\\'/g" -e "s/ /\\\\\ /g"`$'\n'`find -L $path -maxdepth 1 -type f ! -regex ".*/[.][^.]*" ! -regex "^[.]/" -print0 | tr '\000' '\n' | sed -e "s/^[.]\///g" -e "s/'/\\\\\'/g" -e "s/ /\\\\\ /g"` | |
441 | else | |
442 | opts="" | |
443 | fi | |
444 | IFS=$'\n' | |
445 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
446 | unset IFS | |
447 | if [[ ${#COMPREPLY[*]} = 1 ]]; then | |
448 | newpath=${COMPREPLY[0]%/*} | |
449 | if [[ -d ${newpath} ]]; then | |
450 | opts=`find -L $newpath -maxdepth 1 -type d ! -regex ".*/[.].*" ! -regex "^[.]/" -print0 | tr '\000' '\n' | sed -e "s/$/\//g" -e "s/^[.]\///g" -e "s/'/\\\\\'/g" -e "s/ /\\\\\ /g"`$'\n'`find -L $newpath -maxdepth 1 -type f ! -regex ".*/[.][^.]*" ! -regex "^[.]/" -print0 | tr '\000' '\n' | sed -e "s/^[.]\///g" -e "s/'/\\\\\'/g" -e "s/ /\\\\\ /g"` | |
451 | IFS=$'\n' | |
452 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
453 | unset IFS | |
454 | fi | |
455 | fi | |
456 | fi | |
457 | ||
458 | } | |
459 | ||
460 | function _roscomplete_exe { | |
461 | _roscomplete_search_dir "-type f -perm +111" | |
462 | } | |
463 | ||
464 | function _roscomplete_file { | |
465 | _roscomplete_search_dir "-type f ! -regex .*[.][oa]$" | |
466 | } | |
467 | ||
468 | function _roscomplete_launch { | |
469 | arg="${COMP_WORDS[COMP_CWORD]}" | |
470 | COMPREPLY=() | |
471 | if [[ ${arg} =~ \-\-.* ]]; then | |
472 | COMPREPLY=(${COMPREPLY[@]} $(compgen -W "--files --args --nodes --find-node --child --local --screen --server_uri --run_id --wait --port --core --pid --dump-params" -- ${arg})) | |
473 | ||
474 | else | |
475 | _roscomplete_search_dir "( -type f -regex .*\.launch$ -o -type f -regex .*\.test$ )" | |
476 | if [[ $COMP_CWORD == 1 ]]; then | |
477 | COMPREPLY=($(compgen -o plusdirs -f -X "!*.launch" -- ${arg}) ${COMPREPLY[@]} $(compgen -o plusdirs -f -X "!*.test" -- ${arg}) ${COMPREPLY[@]}) | |
478 | fi | |
479 | fi | |
480 | } | |
481 | ||
482 | function _roscomplete_test { | |
483 | arg="${COMP_WORDS[COMP_CWORD]}" | |
484 | if [[ ${arg} =~ \-\-.* ]]; then | |
485 | COMPREPLY=(${COMPREPLY[@]} $(compgen -W "--bare --bare-limit --bare-name --pkgdir --package" -- ${arg})) | |
486 | else | |
487 | _roscomplete_search_dir "( -type f -regex .*\.launch$ -o -type f -regex .*\.test$ )" | |
488 | if [[ $COMP_CWORD == 1 ]]; then | |
489 | COMPREPLY=($(compgen -o plusdirs -f -X "!*.launch" -- ${arg}) ${COMPREPLY[@]} $(compgen -o plusdirs -f -X "!*.test" -- ${arg}) ${COMPREPLY[@]}) | |
490 | fi | |
491 | fi | |
492 | } | |
493 | ||
494 | function _roscomplete_rosbag { | |
495 | local opts | |
496 | COMPREPLY=() | |
497 | arg="${COMP_WORDS[COMP_CWORD]}" | |
498 | ||
499 | if [[ $COMP_CWORD == 1 ]]; then | |
500 | opts="check compress decompress filter fix help info play record reindex" | |
501 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
502 | else | |
503 | if [[ ${arg} =~ \-\-.* ]]; then | |
504 | case ${COMP_WORDS[1]} in | |
505 | check) | |
506 | opts="--genrules --append --noplugins --help" | |
507 | ;; | |
508 | compress|decompress) | |
509 | opts="--output-dir --force --quiet --help" | |
510 | ;; | |
511 | filter) | |
512 | opts="--print --help" | |
513 | ;; | |
514 | fix) | |
515 | opts="--force --noplugins --help" | |
516 | ;; | |
517 | info) | |
518 | opts="--yaml --key --freq --help" | |
519 | ;; | |
520 | play) | |
521 | opts="--help --quiet --immediate --pause --queue --clock --hz --delay --rate --start --skip-empty --loop --keep-alive --try-future-version --topics --bags" | |
522 | ;; | |
523 | record) | |
524 | opts="--help --all --regex --exclude --quiet --output-prefix --output-name --split --size --duration --buffsize --limit --node --bz2" | |
525 | ;; | |
526 | reindex) | |
527 | opts="--help --force --quiet --output-dir" | |
528 | ;; | |
529 | esac | |
530 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
531 | fi | |
532 | fi | |
533 | ||
534 | } | |
535 | ||
536 | function _roscomplete_rospack { | |
537 | local arg opts | |
538 | COMPREPLY=() | |
539 | arg="${COMP_WORDS[COMP_CWORD]}" | |
540 | ||
541 | if [[ $COMP_CWORD == 1 ]]; then | |
542 | opts="help find list list-names list-duplicates langs depends depends-manifests depends1 depends-indent depends-msgsrv depends-why rosdep rosdep0 vcs vcs0 depends-on depends-on1 export plugins cflags-only-I cflags-only-other libs-only-L libs-only-l libs-only-other profile" | |
543 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
544 | else | |
545 | opts=`rospack list-names` | |
546 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
547 | fi | |
548 | ||
549 | } | |
550 | ||
551 | function _roscomplete_rosnode { | |
552 | local arg opts | |
553 | COMPREPLY=() | |
554 | arg="${COMP_WORDS[COMP_CWORD]}" | |
555 | ||
556 | if [[ $COMP_CWORD == 1 ]]; then | |
557 | opts="ping list info machine kill" | |
558 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
559 | elif [[ $COMP_CWORD == 2 ]]; then | |
560 | case ${COMP_WORDS[1]} in | |
561 | info) | |
562 | opts=`rosnode list 2> /dev/null` | |
563 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
564 | ;; | |
565 | ping|list|kill) | |
566 | if [[ ${arg} =~ \-\-.* ]]; then | |
567 | opts="--all --help" | |
568 | else | |
569 | opts=`rosnode list 2> /dev/null` | |
570 | fi | |
571 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
572 | ;; | |
573 | machine) | |
574 | # This takes more logic to determine which machines are present. | |
575 | ;; | |
576 | esac | |
577 | else | |
578 | case ${COMP_WORDS[1]} in | |
579 | kill) | |
580 | # complete on node name | |
581 | opts=`rosnode list 2> /dev/null` | |
582 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
583 | ;; | |
584 | esac | |
585 | fi | |
586 | ||
587 | } | |
588 | function _roscomplete_rosparam { | |
589 | local arg opts | |
590 | COMPREPLY=() | |
591 | arg="${COMP_WORDS[COMP_CWORD]}" | |
592 | ||
593 | if [[ $COMP_CWORD == 1 ]]; then | |
594 | opts="set get load dump delete list" | |
595 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
596 | elif [[ $COMP_CWORD == 2 ]]; then | |
597 | case ${COMP_WORDS[1]} in | |
598 | set|get|delete|list) | |
599 | opts=`rosparam list 2> /dev/null` | |
600 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
601 | ;; | |
602 | load|dump) | |
603 | # complete on files | |
604 | COMPREPLY=($(compgen -f -- ${arg})) | |
605 | ;; | |
606 | esac | |
607 | elif [[ $COMP_CWORD == 3 ]]; then | |
608 | case ${COMP_WORDS[1]} in | |
609 | load|dump) | |
610 | # complete on namespace | |
611 | opts=`rosparam list 2> /dev/null` | |
612 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
613 | ;; | |
614 | esac | |
615 | fi | |
616 | ||
617 | } | |
618 | function _roscomplete_rostopic { | |
619 | local arg opts | |
620 | COMPREPLY=() | |
621 | arg="${COMP_WORDS[COMP_CWORD]}" | |
622 | ||
623 | if [[ $COMP_CWORD == 1 ]]; then | |
624 | opts="bw echo hz list pub type find info" | |
625 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
626 | elif [[ $COMP_CWORD -ge 2 ]]; then | |
627 | if [[ ${arg} =~ \-\-.* ]]; then | |
628 | case ${COMP_WORDS[1]} in | |
629 | pub) | |
630 | COMPREPLY=($(compgen -W "--rate --once --file --latch" -- ${arg})) | |
631 | ;; | |
632 | bw) | |
633 | COMPREPLY=($(compgen -W "--window" -- ${arg})) | |
634 | ;; | |
635 | echo) | |
636 | COMPREPLY=($(compgen -W "--bag --filter --nostr --noarr --clear --all offset" -- ${arg})) | |
637 | ;; | |
638 | hz) | |
639 | COMPREPLY=($(compgen -W "--window --filter" -- ${arg})) | |
640 | ;; | |
641 | list) | |
642 | COMPREPLY=($(compgen -W "--bag --verbose --host" -- ${arg})) | |
643 | ;; | |
644 | esac | |
645 | else | |
646 | case ${COMP_WORDS[1]} in | |
647 | bw|echo|hz|list|type|info) | |
648 | if [[ ${COMP_WORDS[$(( $COMP_CWORD - 1 ))]} == "-b" ]]; then | |
649 | COMPREPLY=($(compgen -f -- ${arg})) | |
650 | else | |
651 | opts=`rostopic list 2> /dev/null` | |
652 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
653 | fi | |
654 | ;; | |
655 | find) | |
656 | opts=`_msg_opts ${COMP_WORDS[$COMP_CWORD]}` | |
657 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
658 | ;; | |
659 | pub) | |
660 | if [[ $COMP_CWORD == 2 ]]; then | |
661 | opts=`rostopic list 2> /dev/null` | |
662 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
663 | elif [[ $COMP_CWORD == 3 ]]; then | |
664 | opts=`_msg_opts ${COMP_WORDS[$COMP_CWORD]}` | |
665 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
666 | elif [[ $COMP_CWORD == 4 ]]; then | |
667 | opts=`rosmsg-proto msg 2> /dev/null -s ${COMP_WORDS[3]}` | |
668 | if [ 0 -eq $? ]; then | |
669 | COMPREPLY="$opts" | |
670 | fi | |
671 | fi | |
672 | ;; | |
673 | esac | |
674 | fi | |
675 | fi | |
676 | } | |
677 | ||
678 | function _roscomplete_rosservice { | |
679 | local arg opts | |
680 | COMPREPLY=() | |
681 | arg="${COMP_WORDS[COMP_CWORD]}" | |
682 | ||
683 | if [[ $COMP_CWORD == 1 ]]; then | |
684 | opts="list call type find uri" | |
685 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
686 | elif [[ $COMP_CWORD == 2 ]]; then | |
687 | case ${COMP_WORDS[1]} in | |
688 | uri|list|type|call) | |
689 | opts=`rosservice list 2> /dev/null` | |
690 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
691 | ;; | |
692 | find) | |
693 | # Need a clever way to do message searching | |
694 | opts=`_srv_opts ${COMP_WORDS[$COMP_CWORD]}` | |
695 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
696 | ;; | |
697 | esac | |
698 | elif [[ $COMP_CWORD == 3 ]]; then | |
699 | case ${COMP_WORDS[1]} in | |
700 | call) | |
701 | type=`rosservice type ${COMP_WORDS[2]}` | |
702 | opts=`rosmsg-proto srv 2> /dev/null -s ${type}` | |
703 | if [ 0 -eq $? ]; then | |
704 | COMPREPLY="$opts" | |
705 | fi | |
706 | ;; | |
707 | esac | |
708 | fi | |
709 | ||
710 | } | |
711 | ||
712 | function _msg_opts { | |
713 | local arg pkgs pkgname msgname searchmsg path count opts | |
714 | ||
715 | if [[ $1 =~ .+/.* ]]; then | |
716 | pkgname=${1%%/*} | |
717 | msgname=${1#*/} | |
718 | searchmsg=1 | |
719 | else | |
720 | pkgname=${1} | |
721 | fi | |
722 | ||
723 | if [[ -z ${searchmsg} ]]; then | |
724 | pkgs=(`rospack list | grep "^${pkgname}"`) | |
725 | count=0 | |
726 | ||
727 | opts="" | |
728 | ||
729 | for (( i = 0 ; i < ${#pkgs[@]} ; i=i+2 )); do | |
730 | if [[ -d ${pkgs[i+1]}/msg ]]; then | |
731 | opts="$opts ${pkgs[i]}/" | |
732 | pkgname=${pkgs[i]} | |
733 | count=$((count+1)) | |
734 | fi | |
735 | done | |
736 | ||
737 | if [[ $count -gt 1 ]]; then | |
738 | echo $opts | |
739 | return 0 | |
740 | fi | |
741 | fi | |
742 | ||
743 | path=`rospack find ${pkgname} 2> /dev/null` | |
744 | ||
745 | if [ $? -eq 0 ] && [ -d ${path}/msg ]; then | |
746 | echo `find -L ${path}/msg -maxdepth 1 -mindepth 1 -name *.msg ! -regex ".*/[.].*" -print0 | tr '\000' '\n' | sed -e "s/.*\/\(.*\)\.msg/${pkgname}\/\1/g"` | |
747 | fi | |
748 | } | |
749 | ||
750 | function _roscomplete_rosmsg { | |
751 | local arg opts | |
752 | COMPREPLY=() | |
753 | arg="${COMP_WORDS[COMP_CWORD]}" | |
754 | ||
755 | if [[ $COMP_CWORD == 1 ]]; then | |
756 | opts="show list md5 package packages" | |
757 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
758 | elif [[ $COMP_CWORD == 2 ]]; then | |
759 | case ${COMP_WORDS[1]} in | |
760 | show|users|md5) | |
761 | opts=`_msg_opts ${COMP_WORDS[$COMP_CWORD]}` | |
762 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
763 | ;; | |
764 | package) | |
765 | opts=`rospack list-names` | |
766 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
767 | ;; | |
768 | packages|list) | |
769 | # This shouldn't really have a completion rule | |
770 | ;; | |
771 | esac | |
772 | fi | |
773 | ||
774 | } | |
775 | ||
776 | function _roscomplete_roscreate_pkg { | |
777 | local arg opts | |
778 | COMPREPLY=() | |
779 | arg="${COMP_WORDS[COMP_CWORD]}" | |
780 | ||
781 | if [[ $COMP_CWORD != 1 ]]; then | |
782 | opts=`rospack list-names` | |
783 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
784 | fi | |
785 | } | |
786 | ||
787 | function _roscomplete_roswtf { | |
788 | local arg | |
789 | COMPREPLY=() | |
790 | arg="${COMP_WORDS[COMP_CWORD]}" | |
791 | if [[ ${arg} =~ \-\-.* ]]; then | |
792 | COMPREPLY=($(compgen -W "--all --no-plugins --offline" -- ${arg})) | |
793 | else | |
794 | if [[ $COMP_CWORD == 1 ]]; then | |
795 | COMPREPLY=($(compgen -o plusdirs -f -X "!*.launch" -- ${arg})) | |
796 | fi | |
797 | fi | |
798 | } | |
799 | ||
800 | function _roscomplete_rosclean { | |
801 | local arg opts | |
802 | COMPREPLY=() | |
803 | arg="${COMP_WORDS[COMP_CWORD]}" | |
804 | if [[ $COMP_CWORD == 1 ]]; then | |
805 | opts="check purge" | |
806 | COMPREPLY=($(compgen -W "$opts" -- ${arg})) | |
807 | fi | |
808 | } | |
809 | ||
810 | complete -F "_roscomplete_sub_dir" -o "nospace" "roscd" | |
811 | complete -F "_roscomplete_sub_dir" -o "nospace" "rospd" | |
812 | complete -F "_roscomplete_sub_dir" -o "nospace" "rosls" | |
813 | complete -F "_roscomplete_rosmake" "rosmake" | |
814 | complete -F "_roscomplete_rosclean" "rosclean" | |
815 | complete -F "_roscomplete_exe" "rosrun" | |
816 | complete -F "_roscomplete_file" "rosed" | |
817 | complete -F "_roscomplete_file" "roscp" | |
818 | complete -F "_roscomplete_launch" -o filenames "roslaunch" | |
819 | complete -F "_roscomplete_test" -o filenames "rostest" | |
820 | complete -F "_roscomplete_rospack" "rospack" | |
821 | complete -F "_roscomplete_rosbag" -o default "rosbag" | |
822 | complete -F "_roscomplete_rosnode" "rosnode" | |
823 | complete -F "_roscomplete_rosparam" "rosparam" | |
824 | complete -F "_roscomplete_rostopic" "rostopic" | |
825 | complete -F "_roscomplete_rosservice" "rosservice" | |
826 | complete -F "_roscomplete_rosmsg" "rosmsg" | |
827 | complete -F "_roscomplete_rossrv" "rossrv" | |
828 | complete -F "_roscomplete_roscreate_pkg" "roscreate-pkg" | |
829 | complete -F "_roscomplete_roswtf" -o filenames "roswtf" |
0 | alias roscd 'cd `rospack find \!*; if ($status) rosstack find \!*;`' | |
1 | alias rcd roscd | |
2 | alias rosls 'ls `rospack find \!*`' | |
3 | #alias rosd 'echo "unimplemented"' | |
4 | #alias rospd 'echo "unimplemented"' | |
5 | #alias rosed 'echo "unimplemented"' | |
6 | #alias roscmd 'echo "unimplemented"' | |
7 | #alias roscp 'echo "unimplemented"' | |
8 | ||
9 | complete roscd 'p/1/`rospack list-names && rosstack list-names`/' | |
10 | complete rosmake 'p/*/`rospack list-names && rosstack list-names`/' | |
11 | complete rosupdate 'p/1/`rospack list-names && rosstack list-names`/' | |
12 | complete rosdeb 'p/*/`rospack list-names && rosstack list-names`/' | |
13 | ||
14 | complete rosdep \ | |
15 | 'p/1/(help generate_bash install depdb what_needs check satisfy)/' \ | |
16 | 'n/{generate_bash,install,depdb,check,satisfy}/`rospack list-names`/' | |
17 | ||
18 | complete rospack \ | |
19 | 'p/1/(help find list list-names langs deps deps-manifests deps1 deps-indent rosdeps rosdeps0 vcs vcs0 depends-on)/' \ | |
20 | 'n/{find,deps,deps-manifests,deps1,deps-indent,rosdeps,rosdeps0,vcs,vcs0,depends-on,depends-on1,export,cflags-only-I,cflags-only-other,libs-only-L,libs-only-l,libs-only-other}/`rospack list-names`/' | |
21 | ||
22 | complete rosstack \ | |
23 | 'p/1/(help find list list-names langs deps deps-manifests deps1 deps-indent rosdeps rosdeps0 vcs vcs0 depends-on)/' \ | |
24 | 'n/{find,deps,deps-manifests,deps1,deps-indent,rosdeps,rosdeps0,vcs,vcs0,depends-on,depends-on1,export,cflags-only-I,cflags-only-other,libs-only-L,libs-only-l,libs-only-other}/`rosstack list-names`/' | |
25 | ||
26 | complete rostopic \ | |
27 | 'p/1/(hz echo type list)/' \ | |
28 | 'n/{echo,hz,type}/`rostopic list | sort -u`/' | |
29 | ||
30 | complete rosmsg \ | |
31 | 'p/1/(show users package packages)/' \ | |
32 | 'n@{show,users}@`rostopic list -v | grep " \* " | cut -f 4 -d " " | grep -v unknown | sed -e "s/[\d93\d91]//g"`@' \ | |
33 | 'n/{package,packages}/`rospack list-names`/' | |
34 | ||
35 | complete rossrv \ | |
36 | 'p/1/(show users package packages)/' \ | |
37 | 'n@{show,users}@`rostopic list -v | grep " \* " | cut -f 4 -d " +" | grep -v unknown | sed -e "s/[\d93\d91]//g"`@' \ | |
38 | 'n/{package,packages}/`rospack list-names`/' | |
39 | ||
40 | complete rosrun \ | |
41 | 'p/1/`rospack list-names`/' \ | |
42 | 'p@2@`rospack find $:1 | xargs -I 1 find 1 -perm /a+x -type f -printf "%f\n"`@' | |
43 | ||
44 | complete roslaunch \ | |
45 | 'p/1/`rospack list-names && find . -maxdepth 3 -regex ".*[xl][am][ul]n*c*h*" -printf "%P\n" `/' \ | |
46 | 'p@2@`rospack find $:1 | xargs -I 1 find 1 -maxdepth 3 -regex ".*[xl][am][ul]n*c*h*" -type f -printf "%f\n"`@' | |
47 |
0 | # roszsh - functions to support ROS users | |
1 | # Useful things to know: | |
2 | # 'local' variables get unset after function, all others stay forever | |
3 | # 'reply' is the var used by zsh compctl builtin | |
4 | ||
5 | function _rossed { | |
6 | if [[ `uname` == Darwin || `uname` == FreeBSD ]]; then | |
7 | sed -E "$@" | |
8 | else | |
9 | sed -r "$@" | |
10 | fi | |
11 | } | |
12 | ||
13 | function _rosfind { | |
14 | if [[ `uname` == Darwin || `uname` == FreeBSD ]]; then | |
15 | # BSD find needs -E for extended regexp | |
16 | find -E "$@" | |
17 | else | |
18 | find "$@" | |
19 | fi | |
20 | } | |
21 | ||
22 | # _ros_location_find | |
23 | # based on $ROS_LOCATIONS, which the user can set to any colon | |
24 | # separated of key=folder pairs also resolves keys 'log' and | |
25 | # 'test_results' to ROS defaults finally resolves to package, then | |
26 | # stack echoes its result | |
27 | function _ros_location_find { | |
28 | local homedir ROS_LOCATION_KEYS_ARR ROS_LOCATIONS_ARR loc | |
29 | homedir=`echo $HOME | sed -e "s/\//\t\//g" -e "s/\t/\\\\\/g"` | |
30 | ROS_LOCATION_KEYS_ARR=(`echo $ROS_LOCATIONS | _rossed -e 's/([^:=]*)=([^:=]*)(:*[^=])*(:|$)/\1 /g'`) | |
31 | ROS_LOCATIONS_ARR=(`echo $ROS_LOCATIONS | _rossed -e 's/([^:=]*)=([^:=]*)(:*[^=])*(:|$)/\2 /g' -e "s/~/${homedir}/g"`) | |
32 | ||
33 | for (( i = 1 ; i <= ${#ROS_LOCATION_KEYS_ARR[@]} ; i++ )); do | |
34 | if [[ $1 == ${ROS_LOCATION_KEYS_ARR[$i]} ]]; then | |
35 | echo ${ROS_LOCATIONS_ARR[i]} | |
36 | return 0 | |
37 | fi | |
38 | done | |
39 | ||
40 | if [[ $1 == log ]]; then | |
41 | echo `roslaunch-logs` | |
42 | return 0 | |
43 | elif [[ $1 == test_results ]]; then | |
44 | echo `rosrun rosunit test_results_dir.py` | |
45 | return 0 | |
46 | fi | |
47 | ||
48 | loc=`export ROS_CACHE_TIMEOUT=-1.0 && rospack find $1 2> /dev/null` | |
49 | if [[ $? != 0 ]]; then | |
50 | loc=`export ROS_CACHE_TIMEOUT=-1.0 && rosstack find $1 2> /dev/null` | |
51 | if [[ $? != 0 ]]; then | |
52 | return 1 | |
53 | fi | |
54 | echo $loc | |
55 | return 0 | |
56 | fi | |
57 | echo $loc | |
58 | return 0 | |
59 | } | |
60 | ||
61 | function _ros_list_locations { | |
62 | local ROS_LOCATION_KEYS packages stacks | |
63 | ROS_LOCATION_KEYS=`echo $ROS_LOCATIONS | _rossed -e 's/([^:=]*)=([^:=]*)(:*[^=])*(:|$)/\1 /g'` | |
64 | packages=`export ROS_CACHE_TIMEOUT=-1.0 && rospack list-names` | |
65 | stacks=`export ROS_CACHE_TIMEOUT=-1.0 && rosstack list-names` | |
66 | echo $packages $stacks log test_results $ROS_LOCATION_KEYS | tr ' ' '\n' | |
67 | return 0 | |
68 | } | |
69 | ||
70 | function _ros_decode_path { | |
71 | local rosname rosdir reldir last rospackdir rosstack_result rosstackdir | |
72 | rosvals=() | |
73 | if [[ -z $1 ]]; then | |
74 | return 0 | |
75 | fi | |
76 | ||
77 | echo $1 | grep -G '.\+/.*' > /dev/null | |
78 | if [[ $? == 0 ]]; then | |
79 | rosname=${1%%/*} | |
80 | reldir=/${1#*/} | |
81 | last=${reldir##*/} | |
82 | reldir=${reldir%/*}/ | |
83 | else | |
84 | rosname=$1 | |
85 | if [[ -z $2 || $2 != "forceeval" ]]; then | |
86 | rosvals=(${rosname}) | |
87 | return 1 | |
88 | fi | |
89 | fi | |
90 | ||
91 | if [[ $rosname == ros ]]; then | |
92 | rosdir=`rosstack find ros` | |
93 | elif [[ $rosname == pkg ]]; then | |
94 | rosdir=${ROS_PACKAGE_PATH%%:*} | |
95 | elif [[ $rosname == log ]]; then | |
96 | rosdir=`roslaunch-logs` | |
97 | elif [[ $rosname == test_results ]]; then | |
98 | rosdir=`rosrun rosunit test_results_dir.py` | |
99 | else | |
100 | rospackdir=`export ROS_CACHE_TIMEOUT=-1.0 && rospack find $rosname 2> /dev/null` | |
101 | rospack_result=$? | |
102 | rosstackdir=`export ROS_CACHE_TIMEOUT=-1.0 && rosstack find $rosname 2> /dev/null` | |
103 | rosstack_result=$? | |
104 | if [[ $rospack_result == 0 ]]; then | |
105 | rosdir=$rospackdir | |
106 | elif [[ $rosstack_result == 0 ]]; then | |
107 | rosdir=$rosstackdir | |
108 | else | |
109 | rosvals=(${rosname}) | |
110 | return 1 | |
111 | fi | |
112 | fi | |
113 | ||
114 | rosvals=(${rosname} ${rosdir} ${reldir} ${last}) | |
115 | } | |
116 | ||
117 | function rospython { | |
118 | if [[ $1 = "--help" ]]; then | |
119 | echo -e "usage: rospython [package] \n\nRun python loading package manifest first." | |
120 | return 0 | |
121 | fi | |
122 | if [[ -z $1 ]]; then | |
123 | if [[ -f ./manifest.xml ]]; then | |
124 | pkgname=`basename \`pwd\`` | |
125 | python -i -c "import roslib; roslib.load_manifest('$pkgname')" | |
126 | else | |
127 | python | |
128 | fi | |
129 | else | |
130 | python -i -c "import roslib; roslib.load_manifest('$1')" | |
131 | fi | |
132 | } | |
133 | ||
134 | function roscd { | |
135 | local rosvals | |
136 | if [[ $1 = "--help" ]] | [[ $# -gt 1 ]]; then | |
137 | echo -e "usage: roscd package\n\nJump to target package." | |
138 | return 0 | |
139 | fi | |
140 | if [ -z $1 ]; then | |
141 | if [ -z $ROS_WORKSPACE ]; then | |
142 | echo -e "No ROS_WORKSPACE set. Please set ROS_WORKSPACE to use roscd with no arguments." | |
143 | return 1 | |
144 | fi | |
145 | cd ${ROS_WORKSPACE} | |
146 | return 0 | |
147 | fi | |
148 | ||
149 | _ros_decode_path $1 forceeval | |
150 | if [ $? != 0 ]; then | |
151 | echo "roscd: No such package '$1'" | |
152 | return 1 | |
153 | elif [ -z ${rosvals[1]} ]; then | |
154 | if [ -z $ROS_WORKSPACE ]; then | |
155 | echo -e "No ROS_WORKSPACE set. Please set ROS_WORKSPACE to use roscd with no arguments." | |
156 | return 1 | |
157 | fi | |
158 | cd ${ROS_WORKSPACE} | |
159 | return 0 | |
160 | else | |
161 | cd ${rosvals[2]}${rosvals[3]}${rosvals[4]} | |
162 | return 0 | |
163 | fi | |
164 | } | |
165 | ||
166 | function _is_integer { | |
167 | [ "$1" -eq "$1" ] > /dev/null 2>&1 | |
168 | return $? | |
169 | } | |
170 | ||
171 | function rosd { | |
172 | if [[ $1 = "--help" ]]; then | |
173 | echo -e "usage: rosd\n\nDisplays the list of currently remembered directories with indexes." | |
174 | return 0 | |
175 | fi | |
176 | let count=0; | |
177 | for items in `dirs`; | |
178 | do | |
179 | echo $count $items; | |
180 | let count=$((count+1)); | |
181 | done | |
182 | } | |
183 | ||
184 | function rospd { | |
185 | if [[ $1 = "--help" ]]; then | |
186 | echo -e "usage: rospd\n\nLike pushd, also accepts indexes from rosd." | |
187 | return 0 | |
188 | fi | |
189 | if _is_integer $1; then | |
190 | pushd +$1 > /dev/null ; | |
191 | else | |
192 | local rosvals | |
193 | _ros_decode_path $1 forceeval | |
194 | pushd ${rosvals[2]}${rosvals[3]}${rosvals[4]} > /dev/null ; | |
195 | fi | |
196 | rosd | |
197 | } | |
198 | ||
199 | function rosls { | |
200 | local rosvals | |
201 | if [[ $1 = "--help" ]]; then | |
202 | echo -e "usage: rosls [package]\n\nLists contents of a package directory." | |
203 | return 0 | |
204 | fi | |
205 | _ros_decode_path $1 forceeval | |
206 | ls ${rosvals[2]}${rosvals[3]}${rosvals[4]} $2 | |
207 | } | |
208 | ||
209 | # sets arg as return value | |
210 | function _roscmd { | |
211 | local pkgdir exepath opt | |
212 | pkgdir=`export ROS_CACHE_TIMEOUT=-1.0 && rospack find $1 2> /dev/null` | |
213 | if [[ $? != 0 ]] ; then | |
214 | echo "Couldn't find package [$1]" | |
215 | return 1 | |
216 | fi | |
217 | exepath=(`find $pkgdir -name $2 -type f`) | |
218 | if [[ ${#exepath[@]} == 0 ]] ; then | |
219 | echo "That file does not exist in that package." | |
220 | return 1 | |
221 | elif [[ ${#exepath[@]} -gt 1 ]] ; then | |
222 | echo "You have chosen a non-unique filename, please pick one of the following:" | |
223 | select opt in ${exepath[@]}; do | |
224 | echo $opt | |
225 | break | |
226 | done | |
227 | else | |
228 | opt=${exepath[1]} | |
229 | fi | |
230 | arg=${opt} | |
231 | } | |
232 | ||
233 | function rosed { | |
234 | local arg | |
235 | if [[ $1 = "--help" ]]; then | |
236 | echo -e "usage: rosed [package] [file]\n\nEdit a file within a package." | |
237 | return 0 | |
238 | fi | |
239 | _roscmd ${1} ${2} | |
240 | if [[ -z $EDITOR ]]; then | |
241 | vim ${arg} | |
242 | else | |
243 | $EDITOR ${arg} | |
244 | fi | |
245 | } | |
246 | ||
247 | function roscp { | |
248 | local arg | |
249 | if [[ $1 = "--help" ]] | [[ $# -ne 3 ]]; then | |
250 | echo -e "usage: roscp package filename target\n\nCopy a file from a package to target location." | |
251 | return 0 | |
252 | fi | |
253 | _roscmd ${1} ${2} | |
254 | cp ${arg} ${3} | |
255 | } | |
256 | ||
257 | function _roscomplete { | |
258 | local arg opts stack_opts | |
259 | reply=() | |
260 | opts=`export ROS_CACHE_TIMEOUT=-1.0 && rospack list-names` | |
261 | stack_opts=`export ROS_CACHE_TIMEOUT=-1.0 && rosstack list-names` | |
262 | IFS=$'\n' | |
263 | reply=(${=opts} ${=stack_opts}) | |
264 | unset IFS | |
265 | } | |
266 | ||
267 | function _roscomplete_rosmake { | |
268 | local param | |
269 | _roscomplete | |
270 | if [[ $PREFIX == "--"* ]]; then | |
271 | param="--test-only --all --mark-installed --unmark-installed --robust --build-everything --specified-only --buildtest --buildtest1 --output --pre-clean --bootstrap --disable-logging --target --pjobs --threads --profile --skip-blacklist --status-rate" | |
272 | reply=(${=reply} ${=param}) | |
273 | fi | |
274 | } | |
275 | ||
276 | function _roscomplete_sub_dir { | |
277 | local arg opts rosvals sedcmd stack_opts | |
278 | reply=() | |
279 | arg="$1" | |
280 | _ros_decode_path ${arg} | |
281 | if [[ -z ${rosvals[3]} ]]; then | |
282 | opts=`export ROS_CACHE_TIMEOUT=-1.0 && rospack list-names` | |
283 | stack_opts=`export ROS_CACHE_TIMEOUT=-1.0 && rosstack list-names` | |
284 | IFS=$'\n' | |
285 | reply=(${=opts} ${=stack_opts}) | |
286 | unset IFS | |
287 | else | |
288 | if [ -e ${rosvals[2]}${rosvals[3]} ]; then | |
289 | sedcmd="s:^${rosvals[2]}:${rosvals[1]}:"g | |
290 | #echo | |
291 | #echo $sedcmd | |
292 | #echo | |
293 | opts=`find ${rosvals[2]}${rosvals[3]} -maxdepth 1 -mindepth 1 -type d ! -regex ".*/[.].*" -print0 | tr '\000' '\n' | sed -e "$sedcmd"` | |
294 | #echo $opts | |
295 | else | |
296 | opts='' | |
297 | fi | |
298 | IFS=$'\n' | |
299 | reply=(${=opts}) | |
300 | unset IFS | |
301 | fi | |
302 | } | |
303 | ||
304 | function _roscomplete_search_dir { | |
305 | local words arg opts pkgdir pkgdir_result stack_result | |
306 | reply=() | |
307 | words=(${=BUFFER}) | |
308 | pkgdir=`export ROS_CACHE_TIMEOUT=-1.0 && rospack find ${words[2]} 2> /dev/null` | |
309 | pkgdir_result=$? | |
310 | stackdir=`export ROS_CACHE_TIMEOUT=-1.0 && rosstack find ${words[2]} 2> /dev/null` | |
311 | stack_result=$? | |
312 | if [[ $pkgdir_result == 0 ]]; then | |
313 | opts=`find $pkgdir ${=1} -print0 | tr '\000' '\n' | sed -e "s/.*\/\(.*\)/\1/g"` | |
314 | elif [[ $stack_result == 0 ]] ; then | |
315 | opts=`find $stackdir ${=1} -print0 | tr '\000' '\n' | sed -e "s/.*\/\(.*\)/\1/g"` | |
316 | else | |
317 | opts="" | |
318 | fi | |
319 | IFS=$'\n' | |
320 | reply=(${=opts}) | |
321 | unset IFS | |
322 | } | |
323 | ||
324 | function _roscomplete_exe { | |
325 | _roscomplete_search_dir "-type f -perm +111 -regex .*/.*$" | |
326 | } | |
327 | ||
328 | function _roscomplete_file { | |
329 | _roscomplete_search_dir "-type f ! -regex .*/[.].* ! -regex .*[.][oa]$" | |
330 | } | |
331 | ||
332 | function _roscomplete_launchfile { | |
333 | _roscomplete_search_dir "( -type f -regex .*\.launch$ -o -type f -regex .*\.test$ )" | |
334 | } | |
335 | ||
336 | function _roscomplete_rosbag { | |
337 | local opts | |
338 | reply=() | |
339 | ||
340 | if [[ ${CURRENT} == 2 ]]; then | |
341 | opts="check compress decompress filter fix help info play record reindex" | |
342 | reply=(${=opts}) | |
343 | else | |
344 | if [[ ${=${(s: :)words}[$(( ${CURRENT} ))]} =~ \-\- ]]; then | |
345 | opts="--all --help" | |
346 | case ${=${(s: :)words}[2]} in | |
347 | check) | |
348 | opts="--genrules --append --noplugins --help" | |
349 | ;; | |
350 | compress|decompress) | |
351 | opts="--output-dir --force --quiet --help" | |
352 | ;; | |
353 | filter) | |
354 | opts="--print --help" | |
355 | ;; | |
356 | fix) | |
357 | opts="--force --noplugins --help" | |
358 | ;; | |
359 | info) | |
360 | opts="--yaml --key --freq --help" | |
361 | ;; | |
362 | play) | |
363 | opts="--help --quiet --immediate --pause --queue --clock --hz --delay --rate --start --skip-empty --loop --keep-alive --try-future-version --topics --bags" | |
364 | ;; | |
365 | record) | |
366 | opts="--help --all --regex --exclude --quiet --output-prefix --output-name --split --size --duration --buffsize --limit --node --bz2" | |
367 | ;; | |
368 | reindex) | |
369 | opts="--help --force --quiet --output-dir" | |
370 | ;; | |
371 | esac | |
372 | reply=(${=opts}) | |
373 | fi | |
374 | fi | |
375 | ||
376 | } | |
377 | ||
378 | function _roscomplete_rospack { | |
379 | local opts | |
380 | reply=() | |
381 | if [[ ${CURRENT} == 2 ]]; then | |
382 | opts="help find list list-names list-duplicates langs depends depends-manifests depends1 depends-indent depends-msgsrv depends-why rosdep rosdep0 vcs vcs0 depends-on depends-on1 export plugins cflags-only-I cflags-only-other libs-only-L libs-only-l libs-only-other profile" | |
383 | reply=(${=opts}) | |
384 | else | |
385 | opts=`rospack list-names` | |
386 | reply=(${=opts}) | |
387 | fi | |
388 | } | |
389 | ||
390 | function _roscomplete_rosnode { | |
391 | local opts | |
392 | reply=() | |
393 | ||
394 | if [[ ${CURRENT} == 2 ]]; then | |
395 | opts="ping list info machine kill" | |
396 | reply=(${=opts}) | |
397 | elif [[ ${CURRENT} == 3 ]]; then | |
398 | case ${=${(s: :)words}[2]} in | |
399 | info) | |
400 | opts=`rosnode list 2> /dev/null` | |
401 | reply=(${=opts}) | |
402 | ;; | |
403 | ping|list|kill) | |
404 | if [[ ${=${(s: :)words}[$(( ${CURRENT} ))]} =~ \-\- ]]; then | |
405 | opts="--all --help" | |
406 | else | |
407 | opts=`rosnode list 2> /dev/null` | |
408 | fi | |
409 | reply=(${=opts}) | |
410 | ;; | |
411 | machine) | |
412 | # This takes more logic to determine which machines are present. | |
413 | ;; | |
414 | esac | |
415 | else | |
416 | case ${=${(s: :)words}[2]} in | |
417 | kill) | |
418 | # complete on node name | |
419 | opts=`rosnode list 2> /dev/null` | |
420 | reply=(${=opts}) | |
421 | ;; | |
422 | esac | |
423 | fi | |
424 | } | |
425 | ||
426 | function _roscomplete_rosparam { | |
427 | local opts | |
428 | reply=() | |
429 | ||
430 | if [[ ${CURRENT} == 2 ]]; then | |
431 | opts="set get load dump delete list" | |
432 | reply=(${=opts}) | |
433 | elif [[ ${CURRENT} == 3 ]]; then | |
434 | case ${=${(s: :)words}[2]} in | |
435 | set|get|delete|list) | |
436 | opts=`rosparam list 2> /dev/null` | |
437 | reply=(${=opts}) | |
438 | ;; | |
439 | load|dump) | |
440 | # complete on files | |
441 | reply=(${=opts}) | |
442 | ;; | |
443 | esac | |
444 | elif [[ ${CURRENT} == 4 ]]; then | |
445 | case ${=${(s: :)words}[2]} in | |
446 | load|dump) | |
447 | # complete on namespace | |
448 | opts=`rosparam list 2> /dev/null` | |
449 | reply=(${=opts}) | |
450 | ;; | |
451 | esac | |
452 | fi | |
453 | ||
454 | } | |
455 | function _roscomplete_rostopic { | |
456 | local opts | |
457 | reply=() | |
458 | ||
459 | if [[ ${CURRENT} == 2 ]]; then | |
460 | opts="bw echo hz list pub type find info" | |
461 | reply=(${=opts}) | |
462 | elif [[ ${CURRENT} > 2 ]]; then | |
463 | if [[ ${=${(s: :)words}[$(( ${CURRENT} ))]} =~ \-\- ]]; then | |
464 | case ${=${(s: :)words}[2]} in | |
465 | pub) | |
466 | opts="--help --rate --once --file --latch" | |
467 | ;; | |
468 | bw) | |
469 | opts="--help --window" | |
470 | ;; | |
471 | echo) | |
472 | opts="--help --bag --filter --nostr --noarr --clear --all --offset" | |
473 | ;; | |
474 | hz) | |
475 | opts="--help --window --filter" | |
476 | ;; | |
477 | list) | |
478 | opts="--help --bag --verbose --host" | |
479 | ;; | |
480 | type|info) | |
481 | opts="--help" | |
482 | ;; | |
483 | esac | |
484 | reply=(${=opts}) | |
485 | else | |
486 | case ${=${(s: :)words}[2]} in | |
487 | bw|echo|hz|list|type|info) | |
488 | if [[ ${=${(s: :)words}[$(( ${CURRENT} -1 ))]} == "-b" ]]; then | |
489 | opts=`find . -maxdepth 1 -type f -not -name ".*" -not -name "*[~#]" | sed 's!.*/!!'` | |
490 | reply=(${=opts}) | |
491 | else | |
492 | opts=`rostopic list 2> /dev/null` | |
493 | reply=(${=opts}) | |
494 | fi | |
495 | ;; | |
496 | find) | |
497 | opts=`_msg_opts ${=${(s: :)words[-1]}}` | |
498 | reply=(${=opts}) | |
499 | ;; | |
500 | pub) | |
501 | if [[ ${CURRENT} == 3 ]]; then | |
502 | opts=`rostopic list 2> /dev/null` | |
503 | reply=(${=opts}) | |
504 | elif [[ ${CURRENT} == 4 ]]; then | |
505 | if [[ ${=${(s: :)words}[$(( ${CURRENT} ))]} =~ / ]]; then | |
506 | opts=`_msg_opts ${=${(s: :)words[-1]}} | sed -s 's/.*\///'` | |
507 | else | |
508 | opts=`_msg_opts ${=${(s: :)words[-1]}}` | |
509 | fi | |
510 | reply=(${=opts}) | |
511 | elif [[ ${CURRENT} == 5 ]]; then | |
512 | opts=`rosmsg-proto msg 2> /dev/null -s ${=${(s: :)words[-1]}}` | |
513 | reply=(${=opts}) | |
514 | fi | |
515 | ;; | |
516 | esac | |
517 | fi | |
518 | fi | |
519 | } | |
520 | ||
521 | function _roscomplete_rosservice { | |
522 | local opts | |
523 | reply=() | |
524 | ||
525 | ||
526 | if [[ ${CURRENT} == 2 ]]; then | |
527 | opts="list call type find uri" | |
528 | reply=(${=opts}) | |
529 | elif [[ ${CURRENT} == 3 ]]; then | |
530 | case ${=${(s: :)words}[2]} in | |
531 | uri|list|type|call) | |
532 | opts=`rosservice list 2> /dev/null` | |
533 | IFS=$'\n' | |
534 | reply=(${=opts}) | |
535 | unset IFS | |
536 | ;; | |
537 | find) | |
538 | opts=`_srv_opts ${=${(s: :)words[-1]}}` | |
539 | reply=(${=opts}) | |
540 | ;; | |
541 | esac | |
542 | elif [[ ${CURRENT} == 4 ]]; then | |
543 | case ${=${(s: :)words}[2]} in | |
544 | call) | |
545 | type=`rosservice type ${=${(s: :)words[-1]}}` | |
546 | opts=`rosmsg-proto srv 2> /dev/null -s ${type}` | |
547 | reply=(${=opts}) | |
548 | ;; | |
549 | esac | |
550 | fi | |
551 | ||
552 | } | |
553 | ||
554 | function _msg_opts { | |
555 | local pkgname msgname searchmsg pkgs count pkgname2 opts result | |
556 | unset searchmsg | |
557 | ||
558 | if [[ $1 =~ .+/.* ]]; then | |
559 | pkgname=${1%%/*} | |
560 | msgname=${1#*/} | |
561 | searchmsg=1 | |
562 | else | |
563 | pkgname=${1} | |
564 | fi | |
565 | ||
566 | if [[ -z ${searchmsg} ]]; then | |
567 | pkgs=`command rospack list | grep "^${pkgname}"` | |
568 | count=0 | |
569 | ||
570 | opts="" | |
571 | ||
572 | for pkg in ${(f)pkgs}; do | |
573 | pkgdir=${=${(s: :)pkg}[2]} | |
574 | if [[ -d $pkgdir/msg ]]; then | |
575 | pkgname2=${=${(s: :)pkg}[1]} | |
576 | opts="$opts $pkgname2/" | |
577 | count=$((count+1)) | |
578 | fi | |
579 | done | |
580 | ||
581 | if [[ $count > 0 ]]; then | |
582 | echo $opts | |
583 | return 0 | |
584 | fi | |
585 | fi | |
586 | ||
587 | pkgpath=`rospack find ${pkgname} 2> /dev/null` | |
588 | if [[ $? == 0 ]] && [[ -d ${pkgpath}/msg ]]; then | |
589 | result=`find -L ${pkgpath}/msg -maxdepth 1 -mindepth 1 -name \*.msg ! -regex ".\*/[.].\*" -print0 | tr '\000' '\n' | sed -e "s/.*\/\(.*\)\.msg/${pkgname}\/\1/g"` | |
590 | echo $result | |
591 | fi | |
592 | } | |
593 | ||
594 | function _roscomplete_rosmsg { | |
595 | local opts | |
596 | reply=() | |
597 | ||
598 | ||
599 | if [[ ${CURRENT} == 2 ]]; then | |
600 | opts="show list md5 package packages" | |
601 | reply=(${=opts}) | |
602 | elif [[ ${CURRENT} == 3 ]]; then | |
603 | case ${=${(s: :)words}[2]} in | |
604 | show|users|md5) | |
605 | opts=`_msg_opts ${=${(s: :)words[-1]}}` | |
606 | reply=(${=opts}) | |
607 | ;; | |
608 | package) | |
609 | opts=`rospack list-names` | |
610 | reply=(${=opts}) | |
611 | ;; | |
612 | packages|list) | |
613 | # This shouldn't really have a completion rule | |
614 | ;; | |
615 | esac | |
616 | fi | |
617 | } | |
618 | ||
619 | function _srv_opts { | |
620 | local pkgname srvname searchsrv pkgs count opts pkgpath result | |
621 | unset searchsrv | |
622 | ||
623 | if [[ $1 =~ .+/.* ]]; then | |
624 | pkgname=${1%%/*} | |
625 | srvname=${1#*/} | |
626 | searchsrv=1 | |
627 | else | |
628 | pkgname=${1} | |
629 | fi | |
630 | ||
631 | if [[ -z ${searchsrv} ]]; then | |
632 | pkgs=`command rospack list | grep "^${pkgname}"` | |
633 | count=0 | |
634 | ||
635 | opts="" | |
636 | ||
637 | for pkg in ${(f)pkgs}; do | |
638 | pkgdir=${=${(s: :)pkg}[2]} | |
639 | if [[ -d $pkgdir/srv ]]; then | |
640 | pkgname2=${=${(s: :)pkg}[1]} | |
641 | opts="$opts $pkgname2/" | |
642 | count=$((count+1)) | |
643 | fi | |
644 | done | |
645 | ||
646 | if [[ $count > 0 ]]; then | |
647 | echo $opts | |
648 | return 0 | |
649 | fi | |
650 | fi | |
651 | ||
652 | pkgpath=`rospack find ${pkgname} 2> /dev/null` | |
653 | if [[ $? == 0 ]] && [[ -d ${pkgpath}/srv ]]; then | |
654 | result=`find -L ${pkgpath}/srv -maxdepth 1 -mindepth 1 -name \*.srv ! -regex ".\*/[.].\*" -print0 | tr '\000' '\n' | sed -e "s/.*\/\(.*\)\.srv/${pkgname}\/\1/g"` | |
655 | echo $result | |
656 | fi | |
657 | } | |
658 | ||
659 | function _roscomplete_rossrv { | |
660 | local opts | |
661 | reply=() | |
662 | ||
663 | if [[ ${CURRENT} == 2 ]]; then | |
664 | opts="show list md5 package packages" | |
665 | reply=(${=opts}) | |
666 | elif [[ ${CURRENT} == 3 ]]; then | |
667 | case ${=${(s: :)words}[2]} in | |
668 | show|users|md5) | |
669 | opts=`_srv_opts ${=${(s: :)words[-1]}}` | |
670 | reply=(${=opts}) | |
671 | ;; | |
672 | package) | |
673 | opts=`rospack list-names` | |
674 | reply=(${=opts}) | |
675 | ;; | |
676 | packages|list) | |
677 | # This shouldn't really have a completion rule | |
678 | ;; | |
679 | esac | |
680 | fi | |
681 | } | |
682 | ||
683 | function _roscomplete_roscreate_pkg { | |
684 | local opts | |
685 | reply=() | |
686 | ||
687 | if [[ ${CURRENT} > 2 ]]; then | |
688 | opts=`rospack list-names` | |
689 | reply=(${=opts}) | |
690 | fi | |
691 | } | |
692 | ||
693 | compctl -K "_roscomplete_sub_dir" -S / "roscd" "rospd" "rosls" | |
694 | compctl -K "_roscomplete_rosmake" "rosmake" | |
695 | ||
696 | compctl -x 'p[1]' -k "(check purge)" -- "rosclean" | |
697 | compctl -f -x 'p[1]' -K "_roscomplete" - 'p[2]' -K _roscomplete_file -- "rosed" "roscp" | |
698 | compctl -f -x 'p[1]' -K "_roscomplete" - 'p[2]' -K _roscomplete_exe -- "rosrun" | |
699 | compctl -/g '*.(launch|test)' -x 'p[1]' -K "_roscomplete" -tx - 'p[2]' -K _roscomplete_launchfile -- + -x 'S[--]' -k "(--files --args --nodes --find-node --child --local --screen --server_uri --run_id --wait --port --core --pid --dump-params)" -- "roslaunch" | |
700 | compctl -/g '*.(launch|test)' -x 'p[1]' -K "_roscomplete" -tx - 'p[2]' -K _roscomplete_launchfile -- + -x 'S[--]' -k "(--bare --bare-limit --bare-name --pkgdir --package)" -- "rostest" | |
701 | compctl -K "_roscomplete_rospack" "rospack" | |
702 | compctl -K "_roscomplete_rosbag" "rosbag" | |
703 | compctl -K "_roscomplete_rosnode" "rosnode" | |
704 | compctl -K "_roscomplete_rosparam" "rosparam" | |
705 | compctl -x 'p[0,2]' -K "_roscomplete_rostopic" - 'n[1,/] p[3]' -K "_roscomplete_rostopic" - 'p[3]' -S '' -K "_roscomplete_rostopic" - 'p[4]' -K "_roscomplete_rostopic" -- "rostopic" | |
706 | compctl -K "_roscomplete_rosservice" "rosservice" | |
707 | compctl -x 'p[1]' -k "(md5 package packages show users)" - 'p[2]' -S '' -K "_roscomplete_rosmsg" -- "rosmsg" | |
708 | compctl -x 'p[1]' -k "(md5 package packages show users)" - 'p[2]' -S '' -K "_roscomplete_rossrv" -- "rossrv" | |
709 | compctl -K "_roscomplete_roscreate_pkg" "roscreate-pkg" | |
710 | compctl -/g '*.(launch|test)' -x 'S[--]' -k "(--all --no-plugins --offline)" -- "roswtf" |
0 | #!/usr/bin/env bash | |
1 | if [[ $1 = "--help" ]]; then | |
2 | echo "Usage: rosrun PACKAGE EXECUTABLE [ARGS]" | |
3 | echo " rosrun will locate PACKAGE and try to find" | |
4 | echo " an executable named EXECUTABLE in the PACKAGE tree." | |
5 | echo " If it finds it, it will run it with ARGS." | |
6 | exit 0 | |
7 | fi | |
8 | if [ $# -lt 2 ]; then | |
9 | echo "Usage: rosrun PACKAGE EXECUTABLE [ARGS]" | |
10 | echo " rosrun will locate PACKAGE and try to find" | |
11 | echo " an executable named EXECUTABLE in the PACKAGE tree." | |
12 | echo " If it finds it, it will run it with ARGS." | |
13 | exit 1 | |
14 | fi | |
15 | # early check that filename does not end with '/' | |
16 | case $2 in | |
17 | */) echo "Invalid filename: " $2 | |
18 | exit 1 | |
19 | ;; | |
20 | esac | |
21 | # basename also makes .//foo into foo | |
22 | basename=`basename $2` | |
23 | if [[ -n $CMAKE_PREFIX_PATH ]]; then | |
24 | catkin_project_libexec_dir=`catkin_find --first-only --libexec $1 2> /dev/null` | |
25 | fi | |
26 | pkgdir=`rospack find $1` | |
27 | if [[ -z $catkin_project_libexec_dir && -z $pkgdir ]]; then | |
28 | exit 2 | |
29 | fi | |
30 | if [[ ! $2 == */* ]]; then | |
31 | # The -perm /mode usage is not available in find on the Mac | |
32 | #exepathlist=(`find $pkgdir -name $2 -type f -perm /u+x,g+x,o+x`) | |
33 | # -L: #3475 | |
34 | exepathlist=(`find -L $catkin_project_libexec_dir $pkgdir -name $2 -type f -perm +111 ! -regex ".*$pkgdir\/build\/.*" | uniq`) | |
35 | if [[ ${#exepathlist[@]} == 0 ]] ; then | |
36 | echo "[rosrun] Couldn't find executable named $2 below $pkgdir" | |
37 | nonexepathlist=(`find -H $pkgdir -name $2`) | |
38 | if [[ ${#nonexepathlist[@]} != 0 ]] ; then | |
39 | echo "[rosrun] Found the following, but they're either not files, " | |
40 | echo "[rosrun] or not executable:" | |
41 | for p in ${nonexepathlist[@]}; do | |
42 | echo "[rosrun] ${p}" | |
43 | done | |
44 | fi | |
45 | exit 3 | |
46 | ||
47 | elif [[ ${#exepathlist[@]} -gt 1 ]] ; then | |
48 | echo "[rosrun] You have chosen a non-unique executable, please pick one of the following:" | |
49 | select opt in ${exepathlist[@]}; do | |
50 | exepath=$opt | |
51 | break | |
52 | done | |
53 | else | |
54 | exepath=${exepathlist[0]} | |
55 | fi | |
56 | else | |
57 | absname=$pkgdir/$2 | |
58 | if [ ! -f $absname -o ! -x $absname ] ; then | |
59 | echo "[rosrun] Couldn't find executable named $absname" | |
60 | exit 3 | |
61 | fi | |
62 | exepath=$absname | |
63 | fi | |
64 | shift | |
65 | shift | |
66 | exec $exepath "$@" |
0 | #! /usr/bin/env bash | |
1 | ||
2 | # This script is supposed to be a unit test for rosbash, also used for manual checks against other operating systems. | |
3 | ||
4 | # TODO extend this script | |
5 | ||
6 | ||
7 | . ../rosbash | |
8 | ||
9 | echo Testing BASH | |
10 | ||
11 | . test_zshbash.sh | |
12 | ||
13 | # roslaunch completion | |
14 | ||
15 | export COMP_CWORD=1 | |
16 | _roscomplete_launch roslaunch | |
17 | if [[ ! ${COMPREPLY[@]} =~ rosbash ]]; then | |
18 | echo "rosbash package missing from" ${COMPREPLY[@]} ; exit 1 | |
19 | fi | |
20 | echo success roslaunch pkg | |
21 | ||
22 | export COMP_WORDS=("roslaunch" "--") | |
23 | export COMP_CWORD=1 | |
24 | _roscomplete_launch roslaunch "--" roslaunch | |
25 | if [[ ! ${COMPREPLY[@]} == "--files --args --nodes --find-node --child --local --screen --server_uri --run_id --wait --port --core --pid --dump-params" ]]; then | |
26 | echo "roslaunch --options missing" from ${COMPREPLY[@]} ; exit 1 | |
27 | fi | |
28 | echo success roslaunch --option | |
29 | ||
30 | export COMP_WORDS=("roslaunch" "roslaunch") | |
31 | export COMP_CWORD=2 | |
32 | _roscomplete_launch roslaunch roslaunch | |
33 | if [[ ! ${COMPREPLY[@]} =~ "example.launch" ]]; then | |
34 | echo "example.launch missing from " ${COMPREPLY[@]} ; exit 1 | |
35 | fi | |
36 | echo success roslaunch launchfiles | |
37 | ||
38 | ||
39 | # if [[ ! ${COMPREPLY[@]} =~ "example.launch" ]]; then | |
40 | # echo "example.launch missing from " ${COMPREPLY[@]} ; exit 1 | |
41 | # fi | |
42 | # echo success 4⏎ |
0 | #! /usr/bin/env zsh | |
1 | ||
2 | # This script is supposed to be a unit test for rosbash, also used for manual checks against other operating systems. | |
3 | ||
4 | # TODO extend this script | |
5 | ||
6 | ||
7 | . ../roszsh | |
8 | ||
9 | echo "Testing ZSH" | |
10 | ||
11 | . ./test_zshbash.sh | |
12 | ||
13 | # roslaunch completion | |
14 | ||
15 | BUFFER=("foo roslaunch") | |
16 | _roscomplete_search_dir | |
17 | if [[ ! ${reply[@]} =~ "example.launch" ]]; then | |
18 | echo "rosbash package missing from" ${reply[@]} ; exit 1 | |
19 | fi | |
20 | echo success roslaunch launchfiles |
0 | #!/usr/bin/env python | |
1 | ||
2 | import os | |
3 | import subprocess | |
4 | import unittest | |
5 | import tempfile | |
6 | import shutil | |
7 | ||
8 | PKG_PATH = os.getcwd() | |
9 | TEST_PATH = os.path.join(PKG_PATH, 'test') | |
10 | ||
11 | def make_bash_pre_command(strings, currentword): | |
12 | return "bash -c '. %s; export COMP_WORDS=(%s); export COMP_CWORD=%s;"%(os.path.join(PKG_PATH, 'rosbash'), ' '.join(['"%s"'%w for w in strings]), currentword) | |
13 | ||
14 | class TestRosBash(unittest.TestCase): | |
15 | ||
16 | def setUp(self): | |
17 | self.cmdbash = os.path.join(TEST_PATH, 'test_rosbash.bash') | |
18 | self.assertTrue(os.path.exists(self.cmdbash)) | |
19 | ||
20 | self.cmdzsh = os.path.join(TEST_PATH, 'test_roszsh.zsh') | |
21 | self.assertTrue(os.path.exists(self.cmdzsh)) | |
22 | ||
23 | def test_rosbash_completion(self): | |
24 | subprocess.check_call([self.cmdbash], cwd = TEST_PATH) | |
25 | ||
26 | def test_roszsh_completion(self): | |
27 | subprocess.check_call([self.cmdzsh], cwd = TEST_PATH) | |
28 | ||
29 | ||
30 | class TestWithFiles(unittest.TestCase): | |
31 | ||
32 | @classmethod | |
33 | def setUpClass(self): | |
34 | self.test_root_path = tempfile.mkdtemp() | |
35 | ||
36 | @classmethod | |
37 | def tearDownClass(self): | |
38 | shutil.rmtree(self.test_root_path) | |
39 | ||
40 | def test_make_precommand(self): | |
41 | self.assertEqual( "bash -c '. %s; export COMP_WORDS=(\"foo\" \"bar\"); export COMP_CWORD=1;"%os.path.join(PKG_PATH, 'rosbash'), make_bash_pre_command(['foo', 'bar'], 1)) | |
42 | self.assertEqual( "bash -c '. %s; export COMP_WORDS=(\"foo\"); export COMP_CWORD=2;"%os.path.join(PKG_PATH, 'rosbash'), make_bash_pre_command(['foo'], 2)) | |
43 | ||
44 | def test_roslaunch_completion(self): | |
45 | # regression test that roslaunch completion works even in the presence of launchfiles | |
46 | subprocess.check_call("touch foo.launch", shell=True, cwd=self.test_root_path) | |
47 | subprocess.check_call("touch bar.launch", shell=True, cwd=self.test_root_path) | |
48 | ||
49 | cmd = make_bash_pre_command(['rosbash', 'rosbash'], 2) | |
50 | cmd += "_roscomplete_launch rosbash rosbash; echo $COMPREPLY'" | |
51 | p = subprocess.Popen(cmd, | |
52 | shell=True, | |
53 | stdout=subprocess.PIPE, | |
54 | cwd=self.test_root_path) | |
55 | output = p.communicate() | |
56 | self.assertEqual(0, p.returncode, (p.returncode, output, cmd)) | |
57 | ||
58 | self.assertTrue('example.launch' in output[0], (p.returncode, output[0], cmd)) |
0 | # tests that can work against both bash and zsh | |
1 | ||
2 | ||
3 | ||
4 | # rossed (extended regular expressions) | |
5 | if [[ ! `echo abcde | _rossed 's,abc?,foo,'` = "foode" ]]; then | |
6 | echo "rosbash package missing from" ${reply[@]} ; exit 1 | |
7 | fi | |
8 | ||
9 | echo success rossed | |
10 | ||
11 | ||
12 | ROS_LOCATIONS=foohome=/work:share=/share | |
13 | if [[ ! `_ros_location_find foohome` = "/work" ]]; then | |
14 | echo "ros_location_find missed foohome"; exit 1 | |
15 | fi | |
16 | if [[ ! `_ros_location_find share` = "/share" ]]; then | |
17 | echo "ros_location_find missed share"; exit 1 | |
18 | fi | |
19 | ||
20 | echo success ros_location_find |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(rosboost_cfg) | |
2 | find_package(catkin REQUIRED) | |
3 | catkin_package() | |
4 | catkin_package_export() | |
5 | ||
6 | catkin_python_setup() |
0 | <package> | |
1 | <description brief="Contains scripts for determining the boost configuration on a system"> | |
2 | ||
3 | Contains scripts used by the rosboost-cfg tool for determining cflags/lflags/etc. of boost on your system | |
4 | ||
5 | </description> | |
6 | <author>Josh Faust</author> | |
7 | <license>BSD</license> | |
8 | <review status="unreviewed" notes=""/> | |
9 | <url>http://ros.org/wiki/rosboost_cfg</url> | |
10 | </package> | |
11 | ||
12 |
0 | <package> | |
1 | <name>rosboost_cfg</name> | |
2 | <version>1.9.11</version> | |
3 | <description> | |
4 | ||
5 | Contains scripts used by the rosboost-cfg tool for determining cflags/lflags/etc. of boost on your system | |
6 | ||
7 | </description> | |
8 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
9 | <license>BSD</license> | |
10 | ||
11 | <url>http://ros.org/wiki/rosboost_cfg</url> | |
12 | <author>Josh Faust</author> | |
13 | ||
14 | <build_depend>catkin</build_depend> | |
15 | </package> |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2010, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | ||
33 | import rosboost_cfg | |
34 | rosboost_cfg.main() |
0 | #!/usr/bin/env python | |
1 | ||
2 | from distutils.core import setup | |
3 | from catkin_pkg.package import parse_package_for_distutils | |
4 | ||
5 | d = parse_package_for_distutils() | |
6 | d['packages'] = ['rosboost_cfg'] | |
7 | d['package_dir'] = {'': 'src'} | |
8 | d['scripts'] = ['scripts/rosboost-cfg'] | |
9 | d['install_requires'] = ['rospkg'] | |
10 | ||
11 | setup(**d) |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2010, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | from .rosboost_cfg import * |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2010, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | ||
33 | from __future__ import print_function | |
34 | ||
35 | import sys | |
36 | import os | |
37 | import string | |
38 | from glob import glob | |
39 | import subprocess | |
40 | import platform | |
41 | from optparse import OptionParser | |
42 | ||
43 | lib_suffix = "so" | |
44 | if (sys.platform == "darwin"): | |
45 | lib_suffix = "dylib" | |
46 | ||
47 | link_static = 'ROS_BOOST_LINK' in os.environ and os.environ['ROS_BOOST_LINK'] == "static" | |
48 | if (link_static): | |
49 | lib_suffix = "a" | |
50 | ||
51 | no_L_or_I = 'ROS_BOOST_NO_L_OR_I' in os.environ | |
52 | ||
53 | boost_version = None | |
54 | if ('ROS_BOOST_VERSION' in os.environ and len(os.environ['ROS_BOOST_VERSION']) > 0): | |
55 | ver = os.environ['ROS_BOOST_VERSION'] | |
56 | ver = ver.split('.') | |
57 | ||
58 | boost_version = [int(v) for v in ver] | |
59 | if (len(boost_version) == 2): | |
60 | boost_version.append(0) | |
61 | ||
62 | def print_usage_and_exit(): | |
63 | print("Usage: rosboost-cfg --lflags [thread,regex,graph,...]") | |
64 | print(" rosboost-cfg --cflags") | |
65 | print(" rosboost-cfg --libs [thread,regex,graph,...]") | |
66 | print(" rosboost-cfg --include_dirs") | |
67 | print(" rosboost-cfg --lib_dirs") | |
68 | print(" rosboost-cfg --root") | |
69 | sys.exit(1) | |
70 | ||
71 | class BoostError(Exception): | |
72 | def __init__(self, value): | |
73 | self.value = value | |
74 | def __str__(self): | |
75 | return repr(self.value) | |
76 | ||
77 | class Version(object): | |
78 | def __init__(self, major, minor, patch, root, include_dir, lib_dir, is_default_search_location): | |
79 | self.major = major | |
80 | self.minor = minor | |
81 | self.patch = patch | |
82 | self.root = root | |
83 | self.include_dir = include_dir | |
84 | self.lib_dir = lib_dir | |
85 | self.is_default_search_location = is_default_search_location | |
86 | self.is_system_install = os.path.split(self.include_dir)[0] == self.root | |
87 | ||
88 | def __cmp__(self, other): | |
89 | if (self.major != other.major): | |
90 | if self.major < other.major: | |
91 | return -1 | |
92 | else: | |
93 | return 1 | |
94 | if (self.minor != other.minor): | |
95 | if self.minor < other.minor: | |
96 | return -1 | |
97 | else: | |
98 | return 1 | |
99 | if (self.patch != other.patch): | |
100 | if self.patch < other.patch: | |
101 | return -1 | |
102 | else: | |
103 | return 1 | |
104 | ||
105 | return 0 | |
106 | def __repr__(self): | |
107 | return repr((self.major, self.minor, self.patch, self.root, self.include_dir, self.is_default_search_location, self.is_system_install)) | |
108 | ||
109 | def find_lib_dir(root_dir): | |
110 | # prefer lib64 unless explicitly specified in the environment | |
111 | if ('ROS_BOOST_LIB_DIR_NAME' in os.environ): | |
112 | possible_dirs = [os.path.join(root_dir, os.environ['ROS_BOOST_LIB_DIR_NAME'])] | |
113 | else: | |
114 | possible_dirs = [os.path.join(root_dir, "lib64"), os.path.join(root_dir, "lib")] | |
115 | ||
116 | for p in possible_dirs: | |
117 | glob_files = glob("%s*"%(os.path.join(p, "libboost*"))) | |
118 | if (len(glob_files) > 0): | |
119 | return p | |
120 | ||
121 | return None | |
122 | ||
123 | def extract_versions(dir, is_default_search_location): | |
124 | version_paths = [os.path.join(dir, "version.hpp"), | |
125 | os.path.join(dir, "boost", "version.hpp")] | |
126 | glob_dirs = glob("%s*"%(os.path.join(dir, "boost-"))) | |
127 | [version_paths.append(os.path.join(gdir, "boost", "version.hpp")) for gdir in glob_dirs] | |
128 | ||
129 | versions = [] | |
130 | ||
131 | for p in version_paths: | |
132 | ver_string = "" | |
133 | if (os.path.isfile(p)): | |
134 | fh = open(p,"r") | |
135 | lines = fh.readlines() | |
136 | fh.close() | |
137 | for line in lines: | |
138 | if line.find("#define BOOST_VERSION ") > -1: | |
139 | def_string = line.split() | |
140 | ver_string = def_string[2] | |
141 | ver_int = int(ver_string) | |
142 | patch = ver_int % 100 | |
143 | minor = ver_int / 100 % 1000 | |
144 | major = ver_int / 100000 | |
145 | include_dir = os.path.split(os.path.split(p)[0])[0] | |
146 | root_dir = os.path.split(dir)[0] | |
147 | lib_dir = find_lib_dir(root_dir) | |
148 | versions.append(Version(major, minor, patch, root_dir, include_dir, lib_dir, is_default_search_location)) | |
149 | ||
150 | return versions | |
151 | ||
152 | def find_versions(search_paths): | |
153 | vers = [] | |
154 | ||
155 | for path, system in search_paths: | |
156 | path = os.path.join(path, "include") | |
157 | pvers = extract_versions(path, system) | |
158 | [vers.append(ver) for ver in pvers] | |
159 | ||
160 | if (len(vers) == 0): | |
161 | return None | |
162 | ||
163 | if (boost_version is not None): | |
164 | for v in vers: | |
165 | if (v.major == boost_version[0] and v.minor == boost_version[1] and v.patch == boost_version[2]): | |
166 | return [v] | |
167 | ||
168 | raise BoostError('Could not find boost version %s required by ROS_BOOST_VERSION environment variable'%(boost_version)) | |
169 | ||
170 | vers.sort() | |
171 | return vers | |
172 | ||
173 | def find_boost(search_paths): | |
174 | result = find_versions(search_paths) | |
175 | if result is None: | |
176 | return None | |
177 | if len(result) > 1: | |
178 | sys.stderr.write("WARN, found multiple boost versions '%s', using latest"%result) | |
179 | return result[-1] | |
180 | ||
181 | def search_paths(sysroot): | |
182 | _search_paths = [(sysroot+'/usr', True), | |
183 | (sysroot+'/usr/local', True), | |
184 | (None if 'INCLUDE_DIRS' not in os.environ else os.environ['INCLUDE_DIRS'], True), | |
185 | (None if 'CPATH' not in os.environ else os.environ['CPATH'], True), | |
186 | (None if 'C_INCLUDE_PATH' not in os.environ else os.environ['C_INCLUDE_PATH'], True), | |
187 | (None if 'CPLUS_INCLUDE_PATH' not in os.environ else os.environ['CPLUS_INCLUDE_PATH'], True), | |
188 | (None if 'ROS_BOOST_ROOT' not in os.environ else os.environ['ROS_BOOST_ROOT'], False)] | |
189 | ||
190 | search_paths = [] | |
191 | for (str, system) in _search_paths: | |
192 | if (str is not None): | |
193 | dirs = str.split(':') | |
194 | for dir in dirs: | |
195 | if (len(dir) > 0): | |
196 | if (dir.endswith('/include')): | |
197 | dir = dir[:-len('/include')] | |
198 | search_paths.append((dir, system)) | |
199 | return search_paths | |
200 | ||
201 | def lib_dir(ver): | |
202 | return ver.lib_dir | |
203 | ||
204 | def find_lib(ver, name, full_lib = link_static): | |
205 | global lib_suffix | |
206 | global link_static | |
207 | ||
208 | dynamic_search_paths = [] | |
209 | static_search_paths = [] | |
210 | ||
211 | if (ver.is_system_install): | |
212 | dynamic_search_paths = ["libboost_%s-mt.%s"%(name, lib_suffix), | |
213 | "libboost_%s.%s"%(name, lib_suffix)] | |
214 | static_search_paths = ["libboost_%s-mt.a"%(name), | |
215 | "libboost_%s.a"%(name)] | |
216 | else: | |
217 | dynamic_search_paths = ["libboost_%s*%s_%s*.%s"%(name, ver.major, ver.minor, lib_suffix), | |
218 | "libboost_%s-mt*.%s"%(name, lib_suffix), | |
219 | "libboost_%s*.%s"%(name, lib_suffix)] | |
220 | static_search_paths = ["libboost_%s*%s_%s*.a"%(name, ver.major, ver.minor), | |
221 | "libboost_%s-mt*.a"%(name), | |
222 | "libboost_%s*.a"%(name)] | |
223 | ||
224 | # Boost.Python needs some special handling on some systems (Karmic), since it may have per-python-version libs | |
225 | if (name == "python"): | |
226 | python_ver = platform.python_version().split('.') | |
227 | dynamic_search_paths = ["libboost_%s-mt-py%s%s.%s"%(name, python_ver[0], python_ver[1], lib_suffix), | |
228 | "libboost_%s-py%s%s.%s"%(name, python_ver[0], python_ver[1], lib_suffix)] + dynamic_search_paths | |
229 | static_search_paths = ["libboost_%s-mt-py%s%s.a"%(name, python_ver[0], python_ver[1]), | |
230 | "libboost_%s-py%s%s.a"%(name, python_ver[0], python_ver[1])] + static_search_paths | |
231 | ||
232 | search_paths = static_search_paths if link_static else dynamic_search_paths | |
233 | ||
234 | dir = lib_dir(ver) | |
235 | ||
236 | if dir is None: | |
237 | raise BoostError('Could not locate library [%s], version %s'%(name, ver)) | |
238 | ||
239 | for p in search_paths: | |
240 | globstr = os.path.join(dir, p) | |
241 | libs = glob(globstr) | |
242 | if (len(libs) > 0): | |
243 | if (full_lib): | |
244 | return libs[0] | |
245 | else: | |
246 | return os.path.basename(libs[0]) | |
247 | ||
248 | raise BoostError('Could not locate library [%s], version %s in lib directory [%s]'%(name, ver, dir)) | |
249 | ||
250 | def include_dirs(ver, prefix = ''): | |
251 | if ver.is_system_install or no_L_or_I: | |
252 | return "" | |
253 | ||
254 | return " %s%s"%(prefix, ver.include_dir) | |
255 | ||
256 | def cflags(ver): | |
257 | return include_dirs(ver, '-I') | |
258 | ||
259 | def lib_dir_flags(ver): | |
260 | if not ver.is_default_search_location: | |
261 | dir = lib_dir(ver) | |
262 | return ' -L%s -Wl,-rpath,%s'%(dir, dir) | |
263 | ||
264 | return '' | |
265 | ||
266 | def lib_flags(ver, name): | |
267 | lib = find_lib(ver, name) | |
268 | if (link_static): | |
269 | return ' %s'%(lib) | |
270 | else: | |
271 | # Cut off "lib" and extension (.so/.a/.dylib/etc.) | |
272 | return ' -l%s'%(os.path.splitext(lib)[0][len('lib'):]) | |
273 | ||
274 | def lflags(ver, libs): | |
275 | s= lib_dir_flags(ver) + " " | |
276 | for lib in libs: | |
277 | s += lib_flags(ver, lib) + " " | |
278 | return s | |
279 | ||
280 | def libs(ver, libs): | |
281 | s = "" | |
282 | for lib in libs: | |
283 | s += find_lib(ver, lib, True) + " " | |
284 | return s | |
285 | ||
286 | def lib_dirs(ver): | |
287 | if (ver.is_default_search_location or no_L_or_I): | |
288 | return "" | |
289 | ||
290 | return lib_dir(ver) | |
291 | ||
292 | OPTIONS = ['libs', 'include_dirs', 'lib_dirs', 'cflags', 'lflags', 'root', 'print_versions', 'version'] | |
293 | ||
294 | def check_one_option(options, key): | |
295 | for k in dir(options): | |
296 | if (k in OPTIONS): | |
297 | v = getattr(options, k) | |
298 | if (k != key and v): | |
299 | raise BoostError("Only one option (excepting sysroot) is allowed at a time") | |
300 | ||
301 | def main(): | |
302 | if (len(sys.argv) < 2): | |
303 | print_usage_and_exit() | |
304 | ||
305 | parser = OptionParser() | |
306 | parser.add_option("-l", "--libs", dest="libs", type="string", help="") | |
307 | parser.add_option("-i", "--include_dirs", dest="include_dirs", action="store_true", default=False, help="") | |
308 | parser.add_option("-d", "--lib_dirs", dest="lib_dirs", action="store_true", help="") | |
309 | parser.add_option("-c", "--cflags", dest="cflags", action="store_true", default=False, help="") | |
310 | parser.add_option("-f", "--lflags", dest="lflags", type="string", help="") | |
311 | parser.add_option("-r", "--root", dest="root", action="store_true", default=False, help="") | |
312 | parser.add_option("-p", "--print_versions", dest="print_versions", action="store_true", default=False, help="") | |
313 | parser.add_option("-v", "--version", dest="version", action="store_true", default=False, help="") | |
314 | parser.add_option("-s", "--sysroot", dest="sysroot", type="string", default='', help="Location of the system root (usually toolchain root).") | |
315 | ||
316 | (options, args) = parser.parse_args() | |
317 | ||
318 | if (options.print_versions): | |
319 | check_one_option(options, 'print_versions') | |
320 | for ver in find_versions(search_paths(options.sysroot)): | |
321 | print('%s.%s.%s root=%s include_dir=%s'%(ver.major, ver.minor, ver.patch, ver.root, ver.include_dir)) | |
322 | return | |
323 | ||
324 | ver = find_boost(search_paths(options.sysroot)) | |
325 | ||
326 | if ver is None: | |
327 | raise BoostError("Cannot find boost in any of %s"%search_paths(options.sysroot)) | |
328 | sys.exit(0) | |
329 | ||
330 | if options.version: | |
331 | check_one_option(options, 'version') | |
332 | print('%s.%s.%s root=%s include_dir=%s'%(ver.major, ver.minor, ver.patch, ver.root, ver.include_dir)) | |
333 | return | |
334 | ||
335 | if ver.major < 1 or (ver.major == 1 and ver.minor < 37): | |
336 | raise BoostError('Boost version %s.%s.%s does not meet the minimum requirements of boost 1.37.0'%(ver.major, ver.minor, ver.patch)) | |
337 | ||
338 | ||
339 | ||
340 | output = "" | |
341 | if (options.root): | |
342 | check_one_option(options, 'root') | |
343 | output = ver.root | |
344 | elif (options.libs): | |
345 | check_one_option(options, 'libs') | |
346 | output = libs(ver, options.libs.split(',')) | |
347 | elif (options.include_dirs): | |
348 | check_one_option(options, 'include_dirs') | |
349 | output = include_dirs(ver) | |
350 | elif (options.lib_dirs): | |
351 | check_one_option(options, 'lib_dirs') | |
352 | output = lib_dirs(ver) | |
353 | elif (options.cflags): | |
354 | check_one_option(options, 'cflags') | |
355 | output = cflags(ver) | |
356 | elif (options.lflags): | |
357 | check_one_option(options, 'lflags') | |
358 | output = lflags(ver, options.lflags.split(',')) | |
359 | else: | |
360 | print_usage_and_exit() | |
361 | ||
362 | print(output.strip()) | |
363 | ||
364 | if __name__ == "__main__": | |
365 | main() | |
366 |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(rosclean) | |
2 | find_package(catkin REQUIRED) | |
3 | catkin_package() | |
4 | catkin_package_export() | |
5 | ||
6 | catkin_python_setup() | |
7 | ||
8 | catkin_add_nosetests(test) |
0 | <package> | |
1 | <description brief="rosclean"> | |
2 | ||
3 | rosclean: cleanup filesystem resources (e.g. log files). | |
4 | ||
5 | </description> | |
6 | <author>Ken Conley</author> | |
7 | <license>BSD</license> | |
8 | <review status="unreviewed" notes=""/> | |
9 | <url>http://ros.org/wiki/rosclean</url> | |
10 | ||
11 | </package> | |
12 | ||
13 |
0 | <package> | |
1 | <name>rosclean</name> | |
2 | <version>1.9.11</version> | |
3 | <description brief="rosclean"> | |
4 | ||
5 | rosclean: cleanup filesystem resources (e.g. log files). | |
6 | ||
7 | </description> | |
8 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
9 | <license>BSD</license> | |
10 | ||
11 | <url>http://ros.org/wiki/rosclean</url> | |
12 | <author>Ken Conley</author> | |
13 | ||
14 | <build_depend>catkin</build_depend> | |
15 | </package> |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2010, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | ||
33 | import rosclean | |
34 | rosclean.rosclean_main() |
0 | #!/usr/bin/env python | |
1 | ||
2 | from distutils.core import setup | |
3 | from catkin_pkg.package import parse_package_for_distutils | |
4 | ||
5 | d = parse_package_for_distutils() | |
6 | d['packages'] = ['rosclean'] | |
7 | d['package_dir'] = {'': 'src'} | |
8 | d['scripts'] = ['scripts/rosclean'] | |
9 | d['install_requires'] = ['rospkg'] | |
10 | ||
11 | setup(**d) |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2010, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | from __future__ import print_function | |
33 | ||
34 | __version__ = '1.7.0' | |
35 | ||
36 | import os | |
37 | import sys | |
38 | import platform | |
39 | import subprocess | |
40 | ||
41 | import rospkg | |
42 | ||
43 | class CleanupException(Exception): pass | |
44 | ||
45 | def _ask_and_call(cmds, cwd=None): | |
46 | """ | |
47 | Pretty print cmds, ask if they should be run, and if so, runs | |
48 | them using subprocess.check_call. | |
49 | ||
50 | :param cwd: (optional) set cwd of command that is executed, ``str`` | |
51 | :returns: ``True`` if cmds were run. | |
52 | """ | |
53 | # Pretty-print a string version of the commands | |
54 | def quote(s): | |
55 | return '"%s"'%s if ' ' in s else s | |
56 | sys.stdout.write("Okay to execute:\n\n%s\n(y/n)?\n"%('\n'.join([' '.join([quote(s) for s in c]) for c in cmds]))) | |
57 | while 1: | |
58 | input = sys.stdin.readline().strip().lower() | |
59 | if input in ['y', 'n']: | |
60 | break | |
61 | accepted = input == 'y' | |
62 | if accepted: | |
63 | for c in cmds: | |
64 | if cwd: | |
65 | subprocess.check_call(c, cwd=cwd) | |
66 | else: | |
67 | subprocess.check_call(c) | |
68 | return accepted | |
69 | ||
70 | def _usage(): | |
71 | print("""Usage: rosclean <command> | |
72 | ||
73 | Commands: | |
74 | \trosclean check\tCheck usage of log files | |
75 | \trosclean purge\tRemove log files | |
76 | """) | |
77 | sys.exit(getattr(os, 'EX_USAGE', 1)) | |
78 | ||
79 | def _get_check_dirs(): | |
80 | home_dir = rospkg.get_ros_home() | |
81 | log_dir = rospkg.get_log_dir() | |
82 | dirs = [ (log_dir, 'ROS node logs'), | |
83 | (os.path.join(home_dir, 'rosmake'), 'rosmake logs')] | |
84 | return [x for x in dirs if os.path.isdir(x[0])] | |
85 | ||
86 | def _rosclean_cmd_check(argv): | |
87 | dirs = _get_check_dirs() | |
88 | for d, label in dirs: | |
89 | desc = get_human_readable_disk_usage(d) | |
90 | print("%s %s"%(desc, label)) | |
91 | ||
92 | def get_human_readable_disk_usage(d): | |
93 | """ | |
94 | Get human-readable disk usage for directory | |
95 | ||
96 | :param d: directory path, ``str` | |
97 | :returns: human-readable disk usage (du -h), ``str`` | |
98 | """ | |
99 | # only implemented on Linux and FreeBSD for now. Should work on OS X but need to verify first (du is not identical) | |
100 | if platform.system() in ['Linux', 'FreeBSD']: | |
101 | try: | |
102 | return subprocess.Popen(['du', '-sh', d], stdout=subprocess.PIPE).communicate()[0].split()[0] | |
103 | except: | |
104 | raise CleanupException("rosclean is not supported on this platform") | |
105 | else: | |
106 | raise CleanupException("rosclean is not supported on this platform") | |
107 | ||
108 | def get_disk_usage(d): | |
109 | """ | |
110 | Get disk usage in bytes for directory | |
111 | :param d: directory path, ``str`` | |
112 | :returns: disk usage in bytes (du -b) or (du -A) * 1024, ``int`` | |
113 | :raises: :exc:`CleanupException` If get_disk_usage() cannot be used on this platform | |
114 | """ | |
115 | # only implemented on Linux and FreeBSD for now. Should work on OS X but need to verify first (du is not identical) | |
116 | if platform.system() == 'Linux': | |
117 | try: | |
118 | return int(subprocess.Popen(['du', '-sb', d], stdout=subprocess.PIPE).communicate()[0].split()[0]) | |
119 | except: | |
120 | raise CleanupException("rosclean is not supported on this platform") | |
121 | elif platform.system() == 'FreeBSD': | |
122 | try: | |
123 | return int(subprocess.Popen(['du', '-sA', d], stdout=subprocess.PIPE).communicate()[0].split()[0]) * 1024 | |
124 | except: | |
125 | raise CleanupException("rosclean is not supported on this platform") | |
126 | else: | |
127 | raise CleanupException("rosclean is not supported on this platform") | |
128 | ||
129 | def _rosclean_cmd_purge(argv): | |
130 | dirs = _get_check_dirs() | |
131 | ||
132 | for d, label in dirs: | |
133 | print("Purging %s.\nPLEASE BE CAREFUL TO VERIFY THE COMMAND BELOW!"%label) | |
134 | cmds = [['rm', '-rf', d]] | |
135 | try: | |
136 | _ask_and_call(cmds) | |
137 | except: | |
138 | print("FAILED to execute command", file=sys.stderr) | |
139 | ||
140 | def rosclean_main(argv=None): | |
141 | if argv == None: | |
142 | argv = sys.argv | |
143 | if len(argv) < 2: | |
144 | _usage() | |
145 | command = argv[1] | |
146 | if command == 'check': | |
147 | _rosclean_cmd_check(argv) | |
148 | elif command == 'purge': | |
149 | _rosclean_cmd_purge(argv) | |
150 | else: | |
151 | _usage() | |
152 | ||
153 | if __name__ == '__main__': | |
154 | rosclean_main() |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2012, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import sys | |
34 | ||
35 | def test__get_check_dirs(): | |
36 | # just a tripwire, no way to assert the actual values w/o identical reimplementation | |
37 | from rosclean import _get_check_dirs | |
38 | vals = _get_check_dirs() | |
39 | for path, desc in vals: | |
40 | assert os.path.isdir(path) | |
41 | ||
42 | def test_get_human_readable_disk_usage(): | |
43 | from rosclean import get_human_readable_disk_usage | |
44 | val = get_human_readable_disk_usage(get_test_path()) | |
45 | assert val | |
46 | ||
47 | def get_test_path(): | |
48 | return os.path.abspath(os.path.join(os.path.dirname(__file__))) | |
49 | ||
50 | def test_get_disk_usage(): | |
51 | from rosclean import get_disk_usage | |
52 | val = get_disk_usage(get_test_path()) | |
53 | assert val > 0 | |
54 | ||
55 | def test_cmd(): | |
56 | from rosclean import rosclean_main | |
57 | try: | |
58 | rosclean_main(['rosclean', 'fake']) | |
59 | assert False, "should have raised sys exit" | |
60 | except SystemExit: | |
61 | pass | |
62 | ||
63 | # should run cleanly | |
64 | try: | |
65 | rosclean_main(['rosclean', 'check']) | |
66 | except SystemExit: | |
67 | assert False, "failed with sys exit" |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(roscreate) | |
2 | find_package(catkin REQUIRED) | |
3 | catkin_package() | |
4 | catkin_package_export() | |
5 | ||
6 | catkin_python_setup() | |
7 | ||
8 | catkin_add_nosetests(test) |
0 | <package> | |
1 | <description brief="roscreate"> | |
2 | ||
3 | roscreate contains tools that assist in the creation of ROS filesystem resources. There are currently | |
4 | two tools that it provides: <tt>roscreate-pkg</tt> and <tt>roscreate-stack</tt>. <tt>roscreate-pkg</tt> creates a new package directory, | |
5 | including the appropriate build and manifest files. <tt>roscreate-stack</tt> computes a <tt>stack.xml</tt> file from a list of packages | |
6 | in a directory. | |
7 | ||
8 | </description> | |
9 | <author>Ken Conley/kwc@willowgarage.com</author> | |
10 | <license>BSD</license> | |
11 | <review status="Doc reviewed" notes="2010/01/07"/> | |
12 | <url>http://ros.org/wiki/roscreate</url> | |
13 | <platform os="ubuntu" version="9.04"/> | |
14 | <platform os="ubuntu" version="9.10"/> | |
15 | <platform os="ubuntu" version="10.04"/> | |
16 | <platform os="macports" version="macports"/> | |
17 | </package> | |
18 | ||
19 | ||
20 |
0 | <package> | |
1 | <name>roscreate</name> | |
2 | <version>1.9.11</version> | |
3 | <description> | |
4 | roscreate contains a tool that assists in the creation of ROS filesystem resources. | |
5 | It provides: <tt>roscreate-pkg</tt>, which creates a new package directory, | |
6 | including the appropriate build and manifest files. | |
7 | </description> | |
8 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
9 | <license>BSD</license> | |
10 | ||
11 | <url type="website">http://ros.org/wiki/roscreate</url> | |
12 | ||
13 | <author email="kwc@willowgarage.com">Ken Conley</author> | |
14 | ||
15 | <build_depend>catkin</build_depend> | |
16 | </package> |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | ||
33 | import roscreate | |
34 | roscreate.roscreatepkg.roscreatepkg_main() |
0 | #!/usr/bin/env python | |
1 | ||
2 | from distutils.core import setup | |
3 | from catkin_pkg.package import parse_package_for_distutils | |
4 | ||
5 | d = parse_package_for_distutils() | |
6 | d['packages'] = ['roscreate'] | |
7 | d['package_dir'] = {'': 'src'} | |
8 | d['scripts'] = ['scripts/roscreate-pkg'] | |
9 | d['install_requires'] = ['rospkg'] | |
10 | ||
11 | setup(**d) |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | ||
35 | import roscreate.core | |
36 | import roscreate.roscreatepkg |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2009, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | ||
33 | from __future__ import print_function | |
34 | ||
35 | import os | |
36 | import sys | |
37 | ||
38 | import rospkg | |
39 | import pkg_resources | |
40 | ||
41 | def print_warning(msg): | |
42 | """print warning to screen (bold red)""" | |
43 | print('\033[31m%s\033[0m'%msg, file=sys.stderr) | |
44 | ||
45 | def author_name(): | |
46 | """ | |
47 | Utility to compute logged in user name | |
48 | ||
49 | :returns: name of current user, ``str`` | |
50 | """ | |
51 | import getpass | |
52 | name = getpass.getuser() | |
53 | try: | |
54 | import pwd | |
55 | login = name | |
56 | name = pwd.getpwnam(login)[4] | |
57 | name = ''.join(name.split(',')) # strip commas | |
58 | # in case pwnam is not set | |
59 | if not name: | |
60 | name = login | |
61 | except: | |
62 | #pwd failed | |
63 | pass | |
64 | if type(name) == str: | |
65 | name = name.decode('utf-8') | |
66 | return name | |
67 | ||
68 | def read_template(tmplf): | |
69 | """ | |
70 | Read resource template from egg installation, or fallback on rospkg otherwise. | |
71 | ||
72 | :returns: text of template file | |
73 | """ | |
74 | if pkg_resources.resource_exists('roscreate', tmplf): | |
75 | f = pkg_resources.resource_stream('roscreate', tmplf) | |
76 | t = f.read() | |
77 | else: | |
78 | # fallback on rospkg | |
79 | r = rospkg.RosPack() | |
80 | with open(os.path.join(r.get_path('roscreate'), 'templates', tmplf)) as f: | |
81 | t = f.read() | |
82 | if type(t) == str: | |
83 | t = t.decode('utf-8') | |
84 | return t | |
85 | ||
86 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | from __future__ import print_function | |
35 | ||
36 | NAME='roscreate-pkg' | |
37 | ||
38 | import os | |
39 | import sys | |
40 | ||
41 | import roslib.names | |
42 | ||
43 | from roscreate.core import read_template, author_name | |
44 | from rospkg import on_ros_path, RosPack, ResourceNotFound | |
45 | ||
46 | def get_templates(): | |
47 | templates = {} | |
48 | templates['CMakeLists.txt'] = read_template('CMakeLists.tmpl') | |
49 | templates['manifest.xml'] = read_template('manifest.tmpl') | |
50 | templates['mainpage.dox'] = read_template('mainpage.tmpl') | |
51 | templates['Makefile'] = read_template('Makefile.tmpl') | |
52 | return templates | |
53 | ||
54 | def instantiate_template(template, package, brief, description, author, depends): | |
55 | return template%locals() | |
56 | ||
57 | def create_package(package, author, depends, uses_roscpp=False, uses_rospy=False): | |
58 | p = os.path.abspath(package) | |
59 | if os.path.exists(p): | |
60 | print("%s already exists, aborting"%p, file=sys.stderr) | |
61 | sys.exit(1) | |
62 | ||
63 | os.makedirs(p) | |
64 | print("Created package directory", p) | |
65 | ||
66 | if uses_roscpp: | |
67 | # create package/include/package and package/src for roscpp code | |
68 | cpp_path = os.path.join(p, 'include', package) | |
69 | try: | |
70 | os.makedirs(cpp_path) | |
71 | print("Created include directory", cpp_path) | |
72 | cpp_path = os.path.join(p, 'src') | |
73 | os.makedirs(cpp_path) | |
74 | print("Created cpp source directory", cpp_path) | |
75 | except: | |
76 | # file exists | |
77 | pass | |
78 | if uses_rospy: | |
79 | # create package/src/ for python files | |
80 | py_path = os.path.join(p, 'src') | |
81 | try: | |
82 | os.makedirs(py_path) | |
83 | print("Created python source directory", py_path) | |
84 | except: | |
85 | # file exists | |
86 | pass | |
87 | ||
88 | templates = get_templates() | |
89 | for filename, template in templates.iteritems(): | |
90 | contents = instantiate_template(template, package, package, package, author, depends) | |
91 | p = os.path.abspath(os.path.join(package, filename)) | |
92 | with open(p, 'w') as f: | |
93 | f.write(contents.encode('utf-8')) | |
94 | print("Created package file", p) | |
95 | print("\nPlease edit %s/manifest.xml and mainpage.dox to finish creating your package"%package) | |
96 | ||
97 | def roscreatepkg_main(): | |
98 | from optparse import OptionParser | |
99 | parser = OptionParser(usage="usage: %prog <package-name> [dependencies...]", prog=NAME) | |
100 | options, args = parser.parse_args() | |
101 | if not args: | |
102 | parser.error("you must specify a package name and optionally also list package dependencies") | |
103 | package = args[0] | |
104 | ||
105 | if not roslib.names.is_legal_resource_base_name(package): | |
106 | parser.error("illegal package name: %s\nNames must start with a letter and contain only alphanumeric characters\nand underscores."%package) | |
107 | ||
108 | # validate dependencies and turn into XML | |
109 | depends = args[1:] | |
110 | uses_roscpp = 'roscpp' in depends | |
111 | uses_rospy = 'rospy' in depends | |
112 | ||
113 | rospack = RosPack() | |
114 | for d in depends: | |
115 | try: | |
116 | rospack.get_path(d) | |
117 | except ResourceNotFound: | |
118 | print("ERROR: dependency [%s] cannot be found"%d, file=sys.stderr) | |
119 | sys.exit(1) | |
120 | ||
121 | depends = u''.join([u' <depend package="%s"/>\n'%d for d in depends]) | |
122 | ||
123 | if not on_ros_path(os.getcwd()): | |
124 | print('!'*80+"\nWARNING: current working directory is not on ROS_PACKAGE_PATH!\nPlease update your ROS_PACKAGE_PATH environment variable.\n"+'!'*80, file=sys.stderr) | |
125 | if type(package) == str: | |
126 | package = package.decode('utf-8') | |
127 | create_package(package, author_name(), depends, uses_roscpp=uses_roscpp, uses_rospy=uses_rospy) |
0 | cmake_minimum_required(VERSION 2.4.6) | |
1 | include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake) | |
2 | ||
3 | # Append to CPACK_SOURCE_IGNORE_FILES a semicolon-separated list of | |
4 | # directories (or patterns, but directories should suffice) that should | |
5 | # be excluded from the distro. This is not the place to put things that | |
6 | # should be ignored everywhere, like "build" directories; that happens in | |
7 | # rosbuild/rosbuild.cmake. Here should be listed packages that aren't | |
8 | # ready for inclusion in a distro. | |
9 | # | |
10 | # This list is combined with the list in rosbuild/rosbuild.cmake. Note | |
11 | # that CMake 2.6 may be required to ensure that the two lists are combined | |
12 | # properly. CMake 2.4 seems to have unpredictable scoping rules for such | |
13 | # variables. | |
14 | #list(APPEND CPACK_SOURCE_IGNORE_FILES /core/experimental) | |
15 | ||
16 | rosbuild_make_distribution(0.1.0) |
0 | cmake_minimum_required(VERSION 2.4.6) | |
1 | include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake) | |
2 | ||
3 | # Set the build type. Options are: | |
4 | # Coverage : w/ debug symbols, w/o optimization, w/ code-coverage | |
5 | # Debug : w/ debug symbols, w/o optimization | |
6 | # Release : w/o debug symbols, w/ optimization | |
7 | # RelWithDebInfo : w/ debug symbols, w/ optimization | |
8 | # MinSizeRel : w/o debug symbols, w/ optimization, stripped binaries | |
9 | #set(ROS_BUILD_TYPE RelWithDebInfo) | |
10 | ||
11 | rosbuild_init() | |
12 | ||
13 | #set the default path for built executables to the "bin" directory | |
14 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) | |
15 | #set the default path for built libraries to the "lib" directory | |
16 | set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) | |
17 | ||
18 | #uncomment if you have defined messages | |
19 | #rosbuild_genmsg() | |
20 | #uncomment if you have defined services | |
21 | #rosbuild_gensrv() | |
22 | ||
23 | #common commands for building c++ executables and libraries | |
24 | #rosbuild_add_library(${PROJECT_NAME} src/example.cpp) | |
25 | #target_link_libraries(${PROJECT_NAME} another_library) | |
26 | #rosbuild_add_boost_directories() | |
27 | #rosbuild_link_boost(${PROJECT_NAME} thread) | |
28 | #rosbuild_add_executable(example examples/example.cpp) | |
29 | #target_link_libraries(example ${PROJECT_NAME}) |
0 | include $(shell rospack find mk)/cmake_stack.mk⏎ |
0 | include $(shell rospack find mk)/cmake.mk⏎ |
0 | /** | |
1 | \mainpage | |
2 | \htmlinclude manifest.html | |
3 | ||
4 | \b %(package)s | |
5 | ||
6 | <!-- | |
7 | Provide an overview of your package. | |
8 | --> | |
9 | ||
10 | --> | |
11 | ||
12 | ||
13 | */ |
0 | <package> | |
1 | <description brief="%(brief)s"> | |
2 | ||
3 | %(description)s | |
4 | ||
5 | </description> | |
6 | <author>%(author)s</author> | |
7 | <license>BSD</license> | |
8 | <review status="unreviewed" notes=""/> | |
9 | <url>http://ros.org/wiki/%(package)s</url> | |
10 | %(depends)s | |
11 | </package> | |
12 | ||
13 |
0 | <stack> | |
1 | <description brief="%(brief)s">%(description)s</description> | |
2 | <author>%(author)s</author> | |
3 | <license>%(licenses)s</license> | |
4 | %(review)s | |
5 | <url>http://ros.org/wiki/%(stack)s</url> | |
6 | %(depends)s | |
7 | </stack> |
0 | <package> | |
1 | <description brief="depends_roslib"> | |
2 | ||
3 | depends_roslib | |
4 | ||
5 | </description> | |
6 | <author>Kenneth Conley</author> | |
7 | <license>BSD</license> | |
8 | <depend package="roslib"/> | |
9 | ||
10 | </package> | |
11 | ||
12 |
0 | <stack> | |
1 | <description brief="stack1">stack1</description> | |
2 | <author>Maintained by Kenneth Conley</author> | |
3 | <license>BSD</license> | |
4 | <depend stack="ros" /> <!-- roslib --> | |
5 | </stack> |
0 | cmake_minimum_required(VERSION 2.4.6) | |
1 | include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake) | |
2 | ||
3 | # Set the build type. Options are: | |
4 | # Coverage : w/ debug symbols, w/o optimization, w/ code-coverage | |
5 | # Debug : w/ debug symbols, w/o optimization | |
6 | # Release : w/o debug symbols, w/ optimization | |
7 | # RelWithDebInfo : w/ debug symbols, w/ optimization | |
8 | # MinSizeRel : w/o debug symbols, w/ optimization, stripped binaries | |
9 | #set(ROS_BUILD_TYPE RelWithDebInfo) | |
10 | ||
11 | rosbuild_init() | |
12 | ||
13 | #set the default path for built executables to the "bin" directory | |
14 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) | |
15 | #set the default path for built libraries to the "lib" directory | |
16 | set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) | |
17 | ||
18 | #uncomment if you have defined messages | |
19 | #rosbuild_genmsg() | |
20 | #uncomment if you have defined services | |
21 | #rosbuild_gensrv() | |
22 | ||
23 | #common commands for building c++ executables and libraries | |
24 | #rosbuild_add_library(${PROJECT_NAME} src/example.cpp) | |
25 | #target_link_libraries(${PROJECT_NAME} another_library) | |
26 | #rosbuild_add_boost_directories() | |
27 | #rosbuild_link_boost(${PROJECT_NAME} thread) | |
28 | #rosbuild_add_executable(example examples/example.cpp) | |
29 | #target_link_libraries(example ${PROJECT_NAME}) |
0 | include $(shell rospack find mk)/cmake.mk⏎ |
0 | /** | |
1 | \mainpage | |
2 | \htmlinclude manifest.html | |
3 | ||
4 | \b depends_stack1 is ... | |
5 | ||
6 | <!-- | |
7 | In addition to providing an overview of your package, | |
8 | this is the section where the specification and design/architecture | |
9 | should be detailed. While the original specification may be done on the | |
10 | wiki, it should be transferred here once your package starts to take shape. | |
11 | You can then link to this documentation page from the Wiki. | |
12 | --> | |
13 | ||
14 | ||
15 | \section codeapi Code API | |
16 | ||
17 | <!-- | |
18 | Provide links to specific auto-generated API documentation within your | |
19 | package that is of particular interest to a reader. Doxygen will | |
20 | document pretty much every part of your code, so do your best here to | |
21 | point the reader to the actual API. | |
22 | ||
23 | If your codebase is fairly large or has different sets of APIs, you | |
24 | should use the doxygen 'group' tag to keep these APIs together. For | |
25 | example, the roscpp documentation has 'libros' group. | |
26 | --> | |
27 | ||
28 | ||
29 | */⏎ |
0 | <package> | |
1 | <description brief="depends_stack1"> | |
2 | depends_stack1 | |
3 | </description> | |
4 | <author>Kenneth Conley</author> | |
5 | <license>BSD</license> | |
6 | <depend package="depends_roslib" /> | |
7 | </package> | |
8 | ||
9 |
0 | <stack> | |
1 | <description brief="stack2">stack2</description> | |
2 | <author>Maintained by Kenneth Conley</author> | |
3 | <license>BSD</license> | |
4 | <depend stack="stack1" /> <!-- depends_roslib --> | |
5 | </stack> |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2012, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | def test_author_name(): | |
33 | from roscreate.core import author_name | |
34 | val = author_name() | |
35 | assert val, val | |
36 | ||
37 | def test_read_template(): | |
38 | from roscreate.core import read_template | |
39 | s = set() | |
40 | # this unit test will break if any of the templates get removed/renamed | |
41 | tests = ['Makefile.tmpl', 'stack.tmpl', 'mainpage.tmpl', 'CMakeLists.stack.tmpl'] | |
42 | for f in tests: | |
43 | text = read_template(f) | |
44 | s.add(text) | |
45 | # simple assert to make sure we didn't read the same thing from each template | |
46 | assert len(s) == len(tests) | |
47 | ||
48 | # hardcode test against a known template | |
49 | text = read_template('Makefile.tmpl') | |
50 | assert text == 'include $(shell rospack find mk)/cmake.mk' |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(rosmake) | |
2 | find_package(catkin REQUIRED) | |
3 | ||
4 | catkin_package() | |
5 | catkin_package_export() | |
6 | ||
7 | catkin_python_setup() | |
8 | ||
9 | catkin_add_nosetests(test) |
0 | <package> | |
1 | <description brief="rosmake"> | |
2 | ||
3 | rosmake is a ros dependency aware build tool which can be used to | |
4 | build all dependencies in the correct order. | |
5 | ||
6 | </description> | |
7 | <author>Tully Foote/tfoote@willowgarage.com</author> | |
8 | <license>BSD</license> | |
9 | <review status="Doc Reviewed" notes="2010-01-12"/> | |
10 | <url>http://ros.org/wiki/rosmake</url> | |
11 | ||
12 | <export> | |
13 | <rosdoc config="rosdoc.yaml"/> | |
14 | </export> | |
15 | ||
16 | </package> | |
17 | ||
18 |
0 | <package> | |
1 | <name>rosmake</name> | |
2 | <version>1.9.11</version> | |
3 | <description> | |
4 | rosmake is a ros dependency aware build tool which can be used to | |
5 | build all dependencies in the correct order. | |
6 | </description> | |
7 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
8 | <license>BSD</license> | |
9 | ||
10 | <url>http://ros.org/wiki/rosmake</url> | |
11 | ||
12 | <author email="tfoote@willowgarage.com">Tully Foote</author> | |
13 | ||
14 | <build_depend>catkin</build_depend> | |
15 | ||
16 | <export> | |
17 | <rosdoc config="rosdoc.yaml"/> | |
18 | </export> | |
19 | </package> |
0 | - builder: epydoc |
0 | #! /usr/bin/env python | |
1 | ||
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions are met: | |
7 | # | |
8 | # * Redistributions of source code must retain the above copyright | |
9 | # notice, this list of conditions and the following disclaimer. | |
10 | # * Redistributions in binary form must reproduce the above copyright | |
11 | # notice, this list of conditions and the following disclaimer in the | |
12 | # documentation and/or other materials provided with the distribution. | |
13 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
14 | # contributors may be used to endorse or promote products derived from | |
15 | # this software without specific prior written permission. | |
16 | # | |
17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
20 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
21 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
22 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
23 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
24 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
25 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
26 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
27 | # POSSIBILITY OF SUCH DAMAGE. | |
28 | ||
29 | ||
30 | # Author Tully Foote/tfoote@willowgarage.com | |
31 | from __future__ import print_function | |
32 | ||
33 | import os | |
34 | import sys | |
35 | import subprocess | |
36 | import threading | |
37 | ||
38 | import rospkg | |
39 | import rosmake | |
40 | ||
41 | ## make sure that rospack is built, it is a requirement for rosmake | |
42 | def assert_rospack_built(): | |
43 | p = subprocess.Popen(["rospack","help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
44 | output = p.communicate() | |
45 | if p.returncode != 0: | |
46 | print("rospack not available.\nPlease install rospack before using rosmake and make sure it is available on your path. %s %s"%(output[0], output[1]), file=sys.stderr) | |
47 | sys.exit(1) | |
48 | ||
49 | result = 1 | |
50 | with rosmake.Printer(): | |
51 | rma = rosmake.RosMakeAll() | |
52 | ||
53 | try: | |
54 | if rma.main(): | |
55 | result = 0 | |
56 | except rospkg.ResourceNotFound as e: | |
57 | print("cannot find required resource: %s"%(str(e))) | |
58 | ||
59 | #make sure the thread is done | |
60 | rosmake.Printer().join() | |
61 | for t in threading.enumerate(): | |
62 | if t != threading.currentThread(): | |
63 | # Join all threads before exiting | |
64 | print("Cleaning up thread", t) | |
65 | t.join() | |
66 | sys.exit(result) |
0 | #!/usr/bin/env python | |
1 | ||
2 | from distutils.core import setup | |
3 | from catkin_pkg.package import parse_package_for_distutils | |
4 | ||
5 | d = parse_package_for_distutils() | |
6 | d['packages'] = ['rosmake'] | |
7 | d['package_dir'] = {'': 'src'} | |
8 | d['scripts'] = ['scripts/rosmake'] | |
9 | d['install_requires'] = ['rospkg'] | |
10 | ||
11 | setup(**d) |
0 | # Copyright (c) 2009, Willow Garage, Inc. | |
1 | # All rights reserved. | |
2 | # | |
3 | # Redistribution and use in source and binary forms, with or without | |
4 | # modification, are permitted provided that the following conditions are met: | |
5 | # | |
6 | # * Redistributions of source code must retain the above copyright | |
7 | # notice, this list of conditions and the following disclaimer. | |
8 | # * Redistributions in binary form must reproduce the above copyright | |
9 | # notice, this list of conditions and the following disclaimer in the | |
10 | # documentation and/or other materials provided with the distribution. | |
11 | # * Neither the name of the Willow Garage, Inc. nor the names of its | |
12 | # contributors may be used to endorse or promote products derived from | |
13 | # this software without specific prior written permission. | |
14 | # | |
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
19 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
20 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
21 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
22 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
23 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
24 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
25 | # POSSIBILITY OF SUCH DAMAGE. | |
26 | ||
27 | """ | |
28 | Implements the 'rosmake' command-line tool via the L{RosMakeAll} | |
29 | class. This class is for internal-use only within ROS tools. The API | |
30 | is very likely to change in future releases and is not stable. | |
31 | """ | |
32 | ||
33 | from .engine import RosMakeAll, Printer | |
34 | ||
35 | __version__ = '1.7.0' |
0 | #! /usr/bin/env python | |
1 | ||
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions are met: | |
7 | # | |
8 | # * Redistributions of source code must retain the above copyright | |
9 | # notice, this list of conditions and the following disclaimer. | |
10 | # * Redistributions in binary form must reproduce the above copyright | |
11 | # notice, this list of conditions and the following disclaimer in the | |
12 | # documentation and/or other materials provided with the distribution. | |
13 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
14 | # contributors may be used to endorse or promote products derived from | |
15 | # this software without specific prior written permission. | |
16 | # | |
17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
20 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
21 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
22 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
23 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
24 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
25 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
26 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
27 | # POSSIBILITY OF SUCH DAMAGE. | |
28 | ||
29 | # Author Tully Foote/tfoote@willowgarage.com | |
30 | ||
31 | from __future__ import print_function | |
32 | ||
33 | import os | |
34 | import re | |
35 | import signal | |
36 | import sys | |
37 | import subprocess | |
38 | import time | |
39 | import threading | |
40 | import traceback | |
41 | ||
42 | import rospkg | |
43 | ||
44 | try: | |
45 | from exceptions import SystemExit #Python 2.x | |
46 | except ImportError: | |
47 | pass #Python 3.x (in Python 3, 'exceptions' is always imported) | |
48 | ||
49 | from operator import itemgetter | |
50 | ||
51 | from . import parallel_build | |
52 | from . import package_stats | |
53 | ||
54 | from optparse import OptionParser | |
55 | ||
56 | # #3883 | |
57 | _popen_lock = threading.Lock() | |
58 | ||
59 | def make_command(): | |
60 | """ | |
61 | @return: name of 'make' command | |
62 | @rtype: str | |
63 | """ | |
64 | return os.environ.get("MAKE", "make") | |
65 | ||
66 | # this is a copy of the roslogging utility. it's been moved here as it is a common | |
67 | # routine for programs using accessing ROS directories | |
68 | def makedirs_with_parent_perms(p): | |
69 | """ | |
70 | Create the directory using the permissions of the nearest | |
71 | (existing) parent directory. This is useful for logging, where a | |
72 | root process sometimes has to log in the user's space. | |
73 | @param p: directory to create | |
74 | @type p: str | |
75 | """ | |
76 | p = os.path.abspath(p) | |
77 | parent = os.path.dirname(p) | |
78 | # recurse upwards, checking to make sure we haven't reached the | |
79 | # top | |
80 | if not os.path.exists(p) and p and parent != p: | |
81 | makedirs_with_parent_perms(parent) | |
82 | s = os.stat(parent) | |
83 | os.mkdir(p) | |
84 | ||
85 | # if perms of new dir don't match, set anew | |
86 | s2 = os.stat(p) | |
87 | if s.st_uid != s2.st_uid or s.st_gid != s2.st_gid: | |
88 | os.chown(p, s.st_uid, s.st_gid) | |
89 | if s.st_mode != s2.st_mode: | |
90 | os.chmod(p, s.st_mode) | |
91 | ||
92 | class Printer: | |
93 | # storage for the instance reference | |
94 | __instance = None | |
95 | ||
96 | def __init__(self): | |
97 | """ Create singleton instance """ | |
98 | # Check whether we already have an instance | |
99 | if Printer.__instance is None: | |
100 | # Create and remember instance | |
101 | Printer.__instance = Printer.__impl() | |
102 | ||
103 | # Store instance reference as the only member in the handle | |
104 | self.__dict__['_Printer__instance'] = Printer.__instance | |
105 | ||
106 | def __getattr__(self, attr): | |
107 | """ Delegate access to implementation """ | |
108 | return getattr(self.__instance, attr) | |
109 | ||
110 | def __setattr__(self, attr, value): | |
111 | """ Delegate access to implementation """ | |
112 | return setattr(self.__instance, attr, value) | |
113 | ||
114 | def __enter__(self): | |
115 | """Pass through for the __enter__ function for the __instance""" | |
116 | return self.__instance.__enter__() | |
117 | ||
118 | def __exit__(self, mtype, value, tb): | |
119 | """Pass through for the __exit__ function for the __instance""" | |
120 | return self.__instance.__exit__(mtype, value, tb) | |
121 | ||
122 | class __impl(threading.Thread): | |
123 | def __init__(self): | |
124 | threading.Thread.__init__(self) | |
125 | self.build_queue = None | |
126 | self.condition = threading.Condition() | |
127 | self.running = True | |
128 | self.done = False | |
129 | self.status = "" | |
130 | self.verbose = False | |
131 | self.full_verbose = False | |
132 | self.duration = 1./10. | |
133 | self._last_status = None | |
134 | ||
135 | # Rosmake specific data | |
136 | self.cache_argument = None | |
137 | self.cache_right = '' | |
138 | self.pkg_start_times = {} | |
139 | ||
140 | def shutdown(self): | |
141 | self.running = False | |
142 | cycles = 10 | |
143 | for i in range(0,cycles):# sleep for at least 2 cycles of the status testing 'cycles' times | |
144 | if self.done: | |
145 | #print "SUCCESSFULLY SHUTDOWN" | |
146 | return True | |
147 | #print "Sleeping for %f FOR SHUTDOWN. %d threads running"%(max(self.duration/cycles*2, 0.01), threading.activeCount()) | |
148 | time.sleep(max(self.duration, 0.1)/cycles*2) | |
149 | raise Exception("Failed to shutdown status thread in %.2f seconds"%(self.duration * 2)) | |
150 | ||
151 | def __enter__(self): | |
152 | self.start() | |
153 | def __exit__(self, mtype, value, tb): | |
154 | self.shutdown() | |
155 | if value: | |
156 | if not mtype == type(SystemExit()): | |
157 | traceback.print_exception(mtype, value, tb) | |
158 | else: | |
159 | sys.exit(value) | |
160 | ||
161 | def run(self): | |
162 | while self.running: | |
163 | #shutdown if duration set to zero | |
164 | if self.duration <= 0: | |
165 | self.running = False | |
166 | break | |
167 | self.set_status_from_cache() | |
168 | if len(self.pkg_start_times.keys()) > 0: | |
169 | n = self.terminal_width() - len(self.status) | |
170 | status = self.status | |
171 | if n > 0: | |
172 | status = " "*n + self.status | |
173 | if status != self._last_status: | |
174 | self._print_status("%s"%status) | |
175 | self._last_status = status | |
176 | time.sleep(self.duration) | |
177 | self.done = True | |
178 | #print "STATUS THREAD FINISHED" | |
179 | ||
180 | def rosmake_cache_info(self, argument, start_times, right): | |
181 | self.cache_argument = argument | |
182 | self.pkg_start_times = start_times | |
183 | self.cache_right = right | |
184 | ||
185 | def rosmake_pkg_times_to_string(self, start_times): | |
186 | threads = [] | |
187 | for p, t in sorted(start_times.items(), key=itemgetter(1)): #py3k | |
188 | threads.append("[ %s: %.1f sec ]"%(p, time.time() - t)) | |
189 | ||
190 | return " ".join(threads) | |
191 | ||
192 | def set_status_from_cache(self): | |
193 | if self.cache_argument: | |
194 | self.set_status("[ make %s ] "%self.cache_argument + self.rosmake_pkg_times_to_string(self.pkg_start_times), self.cache_right) | |
195 | else: | |
196 | self.set_status("[ make ] " + self.rosmake_pkg_times_to_string(self.pkg_start_times), self.cache_right) | |
197 | ||
198 | def set_status(self, left, right = ''): | |
199 | header = "[ rosmake ] " | |
200 | h = len(header) | |
201 | l = len(left) | |
202 | r = len(right) | |
203 | w = self.terminal_width() | |
204 | if l + r < w - h: | |
205 | padding = w - h - l - r | |
206 | self.status = header + left + " "*padding + right | |
207 | else: | |
208 | self.status = header + left[:(w - h - r - 4)] + "... " + right | |
209 | ||
210 | def print_all(self, s, thread_name=None): | |
211 | if thread_name is None: | |
212 | str = "[ rosmake ] %s"%s | |
213 | ||
214 | ||
215 | else: | |
216 | str = "[rosmake-%s] %s"%(thread_name, s) | |
217 | sys.stdout.write(self.pad_str_to_width(str, self.terminal_width())+"\n") | |
218 | sys.stdout.flush() | |
219 | ||
220 | def print_verbose(self, s, thread_name=None): | |
221 | if self.verbose or self.full_verbose: | |
222 | self.print_all(s, thread_name=thread_name) | |
223 | ||
224 | def print_full_verbose(self, s): | |
225 | if self.full_verbose: | |
226 | print("[ rosmake ] %s"%(s)) | |
227 | ||
228 | def print_tail(self, s, tail_lines=40): | |
229 | lines = s.splitlines() | |
230 | if self.full_verbose: | |
231 | tail_lines = len(lines) | |
232 | ||
233 | num_lines = min(len(lines), tail_lines) | |
234 | if num_lines == tail_lines: | |
235 | print("[ rosmake ] Last %d lines"%(num_lines)) | |
236 | else: | |
237 | print("[ rosmake ] All %d lines"%(num_lines)) | |
238 | print("{" + "-"*79) | |
239 | for l in range(-num_lines, -1): | |
240 | print(" %s"%(lines[l])) | |
241 | print("-"*79 + "}") | |
242 | ||
243 | def _print_status(self, s): | |
244 | sys.stdout.write("%s\r"%(s)) | |
245 | sys.stdout.flush() | |
246 | ||
247 | @staticmethod | |
248 | def terminal_width(): | |
249 | """Estimate the width of the terminal""" | |
250 | width = 0 | |
251 | try: | |
252 | import struct, fcntl, termios | |
253 | s = struct.pack('HHHH', 0, 0, 0, 0) | |
254 | x = fcntl.ioctl(1, termios.TIOCGWINSZ, s) | |
255 | width = struct.unpack('HHHH', x)[1] | |
256 | except IOError: | |
257 | pass | |
258 | if width <= 0: | |
259 | try: | |
260 | width = int(os.environ['COLUMNS']) | |
261 | except: | |
262 | pass | |
263 | if width <= 0: | |
264 | width = 80 | |
265 | ||
266 | return width | |
267 | ||
268 | @staticmethod | |
269 | def pad_str_to_width(str, width): | |
270 | """ Pad the string to be terminal width""" | |
271 | length = len(str) | |
272 | excess = 0 | |
273 | if length < width: | |
274 | excess = width - length | |
275 | return str + " "* excess | |
276 | ||
277 | ||
278 | ||
279 | class RosMakeAll: | |
280 | def __init__(self): | |
281 | self._result_lock = threading.Lock() | |
282 | ||
283 | self.rospack = rospkg.RosPack() | |
284 | self.rosstack = rospkg.RosStack() | |
285 | ||
286 | self.printer = Printer() | |
287 | self.result = {} | |
288 | self.paths = {} | |
289 | self.dependency_tracker = parallel_build.DependencyTracker(rospack=self.rospack) | |
290 | self.flag_tracker = package_stats.PackageFlagTracker(self.dependency_tracker) | |
291 | self.output = {} | |
292 | self.profile = {} | |
293 | self.ros_parallel_jobs = 0 | |
294 | self.build_list = [] | |
295 | self.start_time = time.time() | |
296 | self.log_dir = "" | |
297 | self.logging_enabled = True | |
298 | ||
299 | def num_packages_built(self): | |
300 | """ | |
301 | @return: number of packages that were built | |
302 | @rtype: int | |
303 | """ | |
304 | return len(list(self.result[argument].keys())) #py3k | |
305 | ||
306 | def update_status(self, argument, start_times, right): | |
307 | self.printer.rosmake_cache_info(argument, start_times, right) | |
308 | ||
309 | def build_or_recurse(self,p): | |
310 | if p in self.build_list: | |
311 | return | |
312 | for d in self.dependency_tracker.get_deps_1(p): | |
313 | self.build_or_recurse(d) | |
314 | try: # append it ot the list only if present | |
315 | self.rospack.get_path(p) | |
316 | self.build_list.append(p) | |
317 | except rospkg.ResourceNotFound as ex: | |
318 | if not self.robust_build: | |
319 | self.printer.print_all("Exiting due to missing package: %s"%ex) | |
320 | sys.exit(-1) | |
321 | else: | |
322 | self.printer.print_all("!"*20 + " Package %s does not exist. %s"%(p, ex) + "!"*20) | |
323 | ||
324 | ||
325 | def parallel_build_pkgs(self, build_queue, argument = None, threads = 1): | |
326 | self.profile[argument] = {} | |
327 | self.output[argument] = {} | |
328 | with self._result_lock: | |
329 | if argument not in self.result.keys(): | |
330 | self.result[argument] = {} | |
331 | ||
332 | cts = [] | |
333 | for i in range(0, threads): | |
334 | ct = parallel_build.CompileThread(str(i), build_queue, self, argument) | |
335 | #print "TTTH starting thread ", ct | |
336 | ct.start() | |
337 | cts.append(ct) | |
338 | for ct in cts: | |
339 | try: | |
340 | #print "TTTT Joining", ct | |
341 | ct.join() | |
342 | #print "TTTH naturally ended thread", ct | |
343 | except KeyboardInterrupt: | |
344 | self.printer.print_all( "TTTH Caught KeyboardInterrupt. Stopping build.") | |
345 | build_queue.stop() | |
346 | ct.join() | |
347 | except: #catch all | |
348 | self.printer.print_all("TTTH OTHER exception thrown!!!!!!!!!!!!!!!!!!!!!") | |
349 | ct.join() | |
350 | #print "All threads joined" | |
351 | all_pkgs_passed = True | |
352 | with self._result_lock: | |
353 | for v in self.result[argument].values(): | |
354 | all_pkgs_passed = v and all_pkgs_passed | |
355 | ||
356 | build_passed = build_queue.succeeded() and all_pkgs_passed | |
357 | return build_passed | |
358 | ||
359 | # This function taken from | |
360 | # http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html | |
361 | def _subprocess_setup(self): | |
362 | # Python installs a SIGPIPE handler by default. This is usually not | |
363 | # what non-Python subprocesses expect. | |
364 | signal.signal(signal.SIGPIPE, signal.SIG_DFL) | |
365 | ||
366 | def _build_package(self, package, argument=None): | |
367 | """ | |
368 | Lower-level routine for building a package. Handles execution of actual build command. | |
369 | @param package: package name | |
370 | @type package: str | |
371 | """ | |
372 | local_env = os.environ.copy() | |
373 | if self.ros_parallel_jobs > 0: | |
374 | local_env['ROS_PARALLEL_JOBS'] = "-j -l%d" % self.ros_parallel_jobs | |
375 | elif "ROS_PARALLEL_JOBS" not in os.environ: #if no environment setup and no args fall back to # cpus | |
376 | # num_cpus check can (on OS X) trigger a Popen(), which has | |
377 | #the multithreading bug we wish to avoid on Py2.7. | |
378 | with _popen_lock: | |
379 | local_env['ROS_PARALLEL_JOBS'] = "-j -l%d" % parallel_build.num_cpus() | |
380 | local_env['SVN_CMDLINE'] = "svn --non-interactive" | |
381 | cmd = ["bash", "-c", "cd %s && %s "%(self.rospack.get_path(package), make_command()) ] #UNIXONLY | |
382 | if argument: | |
383 | cmd[-1] += argument | |
384 | self.printer.print_full_verbose (cmd) | |
385 | # #3883: make sure only one Popen command occurs at a time due to | |
386 | # http://bugs.python.org/issue13817 | |
387 | with _popen_lock: | |
388 | command_line = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=local_env, preexec_fn=self._subprocess_setup) | |
389 | (pstd_out, pstd_err) = command_line.communicate() # pstd_err should be None due to pipe above | |
390 | return (command_line.returncode, pstd_out) | |
391 | ||
392 | def build(self, p, argument = None, robust_build=False): | |
393 | """ | |
394 | Build package | |
395 | @param p: package name | |
396 | @type p: str | |
397 | """ | |
398 | return_string = "" | |
399 | try: | |
400 | # warn if ROS_BUILD_BLACKLIST encountered if applicable | |
401 | # do not build packages for which the build has failed | |
402 | if argument == "test": # Tests are not build dependent | |
403 | failed_packages = [] | |
404 | else: | |
405 | with self._result_lock: | |
406 | failed_packages = [j for j in self.result[argument] if not self.result[argument][j] == True] | |
407 | ||
408 | (buildable, error, why) = self.flag_tracker.can_build(p, self.skip_blacklist, failed_packages) | |
409 | if buildable or self.robust_build: | |
410 | start_time = time.time() | |
411 | (returncode, pstd_out) = self._build_package(p, argument) | |
412 | self.profile[argument][p] = time.time() - start_time | |
413 | self.output[argument][p] = pstd_out | |
414 | if argument: | |
415 | log_type = "build_%s"%argument | |
416 | else: | |
417 | log_type = "build" | |
418 | if not returncode: | |
419 | self.printer.print_full_verbose( pstd_out) | |
420 | with self._result_lock: | |
421 | self.result[argument][p] = True | |
422 | ||
423 | num_warnings = len(re.findall("warning:", pstd_out)) | |
424 | if num_warnings > 0: | |
425 | return_string = ("[PASS] [ %.2f seconds ] -- WARNING: %d compiler warnings"%(self.profile[argument][p], num_warnings)) | |
426 | else: | |
427 | return_string = ("[PASS] [ %.2f seconds ]"%( self.profile[argument][p])) | |
428 | self.output_to_file(p, log_type, pstd_out, num_warnings > 0) | |
429 | else: | |
430 | success = False | |
431 | no_target = len(re.findall("No rule to make target", pstd_out)) > 0 | |
432 | interrupt = len(re.findall("Interrupt", pstd_out)) > 0 | |
433 | if no_target: | |
434 | return_string = ( "[SKIP] No rule to make target %s"%( argument)) | |
435 | success = True | |
436 | elif interrupt: | |
437 | return_string = ("[Interrupted]" ) | |
438 | else: | |
439 | return_string = ( "[FAIL] [ %.2f seconds ]"%( self.profile[argument][p])) | |
440 | with self._result_lock: | |
441 | self.result[argument][p] = True if no_target else False | |
442 | ||
443 | if success == False: #don't print tail if [SKIP] target | |
444 | self.printer.print_tail( pstd_out) | |
445 | self.output_to_file(p, log_type, pstd_out, always_print= not (no_target or interrupt)) | |
446 | ||
447 | return (success, return_string) | |
448 | else: | |
449 | with self._result_lock: | |
450 | self.result[argument][p] = error | |
451 | ||
452 | return_string += why | |
453 | return(error, return_string) | |
454 | return (True, return_string) # this means that we didn't error in any case above | |
455 | except rospkg.ResourceNotFound as ex: | |
456 | with self._result_lock: | |
457 | self.result[argument][p] = False | |
458 | self.printer.print_verbose ("[SKIP] Package %s not found\n" % p) | |
459 | self.output[argument][p] = "Package not found %s"%ex | |
460 | return (False, return_string) | |
461 | ||
462 | ||
463 | def output_to_file(self, package, log_type, stdout, always_print= False): | |
464 | if not self.logging_enabled: | |
465 | return | |
466 | package_log_dir = os.path.join(self.log_dir, package) | |
467 | ||
468 | std_out_filename = os.path.join(package_log_dir, log_type + "_output.log") | |
469 | if not os.path.exists (package_log_dir): | |
470 | makedirs_with_parent_perms(package_log_dir) | |
471 | with open(std_out_filename, 'w') as stdout_file: | |
472 | stdout_file.write(stdout) | |
473 | print_string = "Output from build of package %s written to:\n[ rosmake ] %s"%(package, std_out_filename) | |
474 | if always_print: | |
475 | self.printer.print_all(print_string) | |
476 | else: | |
477 | self.printer.print_full_verbose(print_string) | |
478 | ||
479 | def generate_summary_output(self, log_dir): | |
480 | if not self.logging_enabled: | |
481 | return | |
482 | ||
483 | self.printer.print_all("Results:") | |
484 | if 'clean' in self.result.keys(): | |
485 | self.printer.print_all("Cleaned %d packages."%len(self.result['clean'])) | |
486 | if None in self.result.keys(): | |
487 | build_failure_count = len([p for p in self.result[None].keys() if self.result[None][p] == False]) | |
488 | self.printer.print_all("Built %d packages with %d failures."%(len(self.result[None]), build_failure_count)) | |
489 | if 'test' in self.result.keys(): | |
490 | test_failure_count = len([p for p in self.result['test'].keys() if self.result['test'][p] == False]) | |
491 | self.printer.print_all("Tested %d packages with %d failures."%(len(self.result['test']), test_failure_count)) | |
492 | self.printer.print_all("Summary output to directory") | |
493 | self.printer.print_all("%s"%self.log_dir) | |
494 | if self.rejected_packages: | |
495 | self.printer.print_all("WARNING: Skipped command line arguments: %s because they could not be resolved to a stack name or a package name. "%self.rejected_packages) | |
496 | ||
497 | ||
498 | ||
499 | if None in self.result.keys(): | |
500 | if len(self.result[None].keys()) > 0: | |
501 | buildfail_filename = os.path.join(log_dir, "buildfailures.txt") | |
502 | with open(buildfail_filename, 'w') as bf: | |
503 | bf.write("Build failures:\n") | |
504 | for key in self.build_list: | |
505 | if key in self.result[None].keys() and self.result[None][key] == False: | |
506 | bf.write("%s\n"%key) | |
507 | if None in self.output.keys(): | |
508 | buildfail_context_filename = os.path.join(log_dir, "buildfailures-with-context.txt") | |
509 | with open(buildfail_context_filename, 'w') as bfwc: | |
510 | bfwc.write("Build failures with context:\n") | |
511 | for key in self.build_list: | |
512 | if key in self.result[None].keys() and self.result[None][key] == False: | |
513 | bfwc.write("---------------------\n") | |
514 | bfwc.write("%s\n"%key) | |
515 | if key in self.output[None]: | |
516 | bfwc.write(self.output[None][key]) | |
517 | ||
518 | if "test" in self.result.keys(): | |
519 | if len(self.result["test"].keys()) > 0: | |
520 | testfail_filename = os.path.join(log_dir, "testfailures.txt") | |
521 | with open(testfail_filename, 'w') as btwc: | |
522 | btwc.write("Test failures:\n") | |
523 | for key in self.build_list: | |
524 | if key in self.result["test"].keys() and self.result["test"][key] == False: | |
525 | btwc.write("%s\n"%key) | |
526 | ||
527 | if "test" in self.output.keys(): | |
528 | testfail_filename = os.path.join(log_dir, "testfailures-with-context.txt") | |
529 | with open(testfail_filename, 'w') as btwc: | |
530 | btwc.write("Test failures with context:\n") | |
531 | for key in self.build_list: | |
532 | if key in self.result["test"].keys() and self.result["test"][key] == False: | |
533 | btwc.write("%s\n"%key) | |
534 | if key in self.output["test"]: | |
535 | btwc.write(self.output["test"][key]) | |
536 | ||
537 | profile_filename = os.path.join(log_dir, "profile.txt") | |
538 | with open(profile_filename, 'w') as pf: | |
539 | pf.write(self.get_profile_string()) | |
540 | ||
541 | ||
542 | ||
543 | def get_profile_string(self): | |
544 | output = '--------------\nProfile\n--------------\n' | |
545 | total = 0.0 | |
546 | count = 1 | |
547 | for key in self.build_list: | |
548 | build_results = ["[Not Built ]", "[ Built ]", "[Build Fail]"]; | |
549 | test_results = ["[Untested ]", "[Test Pass]", "[Test Fail]"]; | |
550 | build_result = 0 | |
551 | test_result = 0 | |
552 | test_time = 0.0 | |
553 | build_time = 0.0 | |
554 | ||
555 | if None in self.result.keys(): | |
556 | if key in self.result[None].keys(): | |
557 | if self.result[None][key] == True: | |
558 | build_result = 1 | |
559 | else: | |
560 | build_result = 2 | |
561 | ||
562 | if "test" in self.profile.keys(): | |
563 | if key in self.result["test"].keys(): | |
564 | if self.result["test"][key] == True: | |
565 | test_result = 1 | |
566 | else: | |
567 | test_result = 2 | |
568 | ||
569 | if None in self.profile.keys(): | |
570 | if key in self.profile[None].keys(): | |
571 | build_time = self.profile[None][key] | |
572 | ||
573 | if "test" in self.profile.keys(): | |
574 | if key in self.profile["test"].keys(): | |
575 | test_time = self.profile["test"][key] | |
576 | ||
577 | ||
578 | output = output + "%3d: %s in %.2f %s in %.2f --- %s\n"% (count, build_results[build_result], build_time , test_results[test_result], test_time, key) | |
579 | total = total + build_time | |
580 | count = count + 1 | |
581 | ||
582 | elapsed_time = self.finish_time - self.start_time | |
583 | output = output + "----------------\n" + "%.2f Cumulative, %.2f Elapsed, %.2f Speedup \n"%(total, elapsed_time, float(total) / float(elapsed_time)) | |
584 | return output | |
585 | ||
586 | def main(self): | |
587 | """ | |
588 | main command-line entrypoint | |
589 | """ | |
590 | parser = OptionParser(usage="usage: %prog [options] [PACKAGE]...", | |
591 | description="rosmake recursively builds all dependencies before building a package", prog='rosmake') | |
592 | parser.add_option("--test-only", dest="test_only", default=False, | |
593 | action="store_true", help="only run tests") | |
594 | parser.add_option("-t", dest="test", default=False, | |
595 | action="store_true", help="build and test packages") | |
596 | parser.add_option("-a", "--all", dest="build_all", default=False, | |
597 | action="store_true", help="select all packages") | |
598 | parser.add_option("-i", "--mark-installed", dest="mark_installed", default=False, | |
599 | action="store_true", help="On successful build, mark specified packages as installed with ROS_NOBUILD") | |
600 | parser.add_option("-u", "--unmark-installed", dest="unmark_installed", default=False, | |
601 | action="store_true", help="Remove ROS_NOBUILD from the specified packages. This will not build anything.") | |
602 | parser.add_option("-v", dest="verbose", default=False, | |
603 | action="store_true", help="display errored builds") | |
604 | parser.add_option("-r","-k", "--robust", dest="best_effort", default=False, | |
605 | action="store_true", help="do not stop build on error") | |
606 | parser.add_option("--build-everything", dest="robust", default=False, | |
607 | action="store_true", help="build all packages regardless of errors") | |
608 | parser.add_option("-V", dest="full_verbose", default=False, | |
609 | action="store_true", help="display all builds") | |
610 | parser.add_option("-s", "--specified-only", dest="specified_only", default=False, | |
611 | action="store_true", help="only build packages specified on the command line") | |
612 | parser.add_option("--buildtest", dest="buildtest", | |
613 | action="append", help="package to buildtest") | |
614 | parser.add_option("--buildtest1", dest="buildtest1", | |
615 | action="append", help="package to buildtest1") | |
616 | parser.add_option("--output", dest="output_dir", | |
617 | action="store", help="where to output results") | |
618 | parser.add_option("--pre-clean", dest="pre_clean", | |
619 | action="store_true", help="run make clean first") | |
620 | parser.add_option("--bootstrap", dest="bootstrap", default=False, | |
621 | action="store_true", help="DEPRECATED, UNUSED") | |
622 | parser.add_option("--disable-logging", dest="logging_enabled", default=True, | |
623 | action="store_false", help="turn off all logs") | |
624 | parser.add_option("--target", dest="target", | |
625 | action="store", help="run make with this target") | |
626 | parser.add_option("--pjobs", dest="ros_parallel_jobs", type="int", | |
627 | action="store", help="Override ROS_PARALLEL_JOBS environment variable with this number of jobs.") | |
628 | parser.add_option("--threads", dest="threads", type="int", default = os.environ.get("ROSMAKE_THREADS", parallel_build.num_cpus()), | |
629 | action="store", help="Build up to N packages in parallel") | |
630 | parser.add_option("--profile", dest="print_profile", default=False, | |
631 | action="store_true", help="print time profile after build") | |
632 | parser.add_option("--skip-blacklist", dest="skip_blacklist", | |
633 | default=False, action="store_true", | |
634 | help="skip packages containing a file called ROS_BUILD_BLACKLIST (Default behavior will ignore the presence of ROS_BUILD_BLACKLIST)") | |
635 | parser.add_option("--skip-blacklist-osx", dest="skip_blacklist_osx", | |
636 | default=False, action="store_true", | |
637 | help="deprecated option. it will do nothing, please use platform declarations and --require-platform instead") | |
638 | ||
639 | parser.add_option("--status-rate", dest="status_update_rate", | |
640 | action="store", help="How fast to update the status bar in Hz. Default: 5Hz") | |
641 | ||
642 | ||
643 | options, args = parser.parse_args() | |
644 | self.printer.print_all('rosmake starting...') | |
645 | ||
646 | rospack = self.rospack | |
647 | rosstack = self.rosstack | |
648 | ||
649 | testing = False | |
650 | building = True | |
651 | if options.test_only: | |
652 | testing = True | |
653 | building = False | |
654 | elif options.test: | |
655 | testing = True | |
656 | ||
657 | if options.ros_parallel_jobs: | |
658 | self.ros_parallel_jobs = options.ros_parallel_jobs | |
659 | ||
660 | self.robust_build = options.robust | |
661 | self.best_effort = options.best_effort | |
662 | self.threads = options.threads | |
663 | self.skip_blacklist = options.skip_blacklist | |
664 | if options.skip_blacklist_osx: | |
665 | self.printer.print_all("Option --skip-blacklist-osx is deprecated. It will do nothing, please use platform declarations and --require-platform instead"); | |
666 | self.logging_enabled = options.logging_enabled | |
667 | ||
668 | # pass through verbosity options | |
669 | self.printer.full_verbose = options.full_verbose | |
670 | self.printer.verbose = options.verbose | |
671 | if options.status_update_rate: | |
672 | if float(options.status_update_rate)> 0: | |
673 | self.printer.duration = 1.0/float(options.status_update_rate) | |
674 | else: | |
675 | self.printer.duration = 0 | |
676 | ||
677 | packages = [] | |
678 | #load packages from arguments | |
679 | if options.build_all: | |
680 | packages = [x for x in rospack.list() if not self.rospack.get_manifest(x).is_catkin] | |
681 | self.printer.print_all( "Building all packages") | |
682 | else: # no need to extend if all already selected | |
683 | if options.buildtest: | |
684 | for p in options.buildtest: | |
685 | packages.extend(self.rospack.get_depends_on(p)) | |
686 | self.printer.print_all( "buildtest requested for package %s adding it and all dependent packages: "%p) | |
687 | ||
688 | if options.buildtest1: | |
689 | for p in options.buildtest1: | |
690 | packages.extend(self.rospack.get_depends_on(p, implicit=False)) | |
691 | self.printer.print_all( "buildtest1 requested for package %s adding it and all depends-on1 packages: "%p) | |
692 | ||
693 | if len(packages) == 0 and len(args) == 0: | |
694 | p = os.path.basename(os.path.abspath('.')) | |
695 | try: | |
696 | if os.path.samefile(rospack.get_path(p), '.'): | |
697 | packages = [p] | |
698 | self.printer.print_all( "No package specified. Building %s"%packages) | |
699 | else: | |
700 | self.printer.print_all("No package selected and the current directory is not the correct path for package '%s'."%p) | |
701 | ||
702 | except rospkg.ResourceNotFound as ex: | |
703 | try: | |
704 | stack_dir = rosstack.get_path(p) | |
705 | if os.path.samefile(stack_dir, '.'): | |
706 | packages = [p] | |
707 | self.printer.print_all( "No package specified. Building stack %s"%packages) | |
708 | else: | |
709 | self.printer.print_all("No package or stack arguments and the current directory is not the correct path for stack '%s'. Stack directory is: %s."%(p, rosstack.get_path(p))) | |
710 | except: | |
711 | self.printer.print_all("No package or stack specified. And current directory '%s' is not a package name or stack name."%p) | |
712 | else: | |
713 | packages.extend(args) | |
714 | ||
715 | self.printer.print_all( "Packages requested are: %s"%packages) | |
716 | ||
717 | # Setup logging | |
718 | if self.logging_enabled: | |
719 | date_time_stamp = "rosmake_output-" + time.strftime("%Y%m%d-%H%M%S") | |
720 | if options.output_dir: | |
721 | #self.log_dir = os.path.join(os.getcwd(), options.output_dir, date_time_stamp); | |
722 | self.log_dir = os.path.abspath(options.output_dir) | |
723 | else: | |
724 | self.log_dir = os.path.join(rospkg.get_ros_home(), "rosmake", date_time_stamp); | |
725 | ||
726 | self.printer.print_all("Logging to directory %s"%self.log_dir) | |
727 | if os.path.exists (self.log_dir) and not os.path.isdir(self.log_dir): | |
728 | self.printer.print_all( "Log destination %s is a file; please remove it or choose a new destination"%self.log_dir) | |
729 | sys.exit(1) | |
730 | if not os.path.exists (self.log_dir): | |
731 | self.printer.print_verbose("%s doesn't exist: creating"%self.log_dir) | |
732 | makedirs_with_parent_perms(self.log_dir) | |
733 | ||
734 | self.printer.print_verbose("Finished setting up logging") | |
735 | ||
736 | stacks_arguments = [s for s in packages if s in rosstack.list()] | |
737 | (self.specified_packages, self.rejected_packages) = rospkg.expand_to_packages(packages, rospack, rosstack) | |
738 | ||
739 | self.printer.print_all("Expanded args %s to:\n%s"%(packages, self.specified_packages)) | |
740 | if self.rejected_packages: | |
741 | self.printer.print_all("WARNING: The following args could not be parsed as stacks or packages: %s"%self.rejected_packages) | |
742 | if len(self.specified_packages) + len(stacks_arguments) == 0: | |
743 | self.printer.print_all("ERROR: No arguments could be parsed into valid package or stack names.") | |
744 | self.printer.running = False | |
745 | return False | |
746 | ||
747 | if options.unmark_installed: | |
748 | for p in self.specified_packages: | |
749 | if self.flag_tracker.remove_nobuild(p): | |
750 | self.printer.print_all("Removed ROS_NOBUILD from %s"%p) | |
751 | self.printer.running = False | |
752 | return True | |
753 | ||
754 | required_packages = self.specified_packages[:] | |
755 | ||
756 | # catch dependent packages which are inside of zero sized stacks #3528 | |
757 | # add them to required list but not the specified list. | |
758 | for s in stacks_arguments: | |
759 | for d in rosstack.get_depends(s, implicit=False): | |
760 | required_packages.extend(rosstack.packages_of(d)) | |
761 | ||
762 | # deduplicate required_packages | |
763 | required_packages = list(set(required_packages)) | |
764 | ||
765 | # make sure all dependencies are satisfied and if not warn | |
766 | buildable_packages = [] | |
767 | for p in required_packages: | |
768 | (buildable, error, str) = self.flag_tracker.can_build(p, self.skip_blacklist, [], False) | |
769 | if buildable: | |
770 | buildable_packages.append(p) | |
771 | ||
772 | #generate the list of packages necessary to build(in order of dependencies) | |
773 | counter = 0 | |
774 | for p in required_packages: | |
775 | ||
776 | counter = counter + 1 | |
777 | self.printer.print_verbose( "Processing %s and all dependencies(%d of %d requested)"%(p, counter, len(packages))) | |
778 | self.build_or_recurse(p) | |
779 | ||
780 | # remove extra packages if specified-only flag is set | |
781 | if options.specified_only: | |
782 | new_list = [] | |
783 | for pkg in self.build_list: | |
784 | if pkg in self.specified_packages: | |
785 | new_list.append(pkg) | |
786 | self.dependency_tracker = parallel_build.DependencyTracker(self.specified_packages, rospack=self.rospack) # this will make the tracker only respond to packages in the list | |
787 | ||
788 | self.printer.print_all("specified-only option was used, only building packages %s"%new_list) | |
789 | self.build_list = new_list | |
790 | ||
791 | if options.pre_clean: | |
792 | build_queue = parallel_build.BuildQueue(self.build_list, parallel_build.DependencyTracker([], rospack=self.rospack), robust_build = True) | |
793 | self.parallel_build_pkgs(build_queue, "clean", threads = options.threads) | |
794 | ||
795 | build_passed = True | |
796 | ||
797 | if building: | |
798 | self.printer.print_verbose ("Building packages %s"% self.build_list) | |
799 | build_queue = parallel_build.BuildQueue(self.build_list, self.dependency_tracker, robust_build = options.robust or options.best_effort) | |
800 | if None not in self.result.keys(): | |
801 | self.result[None] = {} | |
802 | ||
803 | build_passed = self.parallel_build_pkgs(build_queue, options.target, threads = options.threads) | |
804 | ||
805 | tests_passed = True | |
806 | if build_passed and testing: | |
807 | self.printer.print_verbose ("Testing packages %s"% packages) | |
808 | build_queue = parallel_build.BuildQueue(self.specified_packages, parallel_build.DependencyTracker(self.specified_packages, rospack=self.rospack), robust_build = True) | |
809 | tests_passed = self.parallel_build_pkgs(build_queue, "test", threads = 1) | |
810 | ||
811 | ||
812 | if options.mark_installed: | |
813 | if build_passed and tests_passed: | |
814 | for p in self.specified_packages: | |
815 | if self.flag_tracker.add_nobuild(p): | |
816 | self.printer.print_all("Marking %s as installed with a ROS_NOBUILD file"%p) | |
817 | else: | |
818 | self.printer.print_all("All builds and tests did not pass cannot mark packages as installed. ") | |
819 | ||
820 | ||
821 | self.finish_time = time.time() #note: before profiling | |
822 | self.generate_summary_output(self.log_dir) | |
823 | ||
824 | if options.print_profile: | |
825 | self.printer.print_all (self.get_profile_string()) | |
826 | ||
827 | self.printer.running = False | |
828 | return build_passed and tests_passed | |
829 | ||
830 |
0 | #! /usr/bin/env python | |
1 | ||
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions are met: | |
7 | # | |
8 | # * Redistributions of source code must retain the above copyright | |
9 | # notice, this list of conditions and the following disclaimer. | |
10 | # * Redistributions in binary form must reproduce the above copyright | |
11 | # notice, this list of conditions and the following disclaimer in the | |
12 | # documentation and/or other materials provided with the distribution. | |
13 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
14 | # contributors may be used to endorse or promote products derived from | |
15 | # this software without specific prior written permission. | |
16 | # | |
17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
20 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
21 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
22 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
23 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
24 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
25 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
26 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
27 | # POSSIBILITY OF SUCH DAMAGE. | |
28 | ||
29 | ||
30 | # Author Tully Foote/tfoote@willowgarage.com | |
31 | ||
32 | import os | |
33 | import sys | |
34 | import subprocess | |
35 | ||
36 | import rospkg | |
37 | import rospkg.os_detect | |
38 | ||
39 | def _platform_supported(m, os, version): | |
40 | for p in m.platforms: | |
41 | if os == p.os and version == p.version: | |
42 | return True | |
43 | return False | |
44 | ||
45 | def platform_supported(rospack, pkg, os, version): | |
46 | """ | |
47 | Return whether the platform defined by os and version is marked as supported in the package | |
48 | @param pkg The package to test for support | |
49 | @param os The os name to test for support | |
50 | @param version The os version to test for support | |
51 | """ | |
52 | return _platform_supported(rospack.get_manifest(pkg), os, version) | |
53 | ||
54 | class PackageFlagTracker: | |
55 | """ This will use the dependency tracker to test if packages are | |
56 | blacklisted and all their dependents. """ | |
57 | def __init__(self, dependency_tracker, os_name = None, os_version = None): | |
58 | if not os_name and not os_version: | |
59 | try: | |
60 | osd = rospkg.os_detect.OsDetect() | |
61 | self.os_name = osd.get_codename() | |
62 | self.os_version = osd.get_version() | |
63 | except rospkg.os_detect.OsNotDetected as ex: | |
64 | sys.stderr.write("Could not detect OS. platform detection will not work\n") | |
65 | else: | |
66 | self.os_name = os_name | |
67 | self.os_version = os_version | |
68 | ||
69 | self.rospack = rospkg.RosPack() | |
70 | self.blacklisted = {} | |
71 | self.blacklisted_osx = {} | |
72 | self.nobuild = set() | |
73 | self.nomakefile = set() | |
74 | self.packages_tested = set() | |
75 | self.dependency_tracker = dependency_tracker | |
76 | self.build_failed = set() | |
77 | ||
78 | def register_blacklisted(self, blacklisted_package, dependent_package): | |
79 | if dependent_package in self.blacklisted.keys(): | |
80 | self.blacklisted[dependent_package].append(blacklisted_package) | |
81 | else: | |
82 | self.blacklisted[dependent_package] = [blacklisted_package] | |
83 | ||
84 | def register_blacklisted_osx(self, blacklisted_package, dependent_package): | |
85 | if dependent_package in self.blacklisted_osx: | |
86 | self.blacklisted_osx[dependent_package].append(blacklisted_package) | |
87 | else: | |
88 | self.blacklisted_osx[dependent_package] = [blacklisted_package] | |
89 | ||
90 | def _check_package_flags(self, package): | |
91 | if package in self.packages_tested: | |
92 | return | |
93 | rospack = self.rospack | |
94 | path = rospack.get_path(package) | |
95 | ||
96 | if os.path.exists(os.path.join(path, "ROS_BUILD_BLACKLIST")): | |
97 | self.register_blacklisted(package, package) | |
98 | for p in rospack.get_depends_on(package, implicit=True): | |
99 | self.register_blacklisted(package, p) | |
100 | ||
101 | if os.path.exists(os.path.join(path, "ROS_BUILD_BLACKLIST_OSX")): | |
102 | self.register_blacklisted_osx(package, package) | |
103 | for p in rospack.get_depends_on(package, implicit=True): | |
104 | self.register_blacklisted_osx(package, p) | |
105 | ||
106 | # NO_BUILD if marker file or catkin attribute in manifest | |
107 | if os.path.exists(os.path.join(path, "ROS_NOBUILD")): | |
108 | self.nobuild.add(package) | |
109 | if self.rospack.get_manifest(package).is_catkin: | |
110 | self.nobuild.add(package) | |
111 | ||
112 | if not os.path.exists(os.path.join(path, "Makefile")): | |
113 | self.nomakefile.add(package) | |
114 | ||
115 | self.packages_tested.add(package) | |
116 | ||
117 | def is_blacklisted(self, package): | |
118 | # this will noop if already run | |
119 | self._check_package_flags(package) | |
120 | ||
121 | # make sure it's not dependent on a blacklisted package | |
122 | for p in self.dependency_tracker.get_deps(package): | |
123 | if p not in self.packages_tested: | |
124 | self._check_package_flags(p) | |
125 | ||
126 | # test result after checking all dependents. | |
127 | if package in self.blacklisted: | |
128 | return self.blacklisted[package] | |
129 | ||
130 | return [] | |
131 | ||
132 | def is_blacklisted_osx(self, package): | |
133 | # this will noop if already run | |
134 | self._check_package_flags(package) | |
135 | ||
136 | # make sure it's not dependent on a blacklisted_osx package | |
137 | for p in self.dependency_tracker.get_deps(package): | |
138 | if p not in self.packages_tested: | |
139 | self._check_package_flags(p) | |
140 | ||
141 | # test result after checking all dependents. | |
142 | if package in self.blacklisted_osx: | |
143 | return self.blacklisted_osx[package] | |
144 | ||
145 | return [] | |
146 | ||
147 | def has_nobuild(self, package): | |
148 | # this will noop if already run | |
149 | self._check_package_flags(package) | |
150 | ||
151 | # Short circuit if known result | |
152 | if package in self.nobuild: | |
153 | return True | |
154 | return False | |
155 | ||
156 | def has_makefile(self, package): | |
157 | # this will noop if already run | |
158 | self._check_package_flags(package) | |
159 | ||
160 | # Short circuit if known result | |
161 | if package in self.nomakefile: | |
162 | return False | |
163 | return True | |
164 | ||
165 | def add_nobuild(self, package): | |
166 | if self.has_nobuild(package): | |
167 | return True | |
168 | with open(os.path.join(self.rospack.get_path(package), "ROS_NOBUILD"), 'w') as f: | |
169 | f.write("created by rosmake to mark as installed") | |
170 | self.nobuild.add(package) | |
171 | return True | |
172 | return False | |
173 | ||
174 | def remove_nobuild(self, package): | |
175 | if not self.has_nobuild(package): | |
176 | return True | |
177 | try: | |
178 | os.remove(os.path.join(self.rospack.get_path(package), "ROS_NOBUILD")) | |
179 | self.nobuild.remove(package) | |
180 | return True | |
181 | except: | |
182 | return False | |
183 | ||
184 | def mark_build_failed(self, package): | |
185 | self.build_failed.add(package) | |
186 | ||
187 | def build_failed(self, package): | |
188 | return package in self.build_failed | |
189 | ||
190 | def can_build(self, pkg, use_blacklist = False, failed_packages = [], use_makefile = True): | |
191 | """ | |
192 | Return (buildable, error, "reason why not") | |
193 | """ | |
194 | output_str = "" | |
195 | output_state = True | |
196 | buildable = True | |
197 | ||
198 | previously_failed_pkgs = [ pk for pk in failed_packages if pk in self.dependency_tracker.get_deps(pkg)] | |
199 | if len(previously_failed_pkgs) > 0: | |
200 | buildable = False | |
201 | output_state = False | |
202 | output_str += " Package %s cannot be built for dependent package(s) %s failed. \n"%(pkg, previously_failed_pkgs) | |
203 | ||
204 | ||
205 | if use_blacklist: | |
206 | black_listed_dependents = self.is_blacklisted(pkg) | |
207 | if len(black_listed_dependents) > 0: | |
208 | buildable = False | |
209 | output_str += "Cannot build %s ROS_BUILD_BLACKLIST found in packages %s"%(pkg, black_listed_dependents) | |
210 | ||
211 | if self.has_nobuild(pkg): | |
212 | buildable = False | |
213 | output_state = True # dependents are ok, it should already be built | |
214 | output_str += "ROS_NOBUILD in package %s\n"%pkg | |
215 | ||
216 | ||
217 | if use_makefile and not self.has_makefile(pkg): | |
218 | output_state = True # dependents are ok no need to build | |
219 | buildable = False | |
220 | output_str += " No Makefile in package %s\n"%pkg | |
221 | ||
222 | if output_str and output_str[-1] == '\n': | |
223 | output_str = output_str[:-1] | |
224 | ||
225 | return (buildable, output_state, output_str) |
0 | #! /usr/bin/env python | |
1 | ||
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions are met: | |
7 | # | |
8 | # * Redistributions of source code must retain the above copyright | |
9 | # notice, this list of conditions and the following disclaimer. | |
10 | # * Redistributions in binary form must reproduce the above copyright | |
11 | # notice, this list of conditions and the following disclaimer in the | |
12 | # documentation and/or other materials provided with the distribution. | |
13 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
14 | # contributors may be used to endorse or promote products derived from | |
15 | # this software without specific prior written permission. | |
16 | # | |
17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
20 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
21 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
22 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
23 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
24 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
25 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
26 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
27 | # POSSIBILITY OF SUCH DAMAGE. | |
28 | ||
29 | ||
30 | # Author Tully Foote/tfoote@willowgarage.com | |
31 | ||
32 | import os | |
33 | import re | |
34 | import sys | |
35 | import subprocess | |
36 | import time | |
37 | ||
38 | import rospkg | |
39 | import threading | |
40 | ||
41 | if sys.hexversion > 0x03000000: #Python3 | |
42 | python3 = True | |
43 | else: | |
44 | python3 = False | |
45 | ||
46 | def _read_stdout(cmd): | |
47 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
48 | std_out, std_err = p.communicate() | |
49 | if python3: | |
50 | return std_out.decode() | |
51 | else: | |
52 | return std_out | |
53 | ||
54 | def num_cpus(): | |
55 | """ | |
56 | Detects the number of CPUs on a system. Cribbed from pp. | |
57 | """ | |
58 | # Linux, Unix and MacOS: | |
59 | if hasattr(os, "sysconf"): | |
60 | if "SC_NPROCESSORS_ONLN" in os.sysconf_names: | |
61 | # Linux & Unix: | |
62 | ncpus = os.sysconf("SC_NPROCESSORS_ONLN") | |
63 | if isinstance(ncpus, int) and ncpus > 0: | |
64 | return ncpus | |
65 | else: # OSX: | |
66 | return int(_read_stdout(["sysctl", "-n", "hw.ncpu"])) or 1 | |
67 | # Windows: | |
68 | if "NUMBER_OF_PROCESSORS" in os.environ: | |
69 | ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]); | |
70 | if ncpus > 0: | |
71 | return ncpus | |
72 | return 1 # Default | |
73 | ||
74 | #TODO: may no longer need this now that we've ported to rospkg | |
75 | class DependencyTracker: | |
76 | """ Track dependencies between packages. This is basically a | |
77 | caching way to call rospkg. It also will allow you to specifiy a | |
78 | range of packages over which to track dependencies. This is useful | |
79 | if you are only building a subset of the tree. For example with the | |
80 | --specified-only option. """ | |
81 | def __init__(self, valid_packages=None, rospack=None): | |
82 | """ | |
83 | @param valid_packages: defaults to rospack list | |
84 | """ | |
85 | if rospack is None: | |
86 | self.rospack = rospkg.RosPack() | |
87 | else: | |
88 | self.rospack = rospack | |
89 | if valid_packages is None: | |
90 | valid_packages = self.rospack.list() | |
91 | self.valid_packages = valid_packages | |
92 | self.deps_1 = {} | |
93 | self.deps = {} | |
94 | ||
95 | def get_deps_1(self, package): | |
96 | if not package in self.deps_1: | |
97 | self.deps_1[package] = [] | |
98 | try: | |
99 | potential_dependencies = self.rospack.get_depends(package, implicit=False) | |
100 | except rospkg.ResourceNotFound: | |
101 | potential_dependencies = [] | |
102 | for p in potential_dependencies: | |
103 | if p in self.valid_packages: | |
104 | self.deps_1[package].append(p) | |
105 | ||
106 | return self.deps_1[package] | |
107 | ||
108 | def get_deps(self, package): | |
109 | if not package in self.deps: | |
110 | self.deps[package] = [] | |
111 | try: | |
112 | potential_dependencies = self.rospack.get_depends(package) | |
113 | except rospkg.ResourceNotFound: | |
114 | potential_dependencies = [] | |
115 | ||
116 | for p in potential_dependencies: | |
117 | if p in self.valid_packages: | |
118 | self.deps[package].append(p) | |
119 | return self.deps[package] | |
120 | ||
121 | def load_fake_deps(self, deps, deps1): | |
122 | self.deps = deps | |
123 | self.deps_1 = deps1 | |
124 | return | |
125 | ||
126 | ||
127 | class CompileThread(threading.Thread): | |
128 | """ This is the class which is used as the thread for parallel | |
129 | builds. This class will query the build queue object for new | |
130 | commands and block on its calls until the build queue says that | |
131 | building is done. """ | |
132 | def __init__(self, name, build_queue, rosmakeall, argument = None): | |
133 | threading.Thread.__init__(self) | |
134 | self.build_queue = build_queue | |
135 | self.rosmakeall = rosmakeall | |
136 | self.argument = argument | |
137 | self.name = name | |
138 | self.logging_enabled = True | |
139 | ||
140 | def run(self): | |
141 | while not self.build_queue.is_done(): | |
142 | pkg = self.build_queue.get_valid_package() | |
143 | if not pkg: | |
144 | if self.build_queue.succeeded(): | |
145 | self.rosmakeall.printer.print_verbose("[ Build Completed Thread Exiting ]", thread_name=self.name); | |
146 | else: | |
147 | self.rosmakeall.printer.print_verbose("[ Build Terminated Thread Exiting ]", thread_name=self.name) | |
148 | break # no more packages must be done | |
149 | ||
150 | # update status after accepting build | |
151 | self.rosmakeall.update_status(self.argument , | |
152 | self.build_queue.get_started_threads(), | |
153 | self.build_queue.progress_str()) | |
154 | ||
155 | if self.argument: | |
156 | self.rosmakeall.printer.print_all ("Starting >>> %s [ make %s ]"%(pkg, self.argument), thread_name=self.name) | |
157 | else: | |
158 | self.rosmakeall.printer.print_all ("Starting >>> %s [ make ] "%pkg, thread_name=self.name) | |
159 | (result, result_string) = self.rosmakeall.build(pkg, self.argument, self.build_queue.robust_build) | |
160 | self.rosmakeall.printer.print_all("Finished <<< %s %s"%(pkg, result_string), thread_name= self.name) | |
161 | #print "Finished2" | |
162 | self.build_queue.return_built(pkg, result) | |
163 | #print "returned" | |
164 | if result or self.build_queue.robust_build: | |
165 | pass#print "result", result, "robust", self.build_queue.robust_build | |
166 | else: | |
167 | if result_string.find("[Interrupted]") != -1: | |
168 | self.rosmakeall.printer.print_all("Caught Interruption", thread_name=self.name) | |
169 | self.build_queue.stop() #todo move this logic into BuildQueue itself | |
170 | break # unnecessary since build_queue is done now while will quit | |
171 | self.rosmakeall.printer.print_all("Halting due to failure in package %s. \n[ rosmake ] Waiting for other threads to complete."%pkg) | |
172 | self.build_queue.stop() | |
173 | break # unnecessary since build_queue is done now, while will quit | |
174 | # update status after at end of build | |
175 | #print "updating status" | |
176 | self.rosmakeall.update_status(self.argument , | |
177 | self.build_queue.get_started_threads(), | |
178 | self.build_queue.progress_str()) | |
179 | #print "done built", len(self.build_queue.built), self.build_queue.built | |
180 | #print "failed", len(self.build_queue.failed), self.build_queue.failed | |
181 | #print "to_build", len(self.build_queue.to_build), self.build_queue.to_build | |
182 | #print "in progress", len(self.build_queue._started), self.build_queue._started | |
183 | ||
184 | #print "last update" | |
185 | # update status before ending thread | |
186 | self.rosmakeall.update_status(self.argument , | |
187 | self.build_queue.get_started_threads(), | |
188 | self.build_queue.progress_str()) | |
189 | #print "thread finished" | |
190 | ||
191 | class BuildQueue: | |
192 | """ This class provides a thread safe build queue. Which will do | |
193 | the sequencing for many CompileThreads. """ | |
194 | def __init__(self, package_list, dependency_tracker, robust_build = False): | |
195 | self._total_pkgs = len(package_list) | |
196 | self.dependency_tracker = dependency_tracker | |
197 | self.to_build = package_list[:] # do a copy not a reference | |
198 | self.built = [] | |
199 | self.failed = [] | |
200 | self.condition = threading.Condition() | |
201 | self._done = False | |
202 | self.robust_build = robust_build | |
203 | self._started = {} | |
204 | self._hack_end_counter = 0 | |
205 | ||
206 | def progress_str(self): | |
207 | return "[ %d Active %d/%d Complete ]"%(len(self._started), len(self.built), self._total_pkgs) | |
208 | ||
209 | def get_started_threads(self): #TODO sort this other than hash order | |
210 | return self._started.copy() | |
211 | ||
212 | def is_completed(self): | |
213 | """Return if the build queue has been completed """ | |
214 | return len(self.built)+ len(self.failed) == self._total_pkgs | |
215 | ||
216 | def is_done(self): | |
217 | """Return if the build queue has been completed """ | |
218 | return self.is_completed() or self._done # finished or halted | |
219 | ||
220 | def succeeded(self): | |
221 | """ Return whether the build queue has completed all packages successfully. """ | |
222 | return len(self.built) == self._total_pkgs #flag that we're finished | |
223 | ||
224 | def stop(self): | |
225 | """ Stop the build queue, including waking all blocking | |
226 | threads. It will not stop in flight builds.""" | |
227 | self._done = True | |
228 | with self.condition: | |
229 | self.condition.notifyAll() # wake any blocking threads | |
230 | ||
231 | def return_built(self, package, successful=True): # mark that a package is built | |
232 | """ The thread which completes a package marks it as done with | |
233 | this method.""" | |
234 | with self.condition: | |
235 | if successful: | |
236 | self.built.append(package) | |
237 | else: | |
238 | self.failed.append(package) | |
239 | if package in self._started.keys(): | |
240 | self._started.pop(package) | |
241 | else: | |
242 | pass #used early on print "\n\n\nERROR THIS SHOULDN't RETURN %s\n\n\n"%package | |
243 | if self.is_completed(): | |
244 | self._done = True | |
245 | self.condition.notifyAll() #wake up any waiting threads | |
246 | ||
247 | def get_valid_package(self): # blocking call to get a package to build returns none if done | |
248 | """ This is a blocking call which will return a package which has | |
249 | all dependencies met. If interrupted or done it will return | |
250 | None""" | |
251 | with self.condition: | |
252 | while (not self.is_done() and len(self.to_build) > 0): | |
253 | for p in self.to_build: | |
254 | dependencies_met = True | |
255 | for d in self.dependency_tracker.get_deps(p): | |
256 | if d not in self.built and not (self.robust_build and d in self.failed): | |
257 | dependencies_met = False | |
258 | #print "Dependency %s not met for %s"%(d, p) | |
259 | break | |
260 | if dependencies_met: # all dependencies met | |
261 | self.to_build.remove(p) | |
262 | self._started[p] = time.time() | |
263 | self._hack_end_counter = 0 #reset end counter if success | |
264 | return p # break out and return package if found | |
265 | elif len(self._started) == 0 and self._hack_end_counter > 2: | |
266 | # we're hung with broken dependencies | |
267 | return None | |
268 | #print "TTGTTTTHTHT Waiting on condition" | |
269 | self.condition.wait(1.0) # failed to find a package wait for a notify before looping | |
270 | ||
271 | self._hack_end_counter += 1 # if we're here too often we will quit | |
272 | if self.is_done(): | |
273 | break | |
274 | ||
275 | return None |
0 | #!/usr/bin/env python | |
1 | # Copyright (c) 2009, Willow Garage, Inc. | |
2 | # All rights reserved. | |
3 | # | |
4 | # Redistribution and use in source and binary forms, with or without | |
5 | # modification, are permitted provided that the following conditions are met: | |
6 | # | |
7 | # * Redistributions of source code must retain the above copyright | |
8 | # notice, this list of conditions and the following disclaimer. | |
9 | # * Redistributions in binary form must reproduce the above copyright | |
10 | # notice, this list of conditions and the following disclaimer in the | |
11 | # documentation and/or other materials provided with the distribution. | |
12 | # * Neither the name of the Willow Garage, Inc. nor the names of its | |
13 | # contributors may be used to endorse or promote products derived from | |
14 | # this software without specific prior written permission. | |
15 | # | |
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
19 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
20 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
21 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
22 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
23 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
24 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
25 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
26 | # POSSIBILITY OF SUCH DAMAGE. | |
27 | ||
28 | import sys | |
29 | import unittest | |
30 | ||
31 | from rosmake import parallel_build | |
32 | ||
33 | class TestDependencyTracker(unittest.TestCase): | |
34 | def setUp(self): | |
35 | self.deps = {} | |
36 | self.deps1 = {} | |
37 | self.deps["a"] = [ "b", "c", "d","e"] | |
38 | self.deps1["a"] = ["b"] | |
39 | self.deps["b"] = ["c"] | |
40 | self.deps1["b"] = ["c"] | |
41 | self.deps["d"] = ["c", "e"] | |
42 | self.deps1["d"] = ["c", "e"] | |
43 | self.dt = parallel_build.DependencyTracker() | |
44 | self.dt.load_fake_deps(self.deps, self.deps1) | |
45 | ||
46 | ||
47 | def test_deps_1(self): | |
48 | self.assertEquals(self.deps1["a"], self.dt.get_deps_1("a")) | |
49 | self.assertEquals(self.deps1["b"], self.dt.get_deps_1("b")) | |
50 | self.assertEquals(self.deps1["d"], self.dt.get_deps_1("d")) | |
51 | ||
52 | def test_deps(self): | |
53 | self.assertEquals(self.deps["a"], self.dt.get_deps("a")) | |
54 | self.assertEquals(self.deps["b"], self.dt.get_deps("b")) | |
55 | self.assertEquals(self.deps["d"], self.dt.get_deps("d")) | |
56 | ||
57 | def test_not_package(self): | |
58 | self.assertEquals([], self.dt.get_deps("This is not a valid package name")) | |
59 | self.assertEquals([], self.dt.get_deps_1("This is not a valid package name")) | |
60 | ||
61 | ||
62 | class TestBuildQueue(unittest.TestCase): | |
63 | ||
64 | def setUp(self): | |
65 | deps = {} | |
66 | deps1 = {} | |
67 | deps1["a"] = ["b"] | |
68 | deps["a"] = ["b", "c", "d", "e", "f"] | |
69 | deps1["b"] = ["c"] | |
70 | deps["b"] = ["c", "d", "e", "f"] | |
71 | deps1["c"] = ["d"] | |
72 | deps["c"] = ["d", "e", "f"] | |
73 | deps1["d"] = ["e"] | |
74 | deps["d"] = ["e", "f"] | |
75 | deps["e"] = ["f"] | |
76 | deps1["e"] = ["f"] | |
77 | deps["f"] = [] | |
78 | deps1["f"] = [] | |
79 | ||
80 | self.serial_tracker = parallel_build.DependencyTracker() | |
81 | self.serial_tracker.load_fake_deps(deps, deps1) | |
82 | ||
83 | deps = {} | |
84 | deps1 = {} | |
85 | deps["a"] = ["b", "c", "d", "e", "f"] | |
86 | deps1["a"] = ["b", "c", "d", "e", "f"] | |
87 | deps["b"] = [] | |
88 | deps1["b"] = [] | |
89 | deps["c"] = [] | |
90 | deps1["c"] = [] | |
91 | deps["d"] = [] | |
92 | deps1["d"] = [] | |
93 | deps["e"] = [] | |
94 | deps1["e"] = [] | |
95 | deps["f"] = [] | |
96 | deps1["f"] = [] | |
97 | ||
98 | self.parallel_tracker = parallel_build.DependencyTracker() | |
99 | self.parallel_tracker.load_fake_deps(deps, deps1) | |
100 | ||
101 | # full queue | |
102 | def test_full_build(self): | |
103 | bq = parallel_build.BuildQueue(["a", "b", "c", "d", "e", "f"], self.serial_tracker) | |
104 | self.assertFalse(bq.is_done()) | |
105 | self.assertFalse(bq.succeeded()) | |
106 | ||
107 | self.assertEqual("f", bq.get_valid_package()) | |
108 | self.assertEqual(0, len(bq.built)) | |
109 | bq.return_built("f") | |
110 | self.assertEqual(1, len(bq.built)) | |
111 | self.assertFalse(bq.is_done()) | |
112 | self.assertFalse(bq.succeeded()) | |
113 | ||
114 | self.assertEqual("e", bq.get_valid_package()) | |
115 | bq.return_built("e") | |
116 | self.assertEqual(2, len(bq.built)) | |
117 | self.assertFalse(bq.is_done()) | |
118 | self.assertFalse(bq.succeeded()) | |
119 | ||
120 | self.assertEqual("d", bq.get_valid_package()) | |
121 | bq.return_built("d") | |
122 | self.assertEqual(3, len(bq.built)) | |
123 | self.assertFalse(bq.is_done()) | |
124 | self.assertFalse(bq.succeeded()) | |
125 | ||
126 | self.assertEqual("c", bq.get_valid_package()) | |
127 | bq.return_built("c") | |
128 | self.assertEqual(4, len(bq.built)) | |
129 | self.assertFalse(bq.is_done()) | |
130 | self.assertFalse(bq.succeeded()) | |
131 | ||
132 | self.assertEqual("b", bq.get_valid_package()) | |
133 | bq.return_built("b") | |
134 | self.assertEqual(5, len(bq.built)) | |
135 | self.assertFalse(bq.is_done()) | |
136 | self.assertFalse(bq.succeeded()) | |
137 | ||
138 | self.assertEqual("a", bq.get_valid_package()) | |
139 | self.assertFalse(bq.is_done()) | |
140 | self.assertFalse(bq.succeeded()) | |
141 | bq.return_built("a") | |
142 | self.assertEqual(6, len(bq.built)) | |
143 | self.assertTrue (bq.is_done()) | |
144 | self.assertTrue (bq.succeeded()) | |
145 | ||
146 | ||
147 | # partial build | |
148 | def test_partial_build(self): | |
149 | bq = parallel_build.BuildQueue(["d", "e", "f"], self.serial_tracker) | |
150 | self.assertFalse(bq.is_done()) | |
151 | self.assertFalse(bq.succeeded()) | |
152 | ||
153 | self.assertEqual("f", bq.get_valid_package()) | |
154 | self.assertEqual(0, len(bq.built)) | |
155 | bq.return_built("f") | |
156 | self.assertEqual(1, len(bq.built)) | |
157 | self.assertFalse(bq.is_done()) | |
158 | self.assertFalse(bq.succeeded()) | |
159 | ||
160 | self.assertEqual("e", bq.get_valid_package()) | |
161 | bq.return_built("e") | |
162 | self.assertEqual(2, len(bq.built)) | |
163 | self.assertFalse(bq.is_done()) | |
164 | self.assertFalse(bq.succeeded()) | |
165 | ||
166 | self.assertEqual("d", bq.get_valid_package()) | |
167 | self.assertFalse(bq.is_done()) | |
168 | self.assertFalse(bq.succeeded()) | |
169 | bq.return_built("d") | |
170 | self.assertEqual(3, len(bq.built)) | |
171 | self.assertTrue(bq.is_done()) | |
172 | self.assertTrue(bq.succeeded()) | |
173 | ||
174 | ||
175 | # abort early | |
176 | def test_abort_early(self): | |
177 | bq = parallel_build.BuildQueue(["a", "b", "c", "d", "e", "f"], self.serial_tracker) | |
178 | self.assertFalse(bq.is_done()) | |
179 | self.assertFalse(bq.succeeded()) | |
180 | self.assertEqual(0, len(bq.built)) | |
181 | ||
182 | self.assertEqual("f", bq.get_valid_package()) | |
183 | bq.return_built("f") | |
184 | self.assertEqual(1, len(bq.built)) | |
185 | self.assertFalse(bq.is_done()) | |
186 | self.assertFalse(bq.succeeded()) | |
187 | ||
188 | self.assertEqual("e", bq.get_valid_package()) | |
189 | bq.return_built("e") | |
190 | self.assertEqual(2, len(bq.built)) | |
191 | self.assertFalse(bq.is_done()) | |
192 | self.assertFalse(bq.succeeded()) | |
193 | ||
194 | self.assertEqual("d", bq.get_valid_package()) | |
195 | bq.return_built("d") | |
196 | self.assertEqual(3, len(bq.built)) | |
197 | self.assertFalse(bq.is_done()) | |
198 | self.assertFalse(bq.succeeded()) | |
199 | ||
200 | bq.stop() | |
201 | self.assertTrue(bq.is_done()) | |
202 | self.assertFalse(bq.succeeded()) | |
203 | ||
204 | self.assertEqual(None, bq.get_valid_package()) | |
205 | ||
206 | # many parallel | |
207 | def test_parallel_build(self): | |
208 | bq = parallel_build.BuildQueue(["a", "b", "c", "d", "e", "f"], self.parallel_tracker) | |
209 | self.assertFalse(bq.is_done()) | |
210 | self.assertFalse(bq.succeeded()) | |
211 | ||
212 | dependents = ["b", "c", "d", "e", "f"] | |
213 | count = 0 | |
214 | total = 6 | |
215 | while len(dependents) > 0: | |
216 | result= bq.get_valid_package() | |
217 | done = len(bq.built) | |
218 | pkgs = bq._total_pkgs | |
219 | self.assertTrue(result in dependents) | |
220 | #print result, done, pkgs | |
221 | dependents.remove(result) | |
222 | self.assertEqual(count, done) | |
223 | self.assertEqual(total, pkgs) | |
224 | self.assertFalse(bq.is_done()) | |
225 | self.assertFalse(bq.succeeded()) | |
226 | bq.return_built(result) | |
227 | count = count + 1 | |
228 | self.assertFalse(bq.is_done()) | |
229 | self.assertFalse(bq.succeeded()) | |
230 | ||
231 | ||
232 | self.assertEqual("a", bq.get_valid_package()) | |
233 | self.assertFalse(bq.is_done()) | |
234 | self.assertFalse(bq.succeeded()) | |
235 | bq.return_built("a") | |
236 | self.assertTrue (bq.is_done()) | |
237 | self.assertTrue (bq.succeeded()) | |
238 | ||
239 | ||
240 | # stalled(future) |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2009, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | ||
32 | import os | |
33 | import sys | |
34 | import subprocess | |
35 | ||
36 | def test_Rosmake_commandline_usage(): | |
37 | assert 0 == subprocess.call(["rosmake", "-h"]) |
0 | cmake_minimum_required(VERSION 2.8) | |
1 | project(rosunit) | |
2 | find_package(catkin REQUIRED) | |
3 | catkin_package() | |
4 | catkin_package_export(CFG_EXTRAS ${PROJECT_NAME}-extras.cmake) | |
5 | ||
6 | catkin_python_setup() | |
7 | ||
8 | install(PROGRAMS | |
9 | scripts/test_results_dir.py | |
10 | scripts/summarize_results.py | |
11 | scripts/rosunit | |
12 | scripts/pycoverage_to_html.py | |
13 | scripts/clean_junit_xml.py | |
14 | scripts/check_test_ran.py | |
15 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/scripts) | |
16 | ||
17 | catkin_add_nosetests(test) |
0 | macro(rosunit_initialize_tests) | |
1 | if (@BUILDSPACE@) | |
2 | find_program_required(ROSUNIT_EXE rosunit | |
3 | PATHS @PROJECT_SOURCE_DIR@/scripts | |
4 | NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) | |
5 | else() | |
6 | find_program_required(ROSUNIT_EXE rosunit | |
7 | PATHS @CMAKE_INSTALL_PREFIX@/bin | |
8 | NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) | |
9 | endif() | |
10 | endmacro() | |
11 | ||
12 | rosunit_initialize_tests() | |
13 | ||
14 | function(add_pyunit file) | |
15 | ||
16 | message(WARNING "add_pyunit() is deprecated. For Python tests, use add_nostests() instead.") | |
17 | ||
18 | # Look for optional TIMEOUT argument, #2645 | |
19 | parse_arguments(_pyunit "TIMEOUT;WORKING_DIRECTORY" "" ${ARGN}) | |
20 | if(NOT _pyunit_TIMEOUT) | |
21 | set(_pyunit_TIMEOUT 60.0) | |
22 | endif() | |
23 | ||
24 | # Check that the file exists, #1621 | |
25 | set(_file_name _file_name-NOTFOUND) | |
26 | if(IS_ABSOLUTE ${file}) | |
27 | set(_file_name ${file}) | |
28 | else() | |
29 | find_file(_file_name ${file} | |
30 | PATHS ${CMAKE_CURRENT_SOURCE_DIR} | |
31 | NO_DEFAULT_PATH | |
32 | NO_CMAKE_FIND_ROOT_PATH) # for cross-compilation. thanks jeremy. | |
33 | if(NOT _file_name) | |
34 | message(FATAL_ERROR "Can't find pyunit file \"${file}\"") | |
35 | endif() | |
36 | endif() | |
37 | ||
38 | # We look for ROS_TEST_COVERAGE=1 | |
39 | # to indicate that coverage reports are being requested. | |
40 | if("$ENV{ROS_TEST_COVERAGE}" STREQUAL "1") | |
41 | set(_covarg "--cov") | |
42 | else() | |
43 | set(_covarg) | |
44 | endif() | |
45 | ||
46 | # Create a legal test name, in case the target name has slashes in it | |
47 | string(REPLACE "/" "_" _testname ${file}) | |
48 | # We use rostest to call the executable to get process control, #1629 | |
49 | set(cmd "${ROSUNIT_EXE} --name=${_testname} --time-limit=${_pyunit_TIMEOUT} --package=${PROJECT_NAME} -- ${_file_name} ${_covarg}") | |
50 | catkin_run_tests_target("rosunit" ${_testname} "rosunit-${_testname}.xml" COMMAND ${cmd} WORKING_DIRECTORY ${_pyunit_WORKING_DIRECTORY}) | |
51 | endfunction() | |
52 |
0 | <package> | |
1 | <description brief="Unit testing for ROS"> | |
2 | ||
3 | Unit-testing package for ROS. This is a lower-level library for rostest and handles unit tests, whereas rostest handles integration tests. | |
4 | ||
5 | </description> | |
6 | <author>Ken Conley</author> | |
7 | <license>BSD</license> | |
8 | <review status="Doc reviewed" notes="2011/01/12, REP 100"/> | |
9 | <url>http://ros.org/wiki/rosunit</url> | |
10 | <export> | |
11 | <rosdoc config="rosdoc.yaml" /> | |
12 | </export> | |
13 | <rosdep name="gtest"/> | |
14 | ||
15 | </package> | |
16 | ||
17 |
0 | <package> | |
1 | <name>rosunit</name> | |
2 | <version>1.9.11</version> | |
3 | <description> | |
4 | Unit-testing package for ROS. This is a lower-level library for rostest and handles unit tests, whereas rostest handles integration tests. | |
5 | </description> | |
6 | <maintainer email="dthomas@willowgarage.com">Dirk Thomas</maintainer> | |
7 | <license>BSD</license> | |
8 | ||
9 | <url type="website">http://ros.org/wiki/rosunit</url> | |
10 | <url type="bugtracker">https://code.ros.org/trac/ros/query?status=assigned&status=new&status=reopened&component=ros&order=priority</url> | |
11 | <url type="repository">https://code.ros.org/svn/ros/stacks/ros/trunk/tools/rosunit</url> | |
12 | ||
13 | <author>Ken Conley</author> | |
14 | ||
15 | <build_depend>catkin</build_depend> | |
16 | ||
17 | <export> | |
18 | <rosdoc config="rosdoc.yaml" /> | |
19 | </export> | |
20 | </package> |
0 | - builder: epydoc⏎ |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | ||
35 | """ | |
36 | Writes a test failure out to test file if it doesn't exist. | |
37 | """ | |
38 | ||
39 | from __future__ import print_function | |
40 | NAME="check_test_ran.py" | |
41 | ||
42 | import os | |
43 | import sys | |
44 | ||
45 | import rospkg | |
46 | import rosunit | |
47 | ||
48 | def usage(): | |
49 | print("""Usage: | |
50 | \t%s test-file.xml | |
51 | or | |
52 | \t%s --rostest pkg-name test-file.xml | |
53 | """%(NAME, NAME), file=sys.stderr) | |
54 | print(sys.argv) | |
55 | sys.exit(getattr(os, 'EX_USAGE', 1)) | |
56 | ||
57 | def check_main(): | |
58 | if len(sys.argv) < 2: | |
59 | usage() | |
60 | if '--rostest' in sys.argv[1:]: | |
61 | if len(sys.argv) != 4: | |
62 | usage() | |
63 | test_pkg, test_file = [a for a in sys.argv[1:] if a != '--rostest'] | |
64 | # this logic derives the output filename that rostest uses | |
65 | ||
66 | r = rospkg.RosPack() | |
67 | pkg_name = rospkg.get_package_name(test_file) | |
68 | pkg_dir = r.get_path(pkg_name) | |
69 | ||
70 | # compute test name for friendlier reporting | |
71 | outname = rosunit.rostest_name_from_path(pkg_dir, test_file) | |
72 | ||
73 | test_file = rosunit.xml_results_file(test_pkg, outname, is_rostest=True) | |
74 | else: | |
75 | if len(sys.argv) != 2: | |
76 | usage() | |
77 | test_file = sys.argv[1] | |
78 | ||
79 | print("Checking for test results in %s"%test_file) | |
80 | ||
81 | if not os.path.exists(test_file): | |
82 | if not os.path.exists(os.path.dirname(test_file)): | |
83 | os.makedirs(os.path.dirname(test_file)) | |
84 | ||
85 | print("Cannot find results, writing failure results to", test_file) | |
86 | ||
87 | with open(test_file, 'w') as f: | |
88 | test_name = os.path.basename(test_file) | |
89 | d = {'test': test_name, 'test_file': test_file } | |
90 | f.write("""<?xml version="1.0" encoding="UTF-8"?> | |
91 | <testsuite tests="1" failures="1" time="1" errors="0" name="%(test)s"> | |
92 | <testcase name="test_ran" status="run" time="1" classname="Results"> | |
93 | <failure message="Unable to find test results for %(test)s, test did not run.\nExpected results in %(test_file)s" type=""/> | |
94 | </testcase> | |
95 | </testsuite>"""%d) | |
96 | ||
97 | if __name__ == '__main__': | |
98 | check_main() |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2009, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | ||
35 | from __future__ import print_function | |
36 | ||
37 | """ | |
38 | clean_junit_xml.py is a simple script that takes all the xml-formatted | |
39 | Ant JUnit XML test output in test_results and aggregates them into | |
40 | test_results/_hudson. In this process, it strips any characters that | |
41 | tend to cause Hudson trouble. | |
42 | """ | |
43 | ||
44 | PKG = 'rosunit' | |
45 | ||
46 | import os | |
47 | import sys | |
48 | ||
49 | import rospkg | |
50 | import rosunit.junitxml as junitxml | |
51 | ||
52 | def prepare_dirs(output_dir_name): | |
53 | test_results_dir = rospkg.get_test_results_dir() | |
54 | print("will read test results from", test_results_dir) | |
55 | output_dir = os.path.join(test_results_dir, output_dir_name) | |
56 | if not os.path.exists(output_dir): | |
57 | print("creating directory", output_dir) | |
58 | os.makedirs(output_dir) | |
59 | return test_results_dir, output_dir | |
60 | ||
61 | def clean_results(test_results_dir, output_dir, filter): | |
62 | """ | |
63 | Read results from test_results_dir and write them into output_dir. | |
64 | """ | |
65 | for d in os.listdir(test_results_dir): | |
66 | if filter and d in filter: | |
67 | continue | |
68 | print("looking at", d) | |
69 | test_dir = os.path.join(test_results_dir, d) | |
70 | if not os.path.isdir(test_dir): | |
71 | continue | |
72 | base_test_name = os.path.basename(test_dir) | |
73 | # for each test result that a package generated, read it, then | |
74 | # rewrite it to our output directory. This will invoke our | |
75 | # cleaning rules on the XML that protect the result from Hudson | |
76 | # issues. | |
77 | for file in os.listdir(test_dir): | |
78 | if file.endswith('.xml'): | |
79 | test_name = base_test_name + '.' + file[:-4] | |
80 | file = os.path.join(test_dir, file) | |
81 | try: | |
82 | result = junitxml.read(file, test_name) | |
83 | output_path = os.path.join(output_dir, "%s.xml"%test_name) | |
84 | with open(output_path, 'w') as f: | |
85 | print("re-writing", output_path) | |
86 | f.write(result.xml().encode('utf-8')) | |
87 | except Exception as e: | |
88 | sys.stderr.write("ignoring [%s]: %s\n"%(file, e)) | |
89 | ||
90 | def main(): | |
91 | ||
92 | print("[clean_junit_xml]: STARTING") | |
93 | ||
94 | output_dir_name = '_hudson' | |
95 | test_results_dir, output_dir = prepare_dirs(output_dir_name) | |
96 | ||
97 | print("[clean_junit_xml]: writing aggregated test results to %s"%output_dir) | |
98 | ||
99 | clean_results(test_results_dir, output_dir, [output_dir_name, '.svn']) | |
100 | ||
101 | print("[clean_junit_xml]: FINISHED") | |
102 | ||
103 | if __name__ == '__main__': | |
104 | main() | |
105 |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | ||
35 | """ | |
36 | Generate HTML reports from coverage.py (aka python-coverage). This is | |
37 | currently a no-frills backend tool. | |
38 | """ | |
39 | ||
40 | import sys | |
41 | import roslib | |
42 | ||
43 | try: | |
44 | import coverage | |
45 | except ImportError, e: | |
46 | sys.stderr.write("ERROR: cannot import python-coverage, coverage report will not run.\nTo install coverage, run 'easy_install coverage'\n") | |
47 | sys.exit(1) | |
48 | ||
49 | def coverage_html(): | |
50 | import os.path | |
51 | if not os.path.isfile('.coverage-modules'): | |
52 | sys.stderr.write("No .coverage-modules file; nothing to do\n") | |
53 | return | |
54 | ||
55 | with open('.coverage-modules','r') as f: | |
56 | modules = [x for x in f.read().split('\n') if x.strip()] | |
57 | ||
58 | cov = coverage.coverage() | |
59 | cov.load() | |
60 | ||
61 | # import everything | |
62 | for m in modules: | |
63 | try: | |
64 | base = m.split('.')[0] | |
65 | roslib.load_manifest(base) | |
66 | __import__(m) | |
67 | except: | |
68 | sys.stderr.write("WARN: cannot import %s\n"%(base)) | |
69 | ||
70 | modlist = '\n'.join([" * %s"%m for m in modules]) | |
71 | sys.stdout.write("Generating for\n%s\n"%(modlist)) | |
72 | ||
73 | # load the module instances to pass to coverage so it can generate annotation html reports | |
74 | mods = [] | |
75 | ||
76 | # TODO: rewrite, buggy | |
77 | for m in modules: | |
78 | mods.extend([v for v in sys.modules.values() if v and v.__name__.startswith(m) and not v in mods]) | |
79 | ||
80 | # dump the output to covhtml directory | |
81 | cov.html_report(mods, directory="covhtml") | |
82 | ||
83 | if __name__ == '__main__': | |
84 | coverage_html() |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2010, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | ||
33 | import rosunit.rosunit_main | |
34 | rosunit.rosunit_main.rosunitmain() |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | ||
35 | """ | |
36 | Prints summary of aggregated test results to stdout. This is useful | |
37 | when running several tests across a package. | |
38 | """ | |
39 | ||
40 | import os | |
41 | import sys | |
42 | import cStringIO | |
43 | ||
44 | import rospkg | |
45 | import rosunit.junitxml as junitxml | |
46 | ||
47 | def create_summary(result, packages): | |
48 | buff = cStringIO.StringIO() | |
49 | ||
50 | buff.write('-'*80+'\n') | |
51 | buff.write('\033[1m[AGGREGATED TEST RESULTS SUMMARY]\033[0m\n\n') | |
52 | ||
53 | errors_failures = [r for r in result.test_case_results if r.errors or r.failures] | |
54 | if errors_failures: | |
55 | buff.write('ERRORS/FAILURES:\n') | |
56 | for tc_result in errors_failures: | |
57 | buff.write(tc_result.description) | |
58 | ||
59 | buff.write("PACKAGES: \n%s\n\n"%'\n'.join([" * %s"%p for p in packages])) | |
60 | ||
61 | buff.write('\nSUMMARY\n') | |
62 | if (result.num_errors + result.num_failures) == 0: | |
63 | buff.write("\033[32m * RESULT: SUCCESS\033[0m\n") | |
64 | else: | |
65 | buff.write("\033[1;31m * RESULT: FAIL\033[0m\n") | |
66 | ||
67 | # TODO: still some issues with the numbers adding up if tests fail to launch | |
68 | ||
69 | # number of errors from the inner tests, plus add in count for tests | |
70 | # that didn't run properly ('result' object). | |
71 | buff.write(" * TESTS: %s\n"%result.num_tests) | |
72 | if result.num_errors: | |
73 | buff.write("\033[1;31m * ERRORS: %s\033[0m\n"%result.num_errors) | |
74 | else: | |
75 | buff.write(" * ERRORS: 0\n") | |
76 | if result.num_failures: | |
77 | buff.write("\033[1;31m * FAILURES: %s\033[0m\n"%result.num_failures) | |
78 | else: | |
79 | buff.write(" * FAILURES: 0\n") | |
80 | return buff.getvalue() | |
81 | ||
82 | def main(): | |
83 | from optparse import OptionParser | |
84 | parser = OptionParser(usage="usage: summarize_results.py [options] package") | |
85 | parser.add_option("--nodeps", | |
86 | dest="no_deps", default=False, | |
87 | action="store_true", | |
88 | help="don't compute test results for the specified package only") | |
89 | (options, args) = parser.parse_args() | |
90 | if len(args) != 1: | |
91 | parser.error("Only one package may be specified") | |
92 | ||
93 | package = args[0] | |
94 | r = rospkg.RosPack() | |
95 | if options.no_deps: | |
96 | packages = [package] | |
97 | else: | |
98 | packages = [package] + r.get_depends_on(package, implicit=True) | |
99 | packages = [p for p in packages if p] | |
100 | ||
101 | result = junitxml.read_all(packages) | |
102 | print create_summary(result, packages) | |
103 | if result.num_errors or result.num_failures: | |
104 | sys.exit(1) | |
105 | ||
106 | if __name__ == '__main__': | |
107 | main() |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2009, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | ||
35 | """ | |
36 | test_results_dir.py simply prints the directory that rosunit/rostest | |
37 | results are stored in. | |
38 | """ | |
39 | ||
40 | from __future__ import print_function | |
41 | import rospkg | |
42 | print(rospkg.get_test_results_dir()) |
0 | #!/usr/bin/env python | |
1 | ||
2 | from distutils.core import setup | |
3 | from catkin_pkg.package import parse_package_for_distutils | |
4 | ||
5 | d = parse_package_for_distutils() | |
6 | d['packages'] = ['rosunit'] | |
7 | d['package_dir'] = {'': 'src'} | |
8 | d['scripts'] = ['scripts/rosunit'] | |
9 | d['install_requires'] = ['rospkg'] | |
10 | ||
11 | setup(**d) |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | """ | |
35 | Library and tools support unit testing in ROS. | |
36 | """ | |
37 | ||
38 | # NOTE: while this makes some forward references to conventions used | |
39 | # in rostest, it does not use rostest itself. | |
40 | import os | |
41 | ||
42 | from .core import xml_results_file, rostest_name_from_path, create_xml_runner, XML_OUTPUT_FLAG | |
43 | from . import junitxml | |
44 | from .baretest import print_runner_summary, print_unittest_summary | |
45 | from .pyunit import unitrun | |
46 | ||
47 | __version__ = '1.7.0' |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2010, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | """ | |
35 | rostest implementation of running bare (gtest-compatible) unit test | |
36 | executables. These do not run in a ROS environment. | |
37 | """ | |
38 | ||
39 | import os | |
40 | import cStringIO | |
41 | import unittest | |
42 | import time | |
43 | import signal | |
44 | import subprocess | |
45 | import traceback | |
46 | ||
47 | import rospkg | |
48 | ||
49 | from .core import xml_results_file, rostest_name_from_path, create_xml_runner, printlog, printerrlog, printlog_bold | |
50 | ||
51 | from . import pmon | |
52 | from . import junitxml | |
53 | ||
54 | BARE_TIME_LIMIT = 60. | |
55 | TIMEOUT_SIGINT = 15.0 #seconds | |
56 | TIMEOUT_SIGTERM = 2.0 #seconds | |
57 | ||
58 | class TestTimeoutException(Exception): pass | |
59 | ||
60 | class BareTestCase(unittest.TestCase): | |
61 | ||
62 | def __init__(self, exe, args, retry=0, time_limit=None, test_name=None, text_mode=False, package_name=None): | |
63 | """ | |
64 | @param exe: path to executable to run | |
65 | @type exe: str | |
66 | @param args: arguments to exe | |
67 | @type args: [str] | |
68 | @type retry: int | |
69 | @param time_limit: (optional) time limit for test. Defaults to BARE_TIME_LIMIT. | |
70 | @type time_limit: float | |
71 | @param test_name: (optional) override automatically generated test name | |
72 | @type test_name: str | |
73 | @param package_name: (optional) override automatically inferred package name | |
74 | @type package_name: str | |
75 | """ | |
76 | super(BareTestCase, self).__init__() | |
77 | self.text_mode = text_mode | |
78 | if package_name: | |
79 | self.package = package_name | |
80 | else: | |
81 | self.package = rospkg.get_package_name(exe) | |
82 | self.exe = os.path.abspath(exe) | |
83 | if test_name is None: | |
84 | self.test_name = os.path.basename(exe) | |
85 | else: | |
86 | self.test_name = test_name | |
87 | ||
88 | # invoke pyunit tests with python executable | |
89 | if self.exe.endswith('.py'): | |
90 | self.args = ['python', self.exe] + args | |
91 | else: | |
92 | self.args = [self.exe] + args | |
93 | if text_mode: | |
94 | self.args = self.args + ['--text'] | |
95 | ||
96 | self.retry = retry | |
97 | self.time_limit = time_limit or BARE_TIME_LIMIT | |
98 | self.pmon = None | |
99 | self.results = junitxml.Result(self.test_name) | |
100 | ||
101 | def setUp(self): | |
102 | self.pmon = pmon.start_process_monitor() | |
103 | ||
104 | def tearDown(self): | |
105 | if self.pmon is not None: | |
106 | pmon.shutdown_process_monitor(self.pmon) | |
107 | self.pmon = None | |
108 | ||
109 | def runTest(self): | |
110 | self.failIf(self.package is None, "unable to determine package of executable") | |
111 | ||
112 | done = False | |
113 | while not done: | |
114 | test_name = self.test_name | |
115 | ||
116 | printlog("Running test [%s]", test_name) | |
117 | ||
118 | #setup the test | |
119 | # - we pass in the output test_file name so we can scrape it | |
120 | test_file = xml_results_file(self.package, test_name, False) | |
121 | if os.path.exists(test_file): | |
122 | printlog("removing previous test results file [%s]", test_file) | |
123 | os.remove(test_file) | |
124 | ||
125 | self.args.append('--gtest_output=xml:%s'%test_file) | |
126 | ||
127 | # run the test, blocks until completion | |
128 | printlog("running test %s"%test_name) | |
129 | timeout_failure = False | |
130 | ||
131 | run_id = None | |
132 | #TODO: really need different, non-node version of LocalProcess instead of these extra args | |
133 | process = LocalProcess(run_id, self.package, self.test_name, self.args, os.environ, False, cwd='cwd', is_node=False) | |
134 | ||
135 | pm = self.pmon | |
136 | pm.register(process) | |
137 | success = process.start() | |
138 | self.assert_(success, "test failed to start") | |
139 | ||
140 | #poll until test terminates or alloted time exceed | |
141 | timeout_t = time.time() + self.time_limit | |
142 | try: | |
143 | while process.is_alive(): | |
144 | #test fails on timeout | |
145 | if time.time() > timeout_t: | |
146 | raise TestTimeoutException("test max time allotted") | |
147 | time.sleep(0.1) | |
148 | ||
149 | except TestTimeoutException, e: | |
150 | if self.retry: | |
151 | timeout_failure = True | |
152 | else: | |
153 | raise | |
154 | ||
155 | if not timeout_failure: | |
156 | printlog("test [%s] finished"%test_name) | |
157 | else: | |
158 | printerrlog("test [%s] timed out"%test_name) | |
159 | ||
160 | ||
161 | if self.text_mode: | |
162 | results = self.results | |
163 | elif not self.text_mode: | |
164 | # load in test_file | |
165 | if not timeout_failure: | |
166 | self.assert_(os.path.isfile(test_file), "test [%s] did not generate test results"%test_name) | |
167 | printlog("test [%s] results are in [%s]", test_name, test_file) | |
168 | results = junitxml.read(test_file, test_name) | |
169 | test_fail = results.num_errors or results.num_failures | |
170 | else: | |
171 | test_fail = True | |
172 | ||
173 | if self.retry > 0 and test_fail: | |
174 | self.retry -= 1 | |
175 | printlog("test [%s] failed, retrying. Retries left: %s"%(test_name, self.retry)) | |
176 | else: | |
177 | done = True | |
178 | self.results = results | |
179 | printlog("test [%s] results summary: %s errors, %s failures, %s tests", | |
180 | test_name, results.num_errors, results.num_failures, results.num_tests) | |
181 | ||
182 | printlog("[ROSTEST] test [%s] done", test_name) | |
183 | ||
184 | ||
185 | #TODO: this is a straight copy from roslaunch. Need to reduce, refactor | |
186 | class LocalProcess(pmon.Process): | |
187 | """ | |
188 | Process launched on local machine | |
189 | """ | |
190 | ||
191 | def __init__(self, run_id, package, name, args, env, log_output, respawn=False, required=False, cwd=None, is_node=True): | |
192 | """ | |
193 | @param run_id: unique run ID for this roslaunch. Used to | |
194 | generate log directory location. run_id may be None if this | |
195 | feature is not being used. | |
196 | @type run_id: str | |
197 | @param package: name of package process is part of | |
198 | @type package: str | |
199 | @param name: name of process | |
200 | @type name: str | |
201 | @param args: list of arguments to process | |
202 | @type args: [str] | |
203 | @param env: environment dictionary for process | |
204 | @type env: {str : str} | |
205 | @param log_output: if True, log output streams of process | |
206 | @type log_output: bool | |
207 | @param respawn: respawn process if it dies (default is False) | |
208 | @type respawn: bool | |
209 | @param cwd: working directory of process, or None | |
210 | @type cwd: str | |
211 | @param is_node: (optional) if True, process is ROS node and accepts ROS node command-line arguments. Default: True | |
212 | @type is_node: False | |
213 | """ | |
214 | super(LocalProcess, self).__init__(package, name, args, env, respawn, required) | |
215 | self.run_id = run_id | |
216 | self.popen = None | |
217 | self.log_output = log_output | |
218 | self.started = False | |
219 | self.stopped = False | |
220 | self.cwd = cwd | |
221 | self.log_dir = None | |
222 | self.pid = -1 | |
223 | self.is_node = is_node | |
224 | ||
225 | # NOTE: in the future, info() is going to have to be sufficient for relaunching a process | |
226 | def get_info(self): | |
227 | """ | |
228 | Get all data about this process in dictionary form | |
229 | """ | |
230 | info = super(LocalProcess, self).get_info() | |
231 | info['pid'] = self.pid | |
232 | if self.run_id: | |
233 | info['run_id'] = self.run_id | |
234 | info['log_output'] = self.log_output | |
235 | if self.cwd is not None: | |
236 | info['cwd'] = self.cwd | |
237 | return info | |
238 | ||
239 | def _configure_logging(self): | |
240 | """ | |
241 | Configure logging of node's log file and stdout/stderr | |
242 | @return: stdout log file name, stderr log file | |
243 | name. Values are None if stdout/stderr are not logged. | |
244 | @rtype: str, str | |
245 | """ | |
246 | log_dir = rospkg.get_log_dir(env=os.environ) | |
247 | if self.run_id: | |
248 | log_dir = os.path.join(log_dir, self.run_id) | |
249 | if not os.path.exists(log_dir): | |
250 | try: | |
251 | os.makedirs(log_dir) | |
252 | except OSError, (errno, msg): | |
253 | if errno == 13: | |
254 | raise RLException("unable to create directory for log file [%s].\nPlease check permissions."%log_dir) | |
255 | else: | |
256 | raise RLException("unable to create directory for log file [%s]: %s"%(log_dir, msg)) | |
257 | # #973: save log dir for error messages | |
258 | self.log_dir = log_dir | |
259 | ||
260 | # send stdout/stderr to file. in the case of respawning, we have to | |
261 | # open in append mode | |
262 | # note: logfileerr: disabling in favor of stderr appearing in the console. | |
263 | # will likely reinstate once roserr/rosout is more properly used. | |
264 | logfileout = logfileerr = None | |
265 | ||
266 | if self.log_output: | |
267 | outf, errf = [os.path.join(log_dir, '%s-%s.log'%(self.name, n)) for n in ['stdout', 'stderr']] | |
268 | if self.respawn: | |
269 | mode = 'a' | |
270 | else: | |
271 | mode = 'w' | |
272 | logfileout = open(outf, mode) | |
273 | if is_child_mode(): | |
274 | logfileerr = open(errf, mode) | |
275 | ||
276 | # #986: pass in logfile name to node | |
277 | node_log_file = log_dir | |
278 | if self.is_node: | |
279 | # #1595: on respawn, these keep appending | |
280 | self.args = _cleanup_remappings(self.args, '__log:=') | |
281 | self.args.append("__log:=%s"%os.path.join(log_dir, "%s.log"%self.name)) | |
282 | ||
283 | return logfileout, logfileerr | |
284 | ||
285 | def start(self): | |
286 | """ | |
287 | Start the process. | |
288 | ||
289 | @raise pmon.FatalProcessLaunch: if process cannot be started and it | |
290 | is not likely to ever succeed | |
291 | """ | |
292 | super(LocalProcess, self).start() | |
293 | try: | |
294 | self.lock.acquire() | |
295 | self.started = self.stopped = False | |
296 | ||
297 | full_env = self.env | |
298 | ||
299 | # _configure_logging() can mutate self.args | |
300 | try: | |
301 | logfileout, logfileerr = self._configure_logging() | |
302 | except Exception as e: | |
303 | printerrlog("[%s] ERROR: unable to configure logging [%s]"%(self.name, str(e))) | |
304 | # it's not safe to inherit from this process as | |
305 | # rostest changes stdout to a StringIO, which is not a | |
306 | # proper file. | |
307 | logfileout, logfileerr = subprocess.PIPE, subprocess.PIPE | |
308 | ||
309 | if self.cwd == 'node': | |
310 | cwd = os.path.dirname(self.args[0]) | |
311 | elif self.cwd == 'cwd': | |
312 | cwd = os.getcwd() | |
313 | elif self.cwd == 'ros-root': | |
314 | cwd = get_ros_root() | |
315 | else: | |
316 | cwd = rospkg.get_ros_home() | |
317 | ||
318 | try: | |
319 | self.popen = subprocess.Popen(self.args, cwd=cwd, stdout=logfileout, stderr=logfileerr, env=full_env, close_fds=True, preexec_fn=os.setsid) | |
320 | except OSError, (errno, msg): | |
321 | self.started = True # must set so is_alive state is correct | |
322 | if errno == 8: #Exec format error | |
323 | raise pmon.FatalProcessLaunch("Unable to launch [%s]. \nIf it is a script, you may be missing a '#!' declaration at the top."%self.name) | |
324 | elif errno == 2: #no such file or directory | |
325 | raise pmon.FatalProcessLaunch("""Roslaunch got a '%s' error while attempting to run: | |
326 | ||
327 | %s | |
328 | ||
329 | Please make sure that all the executables in this command exist and have | |
330 | executable permission. This is often caused by a bad launch-prefix."""%(msg, ' '.join(self.args))) | |
331 | else: | |
332 | raise pmon.FatalProcessLaunch("unable to launch [%s]: %s"%(' '.join(self.args), msg)) | |
333 | ||
334 | self.started = True | |
335 | # Check that the process is either still running (poll returns | |
336 | # None) or that it completed successfully since when we | |
337 | # launched it above (poll returns the return code, 0). | |
338 | poll_result = self.popen.poll() | |
339 | if poll_result is None or poll_result == 0: | |
340 | self.pid = self.popen.pid | |
341 | printlog_bold("process[%s]: started with pid [%s]"%(self.name, self.pid)) | |
342 | return True | |
343 | else: | |
344 | printerrlog("failed to start local process: %s"%(' '.join(self.args))) | |
345 | return False | |
346 | finally: | |
347 | self.lock.release() | |
348 | ||
349 | def is_alive(self): | |
350 | """ | |
351 | @return: True if process is still running | |
352 | @rtype: bool | |
353 | """ | |
354 | if not self.started: #not started yet | |
355 | return True | |
356 | if self.stopped or self.popen is None: | |
357 | return False | |
358 | self.exit_code = self.popen.poll() | |
359 | if self.exit_code is not None: | |
360 | return False | |
361 | return True | |
362 | ||
363 | def get_exit_description(self): | |
364 | """ | |
365 | @return: human-readable description of exit state | |
366 | @rtype: str | |
367 | """ | |
368 | # #973: include location of output location in message | |
369 | if self.exit_code is not None: | |
370 | if self.exit_code: | |
371 | if self.log_dir: | |
372 | return 'process has died [pid %s, exit code %s].\nlog files: %s*.log'%(self.pid, self.exit_code, os.path.join(self.log_dir, self.name)) | |
373 | else: | |
374 | return 'process has died [pid %s, exit code %s]'%(self.pid, self.exit_code) | |
375 | else: | |
376 | if self.log_dir: | |
377 | return 'process has finished cleanly.\nlog file: %s*.log'%(os.path.join(self.log_dir, self.name)) | |
378 | else: | |
379 | return 'process has finished cleanly' | |
380 | else: | |
381 | return 'process has died' | |
382 | ||
383 | def _stop_unix(self, errors): | |
384 | """ | |
385 | UNIX implementation of process killing | |
386 | ||
387 | @param errors: error messages. stop() will record messages into this list. | |
388 | @type errors: [str] | |
389 | """ | |
390 | self.exit_code = self.popen.poll() | |
391 | if self.exit_code is not None: | |
392 | #print "process[%s].stop(): process has already returned %s"%(self.name, self.exit_code) | |
393 | self.popen = None | |
394 | self.stopped = True | |
395 | return | |
396 | ||
397 | pid = self.popen.pid | |
398 | pgid = os.getpgid(pid) | |
399 | ||
400 | try: | |
401 | # Start with SIGINT and escalate from there. | |
402 | os.killpg(pgid, signal.SIGINT) | |
403 | timeout_t = time.time() + TIMEOUT_SIGINT | |
404 | retcode = self.popen.poll() | |
405 | while time.time() < timeout_t and retcode is None: | |
406 | time.sleep(0.1) | |
407 | retcode = self.popen.poll() | |
408 | # Escalate non-responsive process | |
409 | if retcode is None: | |
410 | printerrlog("[%s] escalating to SIGTERM"%self.name) | |
411 | timeout_t = time.time() + TIMEOUT_SIGTERM | |
412 | os.killpg(pgid, signal.SIGTERM) | |
413 | retcode = self.popen.poll() | |
414 | while time.time() < timeout_t and retcode is None: | |
415 | time.sleep(0.2) | |
416 | retcode = self.popen.poll() | |
417 | if retcode is None: | |
418 | printerrlog("[%s] escalating to SIGKILL"%self.name) | |
419 | errors.append("process[%s, pid %s]: required SIGKILL. May still be running."%(self.name, pid)) | |
420 | try: | |
421 | os.killpg(pgid, signal.SIGKILL) | |
422 | # #2096: don't block on SIGKILL, because this results in more orphaned processes overall | |
423 | except OSError as e: | |
424 | if e.args[0] == 3: | |
425 | printerrlog("no [%s] process with pid [%s]"%(self.name, pid)) | |
426 | else: | |
427 | printerrlog("errors shutting down [%s]: %s"%(self.name, e)) | |
428 | finally: | |
429 | self.popen = None | |
430 | ||
431 | def stop(self, errors=[]): | |
432 | """ | |
433 | Stop the process. Record any significant error messages in the errors parameter | |
434 | ||
435 | @param errors: error messages. stop() will record messages into this list. | |
436 | @type errors: [str] | |
437 | """ | |
438 | super(LocalProcess, self).stop(errors) | |
439 | self.lock.acquire() | |
440 | try: | |
441 | try: | |
442 | if self.popen is None: | |
443 | return | |
444 | #NOTE: currently POSIX-only. Need to add in Windows code once I have a test environment: | |
445 | # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/347462 | |
446 | self._stop_unix(errors) | |
447 | except: | |
448 | printerrlog("[%s] EXCEPTION %s"%(self.name, traceback.format_exc())) | |
449 | finally: | |
450 | self.stopped = True | |
451 | self.lock.release() | |
452 | ||
453 | def print_runner_summary(runner_results, junit_results, runner_name='ROSUNIT'): | |
454 | """ | |
455 | Print summary of runner results and actual test results to | |
456 | stdout. For rosunit and rostest, the test is wrapped in an | |
457 | external runner. The results from this runner are important if the | |
458 | runner itself has a failure. | |
459 | ||
460 | @param runner_result: unittest runner result object | |
461 | @type runner_result: _XMLTestResult | |
462 | @param junit_results: Parsed JUnit test results | |
463 | @type junit_results: rosunit.junitxml.Result | |
464 | """ | |
465 | # we have two separate result objects, which can be a bit | |
466 | # confusing. 'result' counts successful _running_ of tests | |
467 | # (i.e. doesn't check for actual test success). The 'r' result | |
468 | # object contains results of the actual tests. | |
469 | ||
470 | buff = cStringIO.StringIO() | |
471 | ||
472 | buff.write("[%s]"%(runner_name)+'-'*71+'\n\n') | |
473 | for tc_result in junit_results.test_case_results: | |
474 | buff.write(tc_result.description) | |
475 | for tc_result in runner_results.failures: | |
476 | buff.write("[%s][failed]\n"%tc_result[0]._testMethodName) | |
477 | ||
478 | buff.write('\nSUMMARY\n') | |
479 | if runner_results.wasSuccessful() and (junit_results.num_errors + junit_results.num_failures) == 0: | |
480 | buff.write("\033[32m * RESULT: SUCCESS\033[0m\n") | |
481 | else: | |
482 | buff.write("\033[1;31m * RESULT: FAIL\033[0m\n") | |
483 | ||
484 | # TODO: still some issues with the numbers adding up if tests fail to launch | |
485 | ||
486 | # number of errors from the inner tests, plus add in count for tests | |
487 | # that didn't run properly ('result' object). | |
488 | buff.write(" * TESTS: %s\n"%junit_results.num_tests) | |
489 | num_errors = junit_results.num_errors+len(runner_results.errors) | |
490 | if num_errors: | |
491 | buff.write("\033[1;31m * ERRORS: %s\033[0m\n"%num_errors) | |
492 | else: | |
493 | buff.write(" * ERRORS: 0\n") | |
494 | num_failures = junit_results.num_failures+len(runner_results.failures) | |
495 | if num_failures: | |
496 | buff.write("\033[1;31m * FAILURES: %s\033[0m\n"%num_failures) | |
497 | else: | |
498 | buff.write(" * FAILURES: 0\n") | |
499 | ||
500 | if runner_results.failures: | |
501 | buff.write("\nERROR: The following tests failed to run:\n") | |
502 | for tc_result in runner_results.failures: | |
503 | buff.write(" * " +tc_result[0]._testMethodName + "\n") | |
504 | ||
505 | print buff.getvalue() | |
506 | ||
507 | def _format_errors(errors): | |
508 | formatted = [] | |
509 | for e in errors: | |
510 | if '_testMethodName' in e[0].__dict__: | |
511 | formatted.append(e[0]._testMethodName) | |
512 | elif 'description' in e[0].__dict__: | |
513 | formatted.append('%s: %s\n' % (str(e[0].description), str(e[1]))) | |
514 | else: | |
515 | formatted.append(str(e[0].__dict__)) | |
516 | return formatted | |
517 | ||
518 | def print_unittest_summary(result): | |
519 | """ | |
520 | Print summary of python unittest result to stdout | |
521 | @param result: test results | |
522 | """ | |
523 | buff = cStringIO.StringIO() | |
524 | buff.write("-------------------------------------------------------------\nSUMMARY:\n") | |
525 | if result.wasSuccessful(): | |
526 | buff.write("\033[32m * RESULT: SUCCESS\033[0m\n") | |
527 | else: | |
528 | buff.write(" * RESULT: FAIL\n") | |
529 | buff.write(" * TESTS: %s\n"%result.testsRun) | |
530 | buff.write(" * ERRORS: %s [%s]\n"%(len(result.errors), ', '.join(_format_errors(result.errors)))) | |
531 | buff.write(" * FAILURES: %s [%s]\n"%(len(result.failures), ', '.join(_format_errors(result.failures)))) | |
532 | print buff.getvalue() | |
533 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | import os | |
35 | import sys | |
36 | import logging | |
37 | ||
38 | import rospkg | |
39 | ||
40 | from .xmlrunner import XMLTestRunner | |
41 | ||
42 | XML_OUTPUT_FLAG = '--gtest_output=xml:' #use gtest-compatible flag | |
43 | ||
44 | def printlog(msg, *args): | |
45 | if args: | |
46 | msg = msg%args | |
47 | print "[ROSUNIT]"+msg | |
48 | ||
49 | def printlog_bold(msg, *args): | |
50 | if args: | |
51 | msg = msg%args | |
52 | print '\033[1m[ROSUNIT]%s\033[0m'%msg | |
53 | ||
54 | def printerrlog(msg, *args): | |
55 | if args: | |
56 | msg = msg%args | |
57 | print >> sys.stderr, "[ROSUNIT]"+msg | |
58 | ||
59 | # this is a copy of the roslogging utility. it's been moved here as it is a common | |
60 | # routine for programs using accessing ROS directories | |
61 | def makedirs_with_parent_perms(p): | |
62 | """ | |
63 | Create the directory using the permissions of the nearest | |
64 | (existing) parent directory. This is useful for logging, where a | |
65 | root process sometimes has to log in the user's space. | |
66 | @param p: directory to create | |
67 | @type p: str | |
68 | """ | |
69 | p = os.path.abspath(p) | |
70 | parent = os.path.dirname(p) | |
71 | # recurse upwards, checking to make sure we haven't reached the | |
72 | # top | |
73 | if not os.path.exists(p) and p and parent != p: | |
74 | makedirs_with_parent_perms(parent) | |
75 | s = os.stat(parent) | |
76 | os.mkdir(p) | |
77 | ||
78 | # if perms of new dir don't match, set anew | |
79 | s2 = os.stat(p) | |
80 | if s.st_uid != s2.st_uid or s.st_gid != s2.st_gid: | |
81 | os.chown(p, s.st_uid, s.st_gid) | |
82 | if s.st_mode != s2.st_mode: | |
83 | os.chmod(p, s.st_mode) | |
84 | ||
85 | def xml_results_file(test_pkg, test_name, is_rostest=False): | |
86 | """ | |
87 | @param test_pkg: name of test's package | |
88 | @type test_pkg: str | |
89 | @param test_name str: name of test | |
90 | @type test_name: str | |
91 | @param is_rostest: True if the results file is for a rostest-generated unit instance | |
92 | @type is_rostest: bool | |
93 | @return: name of xml results file for specified test | |
94 | @rtype: str | |
95 | """ | |
96 | test_dir = os.path.join(rospkg.get_test_results_dir(), test_pkg) | |
97 | if not os.path.exists(test_dir): | |
98 | try: | |
99 | makedirs_with_parent_perms(test_dir) | |
100 | except OSError: | |
101 | raise IOError("cannot create test results directory [%s]. Please check permissions."%(test_dir)) | |
102 | ||
103 | # #576: strip out chars that would bork the filename | |
104 | # this is fairly primitive, but for now just trying to catch some common cases | |
105 | for c in ' "\'&$!`/\\': | |
106 | if c in test_name: | |
107 | test_name = test_name.replace(c, '_') | |
108 | if is_rostest: | |
109 | return os.path.join(test_dir, 'rostest-%s.xml'%test_name) | |
110 | else: | |
111 | return os.path.join(test_dir, 'rosunit-%s.xml'%test_name) | |
112 | ||
113 | def rostest_name_from_path(pkg_dir, test_file): | |
114 | """ | |
115 | Derive name of rostest based on file name/path. rostest follows a | |
116 | certain convention defined above. | |
117 | ||
118 | @return: name of test | |
119 | @rtype: str | |
120 | """ | |
121 | test_file_abs = os.path.abspath(test_file) | |
122 | if test_file_abs.startswith(pkg_dir): | |
123 | # compute package-relative path | |
124 | test_file = test_file_abs[len(pkg_dir):] | |
125 | if test_file[0] == os.sep: | |
126 | test_file = test_file[1:] | |
127 | outname = test_file.replace(os.sep, '_') | |
128 | if '.' in outname: | |
129 | outname = outname[:outname.rfind('.')] | |
130 | return outname | |
131 | ||
132 | def create_xml_runner(test_pkg, test_name, results_file=None, is_rostest=False): | |
133 | """ | |
134 | Create the unittest test runner with XML output | |
135 | @param test_pkg: package name | |
136 | @type test_pkg: str | |
137 | @param test_name: test name | |
138 | @type test_name: str | |
139 | @param is_rostest: if True, use naming scheme for rostest itself instead of individual unit test naming | |
140 | @type is_rostest: bool | |
141 | """ | |
142 | test_name = os.path.basename(test_name) | |
143 | # determine output xml file name | |
144 | if not results_file: | |
145 | results_file = xml_results_file(test_pkg, test_name, is_rostest) | |
146 | test_dir = os.path.abspath(os.path.dirname(results_file)) | |
147 | if not os.path.exists(test_dir): | |
148 | try: | |
149 | makedirs_with_parent_perms(test_dir) #NOTE: this will pass up an error exception if it fails | |
150 | except OSError: | |
151 | raise IOError("cannot create test results directory [%s]. Please check permissions."%(test_dir)) | |
152 | ||
153 | elif os.path.isfile(test_dir): | |
154 | raise Exception("ERROR: cannot run test suite, file is preventing creation of test dir: %s"%test_dir) | |
155 | ||
156 | print "[ROSUNIT] Outputting test results to %s"%results_file | |
157 | outstream = open(results_file, 'w') | |
158 | outstream.write('<?xml version="1.0" encoding="utf-8"?>\n') | |
159 | return XMLTestRunner(stream=outstream) | |
160 |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id$ | |
34 | ||
35 | """ | |
36 | Library for reading and manipulating Ant JUnit XML result files. | |
37 | """ | |
38 | ||
39 | from __future__ import print_function | |
40 | ||
41 | import os | |
42 | import sys | |
43 | import cStringIO | |
44 | import string | |
45 | import codecs | |
46 | import re | |
47 | ||
48 | from xml.dom.minidom import parse, parseString | |
49 | from xml.dom import Node as DomNode | |
50 | ||
51 | import rospkg | |
52 | ||
53 | class TestInfo(object): | |
54 | """ | |
55 | Common container for 'error' and 'failure' results | |
56 | """ | |
57 | ||
58 | def __init__(self, type_, text): | |
59 | """ | |
60 | @param type_: type attribute from xml | |
61 | @type type_: str | |
62 | @param text: text property from xml | |
63 | @type text: str | |
64 | """ | |
65 | self.type = type_ | |
66 | self.text = text | |
67 | ||
68 | class TestError(TestInfo): | |
69 | """ | |
70 | 'error' result container | |
71 | """ | |
72 | def xml(self): | |
73 | return u'<error type="%s"><![CDATA[%s]]></error>'%(self.type, self.text) | |
74 | ||
75 | class TestFailure(TestInfo): | |
76 | """ | |
77 | 'failure' result container | |
78 | """ | |
79 | def xml(self): | |
80 | return u'<failure type="%s"><![CDATA[%s]]></failure>'%(self.type, self.text) | |
81 | ||
82 | ||
83 | class TestCaseResult(object): | |
84 | """ | |
85 | 'testcase' result container | |
86 | """ | |
87 | ||
88 | def __init__(self, name): | |
89 | """ | |
90 | @param name: name of testcase | |
91 | @type name: str | |
92 | """ | |
93 | self.name = name | |
94 | self.failures = [] | |
95 | self.errors = [] | |
96 | self.time = 0.0 | |
97 | self.classname = '' | |
98 | ||
99 | def _passed(self): | |
100 | """ | |
101 | @return: True if test passed | |
102 | @rtype: bool | |
103 | """ | |
104 | return not self.errors and not self.failures | |
105 | ## bool: True if test passed without errors or failures | |
106 | passed = property(_passed) | |
107 | ||
108 | def _failure_description(self): | |
109 | """ | |
110 | @return: description of testcase failure | |
111 | @rtype: str | |
112 | """ | |
113 | if self.failures: | |
114 | tmpl = "[%s][FAILURE]"%self.name | |
115 | tmpl = tmpl + '-'*(80-len(tmpl)) | |
116 | tmpl = tmpl+"\n%s\n"+'-'*80+"\n\n" | |
117 | return '\n'.join(tmpl%x.text for x in self.failures) | |
118 | return '' | |
119 | ||
120 | def _error_description(self): | |
121 | """ | |
122 | @return: description of testcase error | |
123 | @rtype: str | |
124 | """ | |
125 | if self.errors: | |
126 | tmpl = "[%s][ERROR]"%self.name | |
127 | tmpl = tmpl + '-'*(80-len(tmpl)) | |
128 | tmpl = tmpl+"\n%s\n"+'-'*80+"\n\n" | |
129 | return '\n'.join(tmpl%x.text for x in self.errors) | |
130 | return '' | |
131 | ||
132 | def _description(self): | |
133 | """ | |
134 | @return: description of testcase result | |
135 | @rtype: str | |
136 | """ | |
137 | if self.passed: | |
138 | return "[%s][passed]\n"%self.name | |
139 | else: | |
140 | return self._failure_description()+\ | |
141 | self._error_description() | |
142 | ## str: printable description of testcase result | |
143 | description = property(_description) | |
144 | def add_failure(self, failure): | |
145 | """ | |
146 | @param failure TestFailure | |
147 | """ | |
148 | self.failures.append(failure) | |
149 | ||
150 | def add_error(self, error): | |
151 | """ | |
152 | @param failure TestError | |
153 | """ | |
154 | self.errors.append(error) | |
155 | ||
156 | def xml(self): | |
157 | return u' <testcase classname="%s" name="%s" time="%s">\n'%(self.classname, self.name, self.time)+\ | |
158 | '\n '.join([f.xml() for f in self.failures])+\ | |
159 | '\n '.join([e.xml() for e in self.errors])+\ | |
160 | ' </testcase>' | |
161 | ||
162 | class Result(object): | |
163 | __slots__ = ['name', 'num_errors', 'num_failures', 'num_tests', \ | |
164 | 'test_case_results', 'system_out', 'system_err', 'time'] | |
165 | def __init__(self, name, num_errors=0, num_failures=0, num_tests=0): | |
166 | self.name = name | |
167 | self.num_errors = num_errors | |
168 | self.num_failures = num_failures | |
169 | self.num_tests = num_tests | |
170 | self.test_case_results = [] | |
171 | self.system_out = '' | |
172 | self.system_err = '' | |
173 | self.time = 0.0 | |
174 | ||
175 | def accumulate(self, r): | |
176 | """ | |
177 | Add results from r to this result | |
178 | @param r: results to aggregate with this result | |
179 | @type r: Result | |
180 | """ | |
181 | self.num_errors += r.num_errors | |
182 | self.num_failures += r.num_failures | |
183 | self.num_tests += r.num_tests | |
184 | self.time += r.time | |
185 | self.test_case_results.extend(r.test_case_results) | |
186 | if r.system_out: | |
187 | self.system_out += '\n'+r.system_out | |
188 | if r.system_err: | |
189 | self.system_err += '\n'+r.system_err | |
190 | ||
191 | def add_test_case_result(self, r): | |
192 | """ | |
193 | Add results from a testcase to this result container | |
194 | @param r: TestCaseResult | |
195 | @type r: TestCaseResult | |
196 | """ | |
197 | self.test_case_results.append(r) | |
198 | ||
199 | def xml(self): | |
200 | """ | |
201 | @return: document as unicode (UTF-8 declared) XML according to Ant JUnit spec | |
202 | """ | |
203 | return u'<?xml version="1.0" encoding="utf-8"?>'+\ | |
204 | '<testsuite name="%s" tests="%s" errors="%s" failures="%s" time="%s">'%\ | |
205 | (self.name, self.num_tests, self.num_errors, self.num_failures, self.time)+\ | |
206 | '\n'.join([tc.xml() for tc in self.test_case_results])+\ | |
207 | ' <system-out><![CDATA[%s]]></system-out>'%self.system_out+\ | |
208 | ' <system-err><![CDATA[%s]]></system-err>'%self.system_err+\ | |
209 | '</testsuite>' | |
210 | ||
211 | def _text(tag): | |
212 | return reduce(lambda x, y: x + y, [c.data for c in tag.childNodes if c.nodeType in [DomNode.TEXT_NODE, DomNode.CDATA_SECTION_NODE]], "").strip() | |
213 | ||
214 | def _load_suite_results(test_suite_name, test_suite, result): | |
215 | nodes = [n for n in test_suite.childNodes \ | |
216 | if n.nodeType == DomNode.ELEMENT_NODE] | |
217 | for node in nodes: | |
218 | name = node.tagName | |
219 | if name == 'testsuite': | |
220 | # for now we flatten this hierarchy | |
221 | _load_suite_results(test_suite_name, node, result) | |
222 | elif name == 'system-out': | |
223 | if _text(node): | |
224 | system_out = "[%s] stdout"%test_suite_name + "-"*(71-len(test_suite_name)) | |
225 | system_out += '\n'+_text(node) | |
226 | result.system_out += system_out | |
227 | elif name == 'system-err': | |
228 | if _text(node): | |
229 | system_err = "[%s] stderr"%test_suite_name + "-"*(71-len(test_suite_name)) | |
230 | system_err += '\n'+_text(node) | |
231 | result.system_err += system_err | |
232 | elif name == 'testcase': | |
233 | name = node.getAttribute('name') or 'unknown' | |
234 | classname = node.getAttribute('classname') or 'unknown' | |
235 | ||
236 | # mangle the classname for some sense of uniformity | |
237 | # between rostest/unittest/gtest | |
238 | if '__main__.' in classname: | |
239 | classname = classname[classname.find('__main__.')+9:] | |
240 | if classname == 'rostest.rostest.RosTest': | |
241 | classname = 'rostest' | |
242 | elif not classname.startswith(result.name): | |
243 | classname = "%s.%s"%(result.name,classname) | |
244 | ||
245 | time = float(node.getAttribute('time')) or 0.0 | |
246 | tc_result = TestCaseResult("%s/%s"%(test_suite_name,name)) | |
247 | tc_result.classname = classname | |
248 | tc_result.time = time | |
249 | result.add_test_case_result(tc_result) | |
250 | for d in [n for n in node.childNodes \ | |
251 | if n.nodeType == DomNode.ELEMENT_NODE]: | |
252 | # convert 'message' attributes to text elements to keep | |
253 | # python unittest and gtest consistent | |
254 | if d.tagName == 'failure': | |
255 | message = d.getAttribute('message') or '' | |
256 | text = _text(d) or message | |
257 | x = TestFailure(d.getAttribute('type') or '', text) | |
258 | tc_result.add_failure(x) | |
259 | elif d.tagName == 'error': | |
260 | message = d.getAttribute('message') or '' | |
261 | text = _text(d) or message | |
262 | x = TestError(d.getAttribute('type') or '', text) | |
263 | tc_result.add_error(x) | |
264 | ||
265 | ## #603: unit test suites are not good about screening out illegal | |
266 | ## unicode characters. This little recipe I from http://boodebr.org/main/python/all-about-python-and-unicode#UNI_XML | |
267 | ## screens these out | |
268 | RE_XML_ILLEGAL = u'([\u0000-\u0008\u000b-\u000c\u000e-\u001f\ufffe-\uffff])' + \ | |
269 | u'|' + \ | |
270 | u'([%s-%s][^%s-%s])|([^%s-%s][%s-%s])|([%s-%s]$)|(^[%s-%s])' % \ | |
271 | (unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff), | |
272 | unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff), | |
273 | unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff)) | |
274 | _safe_xml_regex = re.compile(RE_XML_ILLEGAL) | |
275 | ||
276 | def _read_file_safe_xml(test_file): | |
277 | """ | |
278 | read in file, screen out unsafe unicode characters | |
279 | """ | |
280 | f = None | |
281 | try: | |
282 | # this is ugly, but the files in question that are problematic | |
283 | # do not declare unicode type. | |
284 | if not os.path.isfile(test_file): | |
285 | raise Exception("test file does not exist") | |
286 | try: | |
287 | f = codecs.open(test_file, "r", "utf-8" ) | |
288 | x = f.read() | |
289 | except: | |
290 | if f is not None: | |
291 | f.close() | |
292 | f = codecs.open(test_file, "r", "iso8859-1" ) | |
293 | x = f.read() | |
294 | ||
295 | for match in _safe_xml_regex.finditer(x): | |
296 | x = x[:match.start()] + "?" + x[match.end():] | |
297 | return x.encode("utf-8") | |
298 | finally: | |
299 | if f is not None: | |
300 | f.close() | |
301 | ||
302 | def read(test_file, test_name): | |
303 | """ | |
304 | Read in the test_result file | |
305 | @param test_file: test file path | |
306 | @type test_file: str | |
307 | @param test_name: name of test | |
308 | @type test_name: str | |
309 | @return: test results | |
310 | @rtype: Result | |
311 | """ | |
312 | try: | |
313 | xml_str = _read_file_safe_xml(test_file) | |
314 | if not xml_str.strip(): | |
315 | print("WARN: test result file is empty [%s]"%(test_file)) | |
316 | return Result(test_name, 0, 0, 0) | |
317 | test_suites = parseString(xml_str).getElementsByTagName('testsuite') | |
318 | except Exception as e: | |
319 | print("WARN: cannot read test result file [%s]: %s"%(test_file, str(e))) | |
320 | return Result(test_name, 0, 0, 0) | |
321 | if not test_suites: | |
322 | print("WARN: test result file [%s] contains no results"%(test_file)) | |
323 | return Result(test_name, 0, 0, 0) | |
324 | ||
325 | results = Result(test_name, 0, 0, 0) | |
326 | for index, test_suite in enumerate(test_suites): | |
327 | # skip test suites which are already covered by a parent test suite | |
328 | if index > 0 and test_suite.parentNode in test_suites[0:index]: | |
329 | continue | |
330 | ||
331 | #test_suite = test_suite[0] | |
332 | vals = [test_suite.getAttribute(attr) for attr in ['errors', 'failures', 'tests']] | |
333 | vals = [v or 0 for v in vals] | |
334 | err, fail, tests = [string.atoi(val) for val in vals] | |
335 | ||
336 | result = Result(test_name, err, fail, tests) | |
337 | result.time = 0.0 if not len(test_suite.getAttribute('time')) else float(test_suite.getAttribute('time')) | |
338 | ||
339 | # Create a prefix based on the test result filename. The idea is to | |
340 | # disambiguate the case when tests of the same name are provided in | |
341 | # different .xml files. We use the name of the parent directory | |
342 | test_file_base = os.path.basename(os.path.dirname(os.path.abspath(test_file))) | |
343 | fname = os.path.basename(test_file) | |
344 | if fname.startswith('TEST-'): | |
345 | fname = fname[5:] | |
346 | if fname.endswith('.xml'): | |
347 | fname = fname[:-4] | |
348 | test_file_base = "%s.%s"%(test_file_base, fname) | |
349 | _load_suite_results(test_file_base, test_suite, result) | |
350 | results.accumulate(result) | |
351 | return results | |
352 | ||
353 | def read_all(filter_=[]): | |
354 | """ | |
355 | Read in the test_results and aggregate into a single Result object | |
356 | @param filter_: list of packages that should be processed | |
357 | @type filter_: [str] | |
358 | @return: aggregated result | |
359 | @rtype: L{Result} | |
360 | """ | |
361 | dir_ = rospkg.get_test_results_dir() | |
362 | root_result = Result('ros', 0, 0, 0) | |
363 | if not os.path.exists(dir_): | |
364 | return root_result | |
365 | for d in os.listdir(dir_): | |
366 | if filter_ and not d in filter_: | |
367 | continue | |
368 | subdir = os.path.join(dir_, d) | |
369 | if os.path.isdir(subdir): | |
370 | for filename in os.listdir(subdir): | |
371 | if filename.endswith('.xml'): | |
372 | filename = os.path.join(subdir, filename) | |
373 | result = read(filename, os.path.basename(subdir)) | |
374 | root_result.accumulate(result) | |
375 | return root_result | |
376 | ||
377 | ||
378 | def test_failure_junit_xml(test_name, message, stdout=None): | |
379 | """ | |
380 | Generate JUnit XML file for a unary test suite where the test failed | |
381 | ||
382 | @param test_name: Name of test that failed | |
383 | @type test_name: str | |
384 | @param message: failure message | |
385 | @type message: str | |
386 | @param stdout: stdout data to include in report | |
387 | @type stdout: str | |
388 | """ | |
389 | if not stdout: | |
390 | return """<?xml version="1.0" encoding="UTF-8"?> | |
391 | <testsuite tests="1" failures="1" time="1" errors="0" name="%s"> | |
392 | <testcase name="test_ran" status="run" time="1" classname="Results"> | |
393 | <failure message="%s" type=""/> | |
394 | </testcase> | |
395 | </testsuite>"""%(test_name, message) | |
396 | else: | |
397 | return """<?xml version="1.0" encoding="UTF-8"?> | |
398 | <testsuite tests="1" failures="1" time="1" errors="0" name="%s"> | |
399 | <testcase name="test_ran" status="run" time="1" classname="Results"> | |
400 | <failure message="%s" type=""/> | |
401 | </testcase> | |
402 | <system-out><![CDATA[[ | |
403 | %s | |
404 | ]]></system-out> | |
405 | </testsuite>"""%(test_name, message, stdout) | |
406 | ||
407 | def test_success_junit_xml(test_name): | |
408 | """ | |
409 | Generate JUnit XML file for a unary test suite where the test succeeded. | |
410 | ||
411 | @param test_name: Name of test that passed | |
412 | @type test_name: str | |
413 | """ | |
414 | return """<?xml version="1.0" encoding="UTF-8"?> | |
415 | <testsuite tests="1" failures="0" time="1" errors="0" name="%s"> | |
416 | <testcase name="test_ran" status="run" time="1" classname="Results"> | |
417 | </testcase> | |
418 | </testsuite>"""%(test_name) | |
419 | ||
420 | def print_summary(junit_results, runner_name='ROSUNIT'): | |
421 | """ | |
422 | Print summary of junitxml results to stdout. | |
423 | """ | |
424 | # we have two separate result objects, which can be a bit | |
425 | # confusing. 'result' counts successful _running_ of tests | |
426 | # (i.e. doesn't check for actual test success). The 'r' result | |
427 | # object contains results of the actual tests. | |
428 | ||
429 | buff = cStringIO.StringIO() | |
430 | buff.write("[%s]"%runner_name+'-'*71+'\n\n') | |
431 | for tc_result in junit_results.test_case_results: | |
432 | buff.write(tc_result.description) | |
433 | ||
434 | buff.write('\nSUMMARY\n') | |
435 | if (junit_results.num_errors + junit_results.num_failures) == 0: | |
436 | buff.write("\033[32m * RESULT: SUCCESS\033[0m\n") | |
437 | else: | |
438 | buff.write("\033[1;31m * RESULT: FAIL\033[0m\n") | |
439 | ||
440 | # TODO: still some issues with the numbers adding up if tests fail to launch | |
441 | ||
442 | # number of errors from the inner tests, plus add in count for tests | |
443 | # that didn't run properly ('result' object). | |
444 | buff.write(" * TESTS: %s\n"%junit_results.num_tests) | |
445 | num_errors = junit_results.num_errors | |
446 | if num_errors: | |
447 | buff.write("\033[1;31m * ERRORS: %s\033[0m\n"%num_errors) | |
448 | else: | |
449 | buff.write(" * ERRORS: 0\n") | |
450 | num_failures = junit_results.num_failures | |
451 | if num_failures: | |
452 | buff.write("\033[1;31m * FAILURES: %s\033[0m\n"%num_failures) | |
453 | else: | |
454 | buff.write(" * FAILURES: 0\n") | |
455 | ||
456 | print(buff.getvalue()) | |
457 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | """ | |
35 | Process monitor | |
36 | """ | |
37 | ||
38 | from __future__ import with_statement | |
39 | ||
40 | import os | |
41 | import sys | |
42 | import time | |
43 | import traceback | |
44 | import Queue | |
45 | import atexit | |
46 | from threading import Thread, RLock, Lock | |
47 | ||
48 | from .core import printlog, printlog_bold, printerrlog | |
49 | ||
50 | class PmonException(Exception): pass | |
51 | ||
52 | class FatalProcessLaunch(PmonException): | |
53 | """ | |
54 | Exception to indicate that a process launch has failed in a fatal | |
55 | manner (i.e. relaunch is unlikely to succeed) | |
56 | """ | |
57 | pass | |
58 | ||
59 | # start/shutdown ################################################ | |
60 | ||
61 | _pmons = [] | |
62 | _pmon_counter = 0 | |
63 | _shutting_down = False | |
64 | def start_process_monitor(): | |
65 | global _pmon_counter | |
66 | if _shutting_down: | |
67 | return None | |
68 | _pmon_counter += 1 | |
69 | name = "ProcessMonitor-%s"%_pmon_counter | |
70 | process_monitor = ProcessMonitor(name) | |
71 | with _shutdown_lock: | |
72 | # prevent race condition with pmon_shutdown() being triggered | |
73 | # as we are starting a ProcessMonitor (i.e. user hits ctrl-C | |
74 | # during startup) | |
75 | _pmons.append(process_monitor) | |
76 | process_monitor.start() | |
77 | return process_monitor | |
78 | ||
79 | def shutdown_process_monitor(process_monitor): | |
80 | """ | |
81 | @param process_monitor: process monitor to kill | |
82 | @type process_monitor: L{ProcessMonitor} | |
83 | @return: True if process_monitor was successfully | |
84 | shutdown. False if it could not be shutdown cleanly or if there is | |
85 | a problem with process_monitor | |
86 | parameter. shutdown_process_monitor() does not throw any exceptions | |
87 | as this is shutdown-critical code. | |
88 | @rtype: bool | |
89 | """ | |
90 | try: | |
91 | if process_monitor is None or process_monitor.is_shutdown: | |
92 | return False | |
93 | ||
94 | process_monitor.shutdown() | |
95 | process_monitor.join(20.0) | |
96 | if process_monitor.isAlive(): | |
97 | return False | |
98 | else: | |
99 | return True | |
100 | except Exception, e: | |
101 | return False | |
102 | ||
103 | _shutdown_lock = Lock() | |
104 | def pmon_shutdown(): | |
105 | global _pmons | |
106 | with _shutdown_lock: | |
107 | if not _pmons: | |
108 | return | |
109 | for p in _pmons: | |
110 | shutdown_process_monitor(p) | |
111 | del _pmons[:] | |
112 | ||
113 | atexit.register(pmon_shutdown) | |
114 | ||
115 | # ############################################################## | |
116 | ||
117 | class Process(object): | |
118 | """ | |
119 | Basic process representation for L{ProcessMonitor}. Must be subclassed | |
120 | to provide actual start()/stop() implementations. | |
121 | """ | |
122 | ||
123 | def __init__(self, package, name, args, env, respawn=False, required=False): | |
124 | self.package = package | |
125 | self.name = name | |
126 | self.args = args | |
127 | self.env = env | |
128 | self.respawn = respawn | |
129 | self.required = required | |
130 | self.lock = Lock() | |
131 | self.exit_code = None | |
132 | # for keeping track of respawning | |
133 | self.spawn_count = 0 | |
134 | ||
135 | def __str__(self): | |
136 | return "Process<%s>"%(self.name) | |
137 | ||
138 | # NOTE: get_info() is going to have to be sufficient for | |
139 | # generating respawn requests, so we must be complete about it | |
140 | ||
141 | def get_info(self): | |
142 | """ | |
143 | Get all data about this process in dictionary form | |
144 | @return: dictionary of all relevant process properties | |
145 | @rtype: dict { str: val } | |
146 | """ | |
147 | info = { | |
148 | 'spawn_count': self.spawn_count, | |
149 | 'args': self.args, | |
150 | 'env': self.env, | |
151 | 'package': self.package, | |
152 | 'name': self.name, | |
153 | 'alive': self.is_alive(), | |
154 | 'respawn': self.respawn, | |
155 | 'required': self.required, | |
156 | } | |
157 | if self.exit_code is not None: | |
158 | info['exit_code'] = self.exit_code | |
159 | return info | |
160 | ||
161 | def start(self): | |
162 | self.spawn_count += 1 | |
163 | ||
164 | def is_alive(self): | |
165 | return False | |
166 | ||
167 | def stop(self, errors=[]): | |
168 | """ | |
169 | Stop the process. Record any significant error messages in the errors parameter | |
170 | ||
171 | @param errors: error messages. stop() will record messages into this list. | |
172 | @type errors: [str] | |
173 | """ | |
174 | pass | |
175 | ||
176 | def get_exit_description(self): | |
177 | if self.exit_code is not None: | |
178 | if self.exit_code: | |
179 | return 'process has died [exit code %s]'%self.exit_code | |
180 | else: | |
181 | # try not to scare users about process exit | |
182 | return 'process has finished cleanly' | |
183 | else: | |
184 | return 'process has died' | |
185 | ||
186 | class DeadProcess(Process): | |
187 | """ | |
188 | Container class to maintain information about a process that has died. This | |
189 | container allows us to delete the actual Process but still maintain the metadata | |
190 | """ | |
191 | def __init__(self, p): | |
192 | super(DeadProcess, self).__init__(p.package, p.name, p.args, p.env, p.respawn) | |
193 | self.exit_code = p.exit_code | |
194 | self.lock = None | |
195 | self.spawn_count = p.spawn_count | |
196 | self.info = p.get_info() | |
197 | def get_info(self): | |
198 | return self.info | |
199 | def start(self): | |
200 | raise Exception("cannot call start on a dead process!") | |
201 | def is_alive(self): | |
202 | return False | |
203 | ||
204 | class ProcessListener(object): | |
205 | """ | |
206 | Listener class for L{ProcessMonitor} | |
207 | """ | |
208 | ||
209 | def process_died(self, process_name, exit_code): | |
210 | """ | |
211 | Notifies listener that process has died. This callback only | |
212 | occurs for processes that die during normal process monitor | |
213 | execution -- processes that are forcibly killed during | |
214 | ProcessMonitor shutdown are not reported. | |
215 | @param process_name: name of process | |
216 | @type process_name: str | |
217 | @param exit_code: exit code of process. If None, it means | |
218 | that ProcessMonitor was unable to determine an exit code. | |
219 | @type exit_code: int | |
220 | """ | |
221 | pass | |
222 | ||
223 | class ProcessMonitor(Thread): | |
224 | ||
225 | def __init__(self, name="ProcessMonitor"): | |
226 | Thread.__init__(self, name=name) | |
227 | self.procs = [] | |
228 | self.plock = RLock() | |
229 | self.is_shutdown = False | |
230 | self.done = False | |
231 | self.setDaemon(True) | |
232 | self.listeners = [] | |
233 | self.dead_list = [] | |
234 | # #885: ensure core procs | |
235 | self.core_procs = [] | |
236 | # #642: flag to prevent process monitor exiting prematurely | |
237 | self._registrations_complete = False | |
238 | ||
239 | def add_process_listener(self, l): | |
240 | """ | |
241 | Listener for process events. MUST be called before | |
242 | ProcessMonitor is running.See ProcessListener class. | |
243 | @param l: listener instance | |
244 | @type l: L{ProcessListener} | |
245 | """ | |
246 | self.listeners.append(l) | |
247 | ||
248 | def register(self, p): | |
249 | """ | |
250 | Register process with L{ProcessMonitor} | |
251 | @param p: Process | |
252 | @type p: L{Process} | |
253 | @raise PmonException: if process with same name is already registered | |
254 | """ | |
255 | e = None | |
256 | with self.plock: | |
257 | if self.has_process(p.name): | |
258 | e = PmonException("cannot add process with duplicate name '%s'"%p.name) | |
259 | elif self.is_shutdown: | |
260 | e = PmonException("cannot add process [%s] after process monitor has been shut down"%p.name) | |
261 | else: | |
262 | self.procs.append(p) | |
263 | if e: | |
264 | raise e | |
265 | ||
266 | def register_core_proc(self, p): | |
267 | """ | |
268 | Register core process with ProcessMonitor. Coreprocesses | |
269 | have special shutdown semantics. They are killed after all | |
270 | other processes, in reverse order in which they are added. | |
271 | @param p Process | |
272 | @type p: L{Process} | |
273 | @raise PmonException: if process with same name is already registered | |
274 | """ | |
275 | self.register(p) | |
276 | self.core_procs.append(p) | |
277 | ||
278 | def registrations_complete(self): | |
279 | """ | |
280 | Inform the process monitor that registrations are complete. | |
281 | After the registrations_complete flag is set, process monitor | |
282 | will exit if there are no processes left to monitor. | |
283 | """ | |
284 | self._registrations_complete = True | |
285 | ||
286 | def unregister(self, p): | |
287 | with self.plock: | |
288 | self.procs.remove(p) | |
289 | ||
290 | def has_process(self, name): | |
291 | """ | |
292 | @return: True if process is still be monitored. If False, process | |
293 | has died or was never registered with process | |
294 | @rtype: bool | |
295 | """ | |
296 | return len([p for p in self.procs if p.name == name]) > 0 | |
297 | ||
298 | def get_process(self, name): | |
299 | """ | |
300 | @return: process registered under \a name, or None | |
301 | @rtype: L{Process} | |
302 | """ | |
303 | with self.plock: | |
304 | v = [p for p in self.procs if p.name == name] | |
305 | if v: | |
306 | return v[0] | |
307 | ||
308 | def kill_process(self, name): | |
309 | """ | |
310 | Kill process that matches name. NOTE: a killed process will | |
311 | continue to show up as active until the process monitor thread | |
312 | has caught that it has died. | |
313 | @param name: Process name | |
314 | @type name: str | |
315 | @return: True if a process named name was removed from | |
316 | process monitor. A process is considered killed if its stop() | |
317 | method was called. | |
318 | @rtype: bool | |
319 | """ | |
320 | if not isinstance(name, basestring): | |
321 | raise PmonException("kill_process takes in a process name but was given: %s"%name) | |
322 | printlog("[%s] kill requested"%name) | |
323 | with self.plock: | |
324 | p = self.get_process(name) | |
325 | if p: | |
326 | try: | |
327 | # no need to accumulate errors, so pass in [] | |
328 | p.stop([]) | |
329 | except Exception as e: | |
330 | printerrlog("Exception: %s"%(str(e))) | |
331 | return True | |
332 | else: | |
333 | return False | |
334 | ||
335 | def shutdown(self): | |
336 | """ | |
337 | Shutdown the process monitor thread | |
338 | """ | |
339 | self.is_shutdown = True | |
340 | ||
341 | def get_active_names(self): | |
342 | """ | |
343 | @return [str]: list of active process names | |
344 | """ | |
345 | with self.plock: | |
346 | retval = [p.name for p in self.procs] | |
347 | return retval | |
348 | ||
349 | def get_process_names_with_spawn_count(self): | |
350 | """ | |
351 | @return: Two lists, where first | |
352 | list of active process names along with the number of times | |
353 | that process has been spawned. Second list contains dead process names | |
354 | and their spawn count. | |
355 | @rtype: [[(str, int),], [(str,int),]] | |
356 | """ | |
357 | with self.plock: | |
358 | actives = [(p.name, p.spawn_count) for p in self.procs] | |
359 | deads = [(p.name, p.spawn_count) for p in self.dead_list] | |
360 | retval = [actives, deads] | |
361 | return retval | |
362 | ||
363 | def run(self): | |
364 | """ | |
365 | thread routine of the process monitor. | |
366 | """ | |
367 | try: | |
368 | #don't let exceptions bomb thread, interferes with exit | |
369 | try: | |
370 | self._run() | |
371 | except: | |
372 | traceback.print_exc() | |
373 | finally: | |
374 | self._post_run() | |
375 | ||
376 | def _run(self): | |
377 | """ | |
378 | Internal run loop of ProcessMonitor | |
379 | """ | |
380 | plock = self.plock | |
381 | dead = [] | |
382 | respawn = [] | |
383 | while not self.is_shutdown: | |
384 | with plock: #copy self.procs | |
385 | procs = self.procs[:] | |
386 | if self.is_shutdown: | |
387 | break | |
388 | ||
389 | for p in procs: | |
390 | try: | |
391 | if not p.is_alive(): | |
392 | exit_code_str = p.get_exit_description() | |
393 | if p.respawn: | |
394 | printlog_bold("[%s] %s\nrespawning..."%(p.name, exit_code_str)) | |
395 | respawn.append(p) | |
396 | elif p.required: | |
397 | printerrlog('='*80+"REQUIRED process [%s] has died!\n%s\nInitiating shutdown!\n"%(p.name, exit_code_str)+'='*80) | |
398 | self.is_shutdown = True | |
399 | else: | |
400 | if p.exit_code: | |
401 | printerrlog("[%s] %s"%(p.name, exit_code_str)) | |
402 | else: | |
403 | printlog_bold("[%s] %s"%(p.name, exit_code_str)) | |
404 | dead.append(p) | |
405 | ||
406 | ## no need for lock as we require listeners be | |
407 | ## added before process monitor is launched | |
408 | for l in self.listeners: | |
409 | l.process_died(p.name, p.exit_code) | |
410 | ||
411 | except Exception, e: | |
412 | traceback.print_exc() | |
413 | #don't respawn as this is an internal error | |
414 | dead.append(p) | |
415 | if self.is_shutdown: | |
416 | break #stop polling | |
417 | for d in dead: | |
418 | try: | |
419 | self.unregister(d) | |
420 | # stop process, don't accumulate errors | |
421 | d.stop([]) | |
422 | ||
423 | # save process data to dead list | |
424 | with plock: | |
425 | self.dead_list.append(DeadProcess(d)) | |
426 | except Exception as e: | |
427 | printerrlog("Exception: %s"%(str(e))) | |
428 | ||
429 | # dead check is to make sure that ProcessMonitor at least | |
430 | # waits until its had at least one process before exiting | |
431 | if self._registrations_complete and dead and not self.procs and not respawn: | |
432 | printlog("all processes on machine have died, roslaunch will exit") | |
433 | self.is_shutdown = True | |
434 | del dead[:] | |
435 | for r in respawn: | |
436 | try: | |
437 | if self.is_shutdown: | |
438 | break | |
439 | printlog("[%s] restarting process"%r.name) | |
440 | # stop process, don't accumulate errors | |
441 | r.stop([]) | |
442 | r.start() | |
443 | except: | |
444 | traceback.print_exc() | |
445 | del respawn[:] | |
446 | time.sleep(0.1) #yield thread | |
447 | #moved this to finally block of _post_run | |
448 | #self._post_run() #kill all processes | |
449 | ||
450 | def _post_run(self): | |
451 | # this is already true entering, but go ahead and make sure | |
452 | self.is_shutdown = True | |
453 | # killall processes on run exit | |
454 | ||
455 | q = Queue.Queue() | |
456 | q.join() | |
457 | ||
458 | with self.plock: | |
459 | # make copy of core_procs for threadsafe usage | |
460 | core_procs = self.core_procs[:] | |
461 | ||
462 | # enqueue all non-core procs in reverse order for parallel kill | |
463 | # #526/885: ignore core procs | |
464 | [q.put(p) for p in reversed(self.procs) if not p in core_procs] | |
465 | ||
466 | # use 10 workers | |
467 | killers = [] | |
468 | for i in range(10): | |
469 | t = _ProcessKiller(q, i) | |
470 | killers.append(t) | |
471 | t.start() | |
472 | ||
473 | # wait for workers to finish | |
474 | q.join() | |
475 | shutdown_errors = [] | |
476 | ||
477 | # accumulate all the shutdown errors | |
478 | for t in killers: | |
479 | shutdown_errors.extend(t.errors) | |
480 | del killers[:] | |
481 | ||
482 | # #526/885: kill core procs last | |
483 | # we don't want to parallelize this as the master has to be last | |
484 | for p in reversed(core_procs): | |
485 | _kill_process(p, shutdown_errors) | |
486 | ||
487 | # delete everything except dead_list | |
488 | with self.plock: | |
489 | del core_procs[:] | |
490 | del self.procs[:] | |
491 | del self.core_procs[:] | |
492 | ||
493 | self.done = True | |
494 | ||
495 | if shutdown_errors: | |
496 | printerrlog("Shutdown errors:\n"+'\n'.join([" * %s"%e for e in shutdown_errors])) | |
497 | ||
498 | def _kill_process(p, errors): | |
499 | """ | |
500 | Routine for kill Process p with appropriate logging to screen and logfile | |
501 | ||
502 | @param p: process to kill | |
503 | @type p: Process | |
504 | @param errors: list of error messages from killed process | |
505 | @type errors: [str] | |
506 | """ | |
507 | try: | |
508 | printlog("[%s] killing on exit"%p.name) | |
509 | # we accumulate errors from each process so that we can print these at the end | |
510 | p.stop(errors) | |
511 | except Exception as e: | |
512 | printerrlog("Exception: %s"%(str(e))) | |
513 | ||
514 | class _ProcessKiller(Thread): | |
515 | ||
516 | def __init__(self, q, i): | |
517 | Thread.__init__(self, name="ProcessKiller-%s"%i) | |
518 | self.q = q | |
519 | self.errors = [] | |
520 | ||
521 | def run(self): | |
522 | q = self.q | |
523 | while not q.empty(): | |
524 | try: | |
525 | p = q.get(False) | |
526 | _kill_process(p, self.errors) | |
527 | q.task_done() | |
528 | except Queue.Empty: | |
529 | pass | |
530 | ||
531 | ||
532 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | """ | |
35 | Wrapper for running Python unittest within rosunit/rostest framework. | |
36 | """ | |
37 | from __future__ import with_statement | |
38 | ||
39 | import sys | |
40 | ||
41 | from .core import create_xml_runner, XML_OUTPUT_FLAG | |
42 | from .baretest import print_unittest_summary | |
43 | ||
44 | def unitrun(package, test_name, test, sysargs=None, coverage_packages=None): | |
45 | """ | |
46 | Wrapper routine from running python unitttests with | |
47 | JUnit-compatible XML output. This is meant for unittests that do | |
48 | not not need a running ROS graph (i.e. offline tests only). | |
49 | ||
50 | This enables JUnit-compatible test reporting so that | |
51 | test results can be reported to higher-level tools. | |
52 | ||
53 | WARNING: unitrun() will trigger a sys.exit() on test failure in | |
54 | order to properly exit with an error code. This routine is meant | |
55 | to be used as a main() routine, not as a library. | |
56 | ||
57 | @param package: name of ROS package that is running the test | |
58 | @type package: str | |
59 | @param coverage_packages: list of Python package to compute coverage results for. Defaults to package | |
60 | @type coverage_packages: [str] | |
61 | @param sysargs: (optional) alternate sys.argv | |
62 | @type sysargs: [str] | |
63 | """ | |
64 | if sysargs is None: | |
65 | # lazy-init sys args | |
66 | import sys | |
67 | sysargs = sys.argv | |
68 | ||
69 | import unittest | |
70 | ||
71 | if coverage_packages is None: | |
72 | coverage_packages = [package] | |
73 | ||
74 | #parse sysargs | |
75 | result_file = None | |
76 | for arg in sysargs: | |
77 | if arg.startswith(XML_OUTPUT_FLAG): | |
78 | result_file = arg[len(XML_OUTPUT_FLAG):] | |
79 | text_mode = '--text' in sysargs | |
80 | ||
81 | coverage_mode = '--cov' in sysargs or '--covhtml' in sysargs | |
82 | if coverage_mode: | |
83 | start_coverage(coverage_packages) | |
84 | ||
85 | # create and run unittest suite with our xmllrunner wrapper | |
86 | suite = unittest.TestLoader().loadTestsFromTestCase(test) | |
87 | if text_mode: | |
88 | result = unittest.TextTestRunner(verbosity=2).run(suite) | |
89 | else: | |
90 | result = create_xml_runner(package, test_name, result_file).run(suite) | |
91 | if coverage_mode: | |
92 | cov_html_dir = 'covhtml' if '--covhtml' in sysargs else None | |
93 | stop_coverage(coverage_packages, html=cov_html_dir) | |
94 | ||
95 | # test over, summarize results and exit appropriately | |
96 | print_unittest_summary(result) | |
97 | ||
98 | if not result.wasSuccessful(): | |
99 | import sys | |
100 | sys.exit(1) | |
101 | ||
102 | # coverage instance | |
103 | _cov = None | |
104 | def start_coverage(packages): | |
105 | global _cov | |
106 | try: | |
107 | import coverage | |
108 | try: | |
109 | _cov = coverage.coverage() | |
110 | # load previous results as we need to accumulate | |
111 | _cov.load() | |
112 | _cov.start() | |
113 | except coverage.CoverageException: | |
114 | print >> sys.stderr, "WARNING: you have an older version of python-coverage that is not support. Please update to the version provided by 'easy_install coverage'" | |
115 | except ImportError, e: | |
116 | print >> sys.stderr, """WARNING: cannot import python-coverage, coverage tests will not run. | |
117 | To install coverage, run 'easy_install coverage'""" | |
118 | ||
119 | def stop_coverage(packages, html=None): | |
120 | """ | |
121 | @param packages: list of packages to generate coverage reports for | |
122 | @type packages: [str] | |
123 | @param html: (optional) if not None, directory to generate html report to | |
124 | @type html: str | |
125 | """ | |
126 | if _cov is None: | |
127 | return | |
128 | import sys, os | |
129 | try: | |
130 | _cov.stop() | |
131 | # accumulate results | |
132 | _cov.save() | |
133 | ||
134 | # - update our own .coverage-modules file list for | |
135 | # coverage-html tool. The reason we read and rewrite instead | |
136 | # of append is that this does a uniqueness check to keep the | |
137 | # file from growing unbounded | |
138 | if os.path.exists('.coverage-modules'): | |
139 | with open('.coverage-modules','r') as f: | |
140 | all_packages = set([x for x in f.read().split('\n') if x.strip()] + packages) | |
141 | else: | |
142 | all_packages = set(packages) | |
143 | with open('.coverage-modules','w') as f: | |
144 | f.write('\n'.join(all_packages)+'\n') | |
145 | ||
146 | try: | |
147 | # list of all modules for html report | |
148 | all_mods = [] | |
149 | ||
150 | # iterate over packages to generate per-package console reports | |
151 | for package in packages: | |
152 | pkg = __import__(package) | |
153 | m = [v for v in sys.modules.values() if v and v.__name__.startswith(package)] | |
154 | all_mods.extend(m) | |
155 | ||
156 | # generate overall report and per module analysis | |
157 | _cov.report(m, show_missing=0) | |
158 | for mod in m: | |
159 | res = _cov.analysis(mod) | |
160 | print "\n%s:\nMissing lines: %s"%(res[0], res[3]) | |
161 | ||
162 | if html: | |
163 | ||
164 | print "="*80+"\ngenerating html coverage report to %s\n"%html+"="*80 | |
165 | _cov.html_report(all_mods, directory=html) | |
166 | except ImportError, e: | |
167 | print >> sys.stderr, "WARNING: cannot import '%s', will not generate coverage report"%package | |
168 | except ImportError, e: | |
169 | print >> sys.stderr, """WARNING: cannot import python-coverage, coverage tests will not run. | |
170 | To install coverage, run 'easy_install coverage'""" | |
171 | ||
172 |
0 | # Software License Agreement (BSD License) | |
1 | # | |
2 | # Copyright (c) 2008, Willow Garage, Inc. | |
3 | # All rights reserved. | |
4 | # | |
5 | # Redistribution and use in source and binary forms, with or without | |
6 | # modification, are permitted provided that the following conditions | |
7 | # are met: | |
8 | # | |
9 | # * Redistributions of source code must retain the above copyright | |
10 | # notice, this list of conditions and the following disclaimer. | |
11 | # * Redistributions in binary form must reproduce the above | |
12 | # copyright notice, this list of conditions and the following | |
13 | # disclaimer in the documentation and/or other materials provided | |
14 | # with the distribution. | |
15 | # * Neither the name of Willow Garage, Inc. nor the names of its | |
16 | # contributors may be used to endorse or promote products derived | |
17 | # from this software without specific prior written permission. | |
18 | # | |
19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
22 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
23 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
24 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
25 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
29 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 | # POSSIBILITY OF SUCH DAMAGE. | |
31 | # | |
32 | # Revision $Id$ | |
33 | ||
34 | from __future__ import with_statement | |
35 | ||
36 | import os | |
37 | import sys | |
38 | import time | |
39 | import unittest | |
40 | import logging | |
41 | ||
42 | import rospkg | |
43 | ||
44 | from . import pmon | |
45 | from . core import xml_results_file, create_xml_runner | |
46 | ||
47 | from .junitxml import print_summary, Result | |
48 | from .baretest import BareTestCase, print_runner_summary | |
49 | ||
50 | ||
51 | _NAME = 'rosunit' | |
52 | ||
53 | def rosunitmain(): | |
54 | from optparse import OptionParser | |
55 | parser = OptionParser(usage="usage: %prog [options] <file> [test args...]", prog=_NAME) | |
56 | parser.add_option("-t", "--text", | |
57 | action="store_true", dest="text_mode", default=False, | |
58 | help="Run with stdout output instead of XML output") | |
59 | parser.add_option("--time-limit", metavar="TIME_LIMIT", | |
60 | dest="time_limit", default=60, | |
61 | help="Set time limit for test") | |
62 | parser.add_option("--name", metavar="TEST_NAME", | |
63 | dest="test_name", default=None, | |
64 | help="Test name") | |
65 | parser.add_option("--package", metavar="PACKAGE_NAME", | |
66 | dest="pkg", default=None, | |
67 | help="Package name (optional)") | |
68 | (options, args) = parser.parse_args() | |
69 | ||
70 | if len(args) < 1: | |
71 | parser.error("You must supply a test file.") | |
72 | ||
73 | test_file = args[0] | |
74 | ||
75 | if options.test_name: | |
76 | test_name = options.test_name | |
77 | else: | |
78 | test_name = os.path.basename(test_file) | |
79 | if '.' in test_name: | |
80 | test_name = test_name[:test_name.rfind('.')] | |
81 | time_limit = float(options.time_limit) if options.time_limit else None | |
82 | ||
83 | # If the caller didn't tell us the package name, we'll try to infer it. | |
84 | # compute some common names we'll be using to generate test names and files | |
85 | pkg = options.pkg | |
86 | if not pkg: | |
87 | pkg = rospkg.get_package_name(test_file) | |
88 | if not pkg: | |
89 | print "Error: failed to determine package name for file '%s'; maybe you should supply the --package argument to rosunit?"%(test_file) | |
90 | sys.exit(1) | |
91 | ||
92 | try: | |
93 | runner_result = None | |
94 | results = Result('rosunit', 0, 0, 0) | |
95 | ||
96 | test_case = BareTestCase(test_file, args[1:], \ | |
97 | retry=0, time_limit=time_limit, \ | |
98 | test_name=test_name, text_mode=options.text_mode, package_name=pkg) | |
99 | suite = unittest.TestSuite() | |
100 | suite.addTest(test_case) | |
101 | ||
102 | if options.text_mode: | |
103 | result = unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite) | |
104 | else: | |
105 | results_file = xml_results_file(pkg, test_name, True) | |
106 | # the is_rostest really just means "wrapper" | |
107 | xml_runner = create_xml_runner(pkg, test_name, \ | |
108 | results_file=results_file, \ | |
109 | is_rostest=True) | |
110 | runner_result = xml_runner.run(suite) | |
111 | finally: | |
112 | pmon.pmon_shutdown() | |
113 | ||
114 | # summary is worthless if textMode is on as we cannot scrape .xml results | |
115 | results = test_case.results | |
116 | if not options.text_mode: | |
117 | print_runner_summary(runner_result, results) | |
118 | else: | |
119 | print "WARNING: overall test result is not accurate when --text is enabled" | |
120 | ||
121 | if runner_result is not None and not runner_result.wasSuccessful(): | |
122 | sys.exit(1) | |
123 | elif results.num_errors or results.num_failures: | |
124 | sys.exit(2) | |
125 | ||
126 | if __name__ == '__main__': | |
127 | rosunitmain() |
0 | """ | |
1 | XML Test Runner for PyUnit | |
2 | """ | |
3 | ||
4 | # Written by Sebastian Rittau <srittau@jroger.in-berlin.de> and placed in | |
5 | # the Public Domain. With contributions by Paolo Borelli. | |
6 | ||
7 | __revision__ = "$Id$" | |
8 | ||
9 | import os.path | |
10 | import re | |
11 | import sys | |
12 | import time | |
13 | import traceback | |
14 | import unittest | |
15 | from StringIO import StringIO | |
16 | from xml.sax.saxutils import escape | |
17 | ||
18 | from StringIO import StringIO | |
19 | ||
20 | ||
21 | class _TestInfo(object): | |
22 | ||
23 | """Information about a particular test. | |
24 | ||
25 | Used by _XMLTestResult. | |
26 | ||
27 | """ | |
28 | ||
29 | def __init__(self, test, time): | |
30 | (self._class, self._method) = test.id().rsplit(".", 1) | |
31 | self._time = time | |
32 | self._error = None | |
33 | self._failure = None | |
34 | ||
35 | @staticmethod | |
36 | def create_success(test, time): | |
37 | """Create a _TestInfo instance for a successful test.""" | |
38 | return _TestInfo(test, time) | |
39 | ||
40 | @staticmethod | |
41 | def create_failure(test, time, failure): | |
42 | """Create a _TestInfo instance for a failed test.""" | |
43 | info = _TestInfo(test, time) | |
44 | info._failure = failure | |
45 | return info | |
46 | ||
47 | @staticmethod | |
48 | def create_error(test, time, error): | |
49 | """Create a _TestInfo instance for an erroneous test.""" | |
50 | info = _TestInfo(test, time) | |
51 | info._error = error | |
52 | return info | |
53 | ||
54 | def print_report(self, stream): | |
55 | """Print information about this test case in XML format to the | |
56 | supplied stream. | |
57 | ||
58 | """ | |
59 | stream.write(' <testcase classname="%(class)s" name="%(method)s" time="%(time).4f">' % \ | |
60 | { | |
61 | "class": self._class, | |
62 | "method": self._method, | |
63 | "time": self._time, | |
64 | }) | |
65 | if self._failure != None: | |
66 | self._print_error(stream, 'failure', self._failure) | |
67 | if self._error != None: | |
68 | self._print_error(stream, 'error', self._error) | |
69 | stream.write('</testcase>\n') | |
70 | ||
71 | def print_report_text(self, stream): | |
72 | #stream.write(' <testcase classname="%(class)s" name="%(method)s" time="%(time).4f">' % \ | |
73 | # { | |
74 | # "class": self._class, | |
75 | # "method": self._method, | |
76 | # "time": self._time, | |
77 | # }) | |
78 | stream.write(self._method) | |
79 | if self._failure != None: | |
80 | stream.write(' ... FAILURE!\n') | |
81 | self._print_error_text(stream, 'failure', self._failure) | |
82 | if self._error != None: | |
83 | stream.write(' ... ERROR!\n') | |
84 | self._print_error_text(stream, 'error', self._error) | |
85 | if self._failure == None and self._error == None: | |
86 | stream.write(' ... ok\n') | |
87 | ||
88 | def _print_error(self, stream, tagname, error): | |
89 | """Print information from a failure or error to the supplied stream.""" | |
90 | text = escape(str(error[1])) | |
91 | stream.write('\n') | |
92 | stream.write(' <%s type="%s">%s\n' \ | |
93 | % (tagname, str(error[0].__name__), text)) | |
94 | tb_stream = StringIO() | |
95 | traceback.print_tb(error[2], None, tb_stream) | |
96 | stream.write(escape(tb_stream.getvalue())) | |
97 | stream.write(' </%s>\n' % tagname) | |
98 | stream.write(' ') | |
99 | ||
100 | def _print_error_text(self, stream, tagname, error): | |
101 | """Print information from a failure or error to the supplied stream.""" | |
102 | text = escape(str(error[1])) | |
103 | stream.write('%s: %s\n' \ | |
104 | % (tagname.upper(), text)) | |
105 | tb_stream = StringIO() | |
106 | traceback.print_tb(error[2], None, tb_stream) | |
107 | stream.write(escape(tb_stream.getvalue())) | |
108 | stream.write('-'*80 + '\n') | |
109 | ||
110 | class _XMLTestResult(unittest.TestResult): | |
111 | ||
112 | """A test result class that stores result as XML. | |
113 | ||
114 | Used by XMLTestRunner. | |
115 | ||
116 | """ | |
117 | ||
118 | def __init__(self, classname): | |
119 | unittest.TestResult.__init__(self) | |
120 | self._test_name = classname | |
121 | self._start_time = None | |
122 | self._tests = [] | |
123 | self._error = None | |
124 | self._failure = None | |
125 | ||
126 | def startTest(self, test): | |
127 | unittest.TestResult.startTest(self, test) | |
128 | self._error = None | |
129 | self._failure = None | |
130 | self._start_time = time.time() | |
131 | ||
132 | def stopTest(self, test): | |
133 | time_taken = time.time() - self._start_time | |
134 | unittest.TestResult.stopTest(self, test) | |
135 | if self._error: | |
136 | info = _TestInfo.create_error(test, time_taken, self._error) | |
137 | elif self._failure: | |
138 | info = _TestInfo.create_failure(test, time_taken, self._failure) | |
139 | else: | |
140 | info = _TestInfo.create_success(test, time_taken) | |
141 | self._tests.append(info) | |
142 | ||
143 | def addError(self, test, err): | |
144 | unittest.TestResult.addError(self, test, err) | |
145 | self._error = err | |
146 | ||
147 | def addFailure(self, test, err): | |
148 | unittest.TestResult.addFailure(self, test, err) | |
149 | self._failure = err | |
150 | ||
151 | def print_report(self, stream, time_taken, out, err): | |
152 | """Prints the XML report to the supplied stream. | |
153 | ||
154 | The time the tests took to perform as well as the captured standard | |
155 | output and standard error streams must be passed in.a | |
156 | ||
157 | """ | |
158 | stream.write('<testsuite errors="%(e)d" failures="%(f)d" ' % \ | |
159 | { "e": len(self.errors), "f": len(self.failures) }) | |
160 | stream.write('name="%(n)s" tests="%(t)d" time="%(time).3f">\n' % \ | |
161 | { | |
162 | "n": self._test_name, | |
163 | "t": self.testsRun, | |
164 | "time": time_taken, | |
165 | }) | |
166 | for info in self._tests: | |
167 | info.print_report(stream) | |
168 | stream.write(' <system-out><![CDATA[%s]]></system-out>\n' % out) | |
169 | stream.write(' <system-err><![CDATA[%s]]></system-err>\n' % err) | |
170 | stream.write('</testsuite>\n') | |
171 | ||
172 | def print_report_text(self, stream, time_taken, out, err): | |
173 | """Prints the text report to the supplied stream. | |
174 | ||
175 | The time the tests took to perform as well as the captured standard | |
176 | output and standard error streams must be passed in.a | |
177 | ||
178 | """ | |
179 | #stream.write('<testsuite errors="%(e)d" failures="%(f)d" ' % \ | |
180 | # { "e": len(self.errors), "f": len(self.failures) }) | |
181 | #stream.write('name="%(n)s" tests="%(t)d" time="%(time).3f">\n' % \ | |
182 | # { | |
183 | # "n": self._test_name, | |
184 | # "t": self.testsRun, | |
185 | # "time": time_taken, | |
186 | # }) | |
187 | for info in self._tests: | |
188 | info.print_report_text(stream) | |
189 | ||
190 | ||
191 | class XMLTestRunner(object): | |
192 | ||
193 | """A test runner that stores results in XML format compatible with JUnit. | |
194 | ||
195 | XMLTestRunner(stream=None) -> XML test runner | |
196 | ||
197 | The XML file is written to the supplied stream. If stream is None, the | |
198 | results are stored in a file called TEST-<module>.<class>.xml in the | |
199 | current working directory (if not overridden with the path property), | |
200 | where <module> and <class> are the module and class name of the test class. | |
201 | ||
202 | """ | |
203 | ||
204 | def __init__(self, stream=None): | |
205 | self._stream = stream | |
206 | self._path = "." | |
207 | ||
208 | def run(self, test): | |
209 | """Run the given test case or test suite.""" | |
210 | class_ = test.__class__ | |
211 | classname = class_.__module__ + "." + class_.__name__ | |
212 | if self._stream == None: | |
213 | filename = "TEST-%s.xml" % classname | |
214 | stream = file(os.path.join(self._path, filename), "w") | |
215 | stream.write('<?xml version="1.0" encoding="utf-8"?>\n') | |
216 | else: | |
217 | stream = self._stream | |
218 | ||
219 | result = _XMLTestResult(classname) | |
220 | start_time = time.time() | |
221 | ||
222 | # TODO: Python 2.5: Use the with statement | |
223 | old_stdout = sys.stdout | |
224 | old_stderr = sys.stderr | |
225 | sys.stdout = StringIO() | |
226 | sys.stderr = StringIO() | |
227 | ||
228 | try: | |
229 | test(result) | |
230 | try: | |
231 | out_s = sys.stdout.getvalue() | |
232 | except AttributeError: | |
233 | out_s = "" | |
234 | try: | |
235 | err_s = sys.stderr.getvalue() | |
236 | except AttributeError: | |
237 | err_s = "" | |
238 | finally: | |
239 | sys.stdout = old_stdout | |
240 | sys.stderr = old_stderr | |
241 | ||
242 | time_taken = time.time() - start_time | |
243 | result.print_report(stream, time_taken, out_s, err_s) | |
244 | ||
245 | result.print_report_text(sys.stdout, time_taken, out_s, err_s) | |
246 | ||
247 | if self._stream == None: | |
248 | stream.close() | |
249 | ||
250 | return result | |
251 | ||
252 | def _set_path(self, path): | |
253 | self._path = path | |
254 | ||
255 | path = property(lambda self: self._path, _set_path, None, | |
256 | """The path where the XML files are stored. | |
257 | ||
258 | This property is ignored when the XML file is written to a file | |
259 | stream.""") | |
260 | ||
261 | ||
262 | class XMLTestRunnerTest(unittest.TestCase): | |
263 | def setUp(self): | |
264 | self._stream = StringIO() | |
265 | ||
266 | def _try_test_run(self, test_class, expected): | |
267 | ||
268 | """Run the test suite against the supplied test class and compare the | |
269 | XML result against the expected XML string. Fail if the expected | |
270 | string doesn't match the actual string. All time attribute in the | |
271 | expected string should have the value "0.000". All error and failure | |
272 | messages are reduced to "Foobar". | |
273 | ||
274 | """ | |
275 | ||
276 | runner = XMLTestRunner(self._stream) | |
277 | runner.run(unittest.makeSuite(test_class)) | |
278 | ||
279 | got = self._stream.getvalue() | |
280 | # Replace all time="X.YYY" attributes by time="0.000" to enable a | |
281 | # simple string comparison. | |
282 | got = re.sub(r'time="\d+\.\d+"', 'time="0.000"', got) | |
283 | # Likewise, replace all failure and error messages by a simple "Foobar" | |
284 | # string. | |
285 | got = re.sub(r'(?s)<failure (.*?)>.*?</failure>', r'<failure \1>Foobar</failure>', got) | |
286 | got = re.sub(r'(?s)<error (.*?)>.*?</error>', r'<error \1>Foobar</error>', got) | |
287 | ||
288 | self.assertEqual(expected, got) | |
289 | ||
290 | def test_no_tests(self): | |
291 | """Regression test: Check whether a test run without any tests | |
292 | matches a previous run. | |
293 | ||
294 | """ | |
295 | class TestTest(unittest.TestCase): | |
296 | pass | |
297 | self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="0" time="0.000"> | |
298 | <system-out><![CDATA[]]></system-out> | |
299 | <system-err><![CDATA[]]></system-err> | |
300 | </testsuite> | |
301 | """) | |
302 | ||
303 | def test_success(self): | |
304 | """Regression test: Check whether a test run with a successful test | |
305 | matches a previous run. | |
306 | ||
307 | """ | |
308 | class TestTest(unittest.TestCase): | |
309 | def test_foo(self): | |
310 | pass | |
311 | self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> | |
312 | <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase> | |
313 | <system-out><![CDATA[]]></system-out> | |
314 | <system-err><![CDATA[]]></system-err> | |
315 | </testsuite> | |
316 | """) | |
317 | ||
318 | def test_failure(self): | |
319 | """Regression test: Check whether a test run with a failing test | |
320 | matches a previous run. | |
321 | ||
322 | """ | |
323 | class TestTest(unittest.TestCase): | |
324 | def test_foo(self): | |
325 | self.assert_(False) | |
326 | self._try_test_run(TestTest, """<testsuite errors="0" failures="1" name="unittest.TestSuite" tests="1" time="0.000"> | |
327 | <testcase classname="__main__.TestTest" name="test_foo" time="0.000"> | |
328 | <failure type="exceptions.AssertionError">Foobar</failure> | |
329 | </testcase> | |
330 | <system-out><![CDATA[]]></system-out> | |
331 | <system-err><![CDATA[]]></system-err> | |
332 | </testsuite> | |
333 | """) | |
334 | ||
335 | def test_error(self): | |
336 | """Regression test: Check whether a test run with a erroneous test | |
337 | matches a previous run. | |
338 | ||
339 | """ | |
340 | class TestTest(unittest.TestCase): | |
341 | def test_foo(self): | |
342 | raise IndexError() | |
343 | self._try_test_run(TestTest, """<testsuite errors="1" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> | |
344 | <testcase classname="__main__.TestTest" name="test_foo" time="0.000"> | |
345 | <error type="exceptions.IndexError">Foobar</error> | |
346 | </testcase> | |
347 | <system-out><![CDATA[]]></system-out> | |
348 | <system-err><![CDATA[]]></system-err> | |
349 | </testsuite> | |
350 | """) | |
351 | ||
352 | def test_stdout_capture(self): | |
353 | """Regression test: Check whether a test run with output to stdout | |
354 | matches a previous run. | |
355 | ||
356 | """ | |
357 | class TestTest(unittest.TestCase): | |
358 | def test_foo(self): | |
359 | print "Test" | |
360 | self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> | |
361 | <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase> | |
362 | <system-out><![CDATA[Test | |
363 | ]]></system-out> | |
364 | <system-err><![CDATA[]]></system-err> | |
365 | </testsuite> | |
366 | """) | |
367 | ||
368 | def test_stderr_capture(self): | |
369 | """Regression test: Check whether a test run with output to stderr | |
370 | matches a previous run. | |
371 | ||
372 | """ | |
373 | class TestTest(unittest.TestCase): | |
374 | def test_foo(self): | |
375 | print >>sys.stderr, "Test" | |
376 | self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> | |
377 | <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase> | |
378 | <system-out><![CDATA[]]></system-out> | |
379 | <system-err><![CDATA[Test | |
380 | ]]></system-err> | |
381 | </testsuite> | |
382 | """) | |
383 | ||
384 | class NullStream(object): | |
385 | """A file-like object that discards everything written to it.""" | |
386 | def write(self, buffer): | |
387 | pass | |
388 | ||
389 | def test_unittests_changing_stdout(self): | |
390 | """Check whether the XMLTestRunner recovers gracefully from unit tests | |
391 | that change stdout, but don't change it back properly. | |
392 | ||
393 | """ | |
394 | class TestTest(unittest.TestCase): | |
395 | def test_foo(self): | |
396 | sys.stdout = XMLTestRunnerTest.NullStream() | |
397 | ||
398 | runner = XMLTestRunner(self._stream) | |
399 | runner.run(unittest.makeSuite(TestTest)) | |
400 | ||
401 | def test_unittests_changing_stderr(self): | |
402 | """Check whether the XMLTestRunner recovers gracefully from unit tests | |
403 | that change stderr, but don't change it back properly. | |
404 | ||
405 | """ | |
406 | class TestTest(unittest.TestCase): | |
407 | def test_foo(self): | |
408 | sys.stderr = XMLTestRunnerTest.NullStream() | |
409 | ||
410 | runner = XMLTestRunner(self._stream) | |
411 | runner.run(unittest.makeSuite(TestTest)) | |
412 | ||
413 | ||
414 | class XMLTestProgram(unittest.TestProgram): | |
415 | def runTests(self): | |
416 | if self.testRunner is None: | |
417 | self.testRunner = XMLTestRunner() | |
418 | unittest.TestProgram.runTests(self) | |
419 | ||
420 | main = XMLTestProgram | |
421 | ||
422 | ||
423 | if __name__ == "__main__": | |
424 | main(module=None) |
0 | #!/usr/bin/env python | |
1 | # Software License Agreement (BSD License) | |
2 | # | |
3 | # Copyright (c) 2008, Willow Garage, Inc. | |
4 | # All rights reserved. | |
5 | # | |
6 | # Redistribution and use in source and binary forms, with or without | |
7 | # modification, are permitted provided that the following conditions | |
8 | # are met: | |
9 | # | |
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. | |
19 | # | |
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. | |
32 | # | |
33 | # Revision $Id: $ | |
34 | ||
35 | import os | |
36 | import io | |
37 | import sys | |
38 | import unittest | |
39 | import tempfile | |
40 | import shutil | |
41 | ||
42 | junitxml = None | |
43 | ||
44 | ## Basic test of xmlresult functionality of reading gtest xml files and | |
45 | ## summarizing their results into a new file. | |
46 | class MockResult(): | |
47 | def __init__(self, directory, filename, suites = [], noSuitesRoot = False): | |
48 | self.filename = os.path.join(directory, filename) | |
49 | self.suites = suites | |
50 | # whether to suppress <testsuites> root node | |
51 | self.noSuitesRoot = noSuitesRoot | |
52 | ||
53 | class MockSuite(): | |
54 | def __init__(self, cases, name, tests = 0, errors = 0, fail = 0, time = 1): | |
55 | self.cases = cases | |
56 | self.tests = tests | |
57 | self.time = time | |
58 | self.fail = fail | |
59 | self.errors = errors | |
60 | self.name = name | |
61 | ||
62 | ||
63 | class MockCase(): | |
64 | def __init__(self, name, errorList = [], classname="", time = 1): | |
65 | self.classname = classname | |
66 | self.name = name | |
67 | self.time = time | |
68 | self.errorList = errorList | |
69 | ||
70 | class MockErrorType(Exception): | |
71 | def __init__(self, value, etype = ''): | |
72 | self.value = value | |
73 | self.__name__ = value | |
74 | self.type = etype | |
75 | ||
76 | def _writeMockResultFile(result): | |
77 | "writes a test result as a gtest compatible test runner would do" | |
78 | with open(result.filename, 'w') as f: | |
79 | f.write(u"""<?xml version="1.0" encoding="UTF-8"?>""") | |
80 | if len(result.suites) > 1 or result.noSuitesRoot == False: | |
81 | f.write(u"""<testsuites>\n""") | |
82 | for suite in result.suites: | |
83 | f.write('<testsuite tests="'+str(suite.tests)+'" failures="'+str(suite.fail)+'" time="'+str(suite.time)+'" errors="'+str(suite.errors)+'" name="'+suite.name+'">\n') | |
84 | for case in suite.cases: | |
85 | f.write('<testcase name="'+case.name+'" status="run" time="'+str(case.time)+'" classname="'+case.classname+'">\n') | |
86 | for error in case.errorList: | |
87 | f.write('<failure message="'+error.value+'" type="'+error.value+'"/>\n') | |
88 | f.write('</testcase>\n') | |
89 | f.write('</testsuite>\n') | |
90 | if len(result.suites) > 1 or result.noSuitesRoot == False: | |
91 | f.write(u"""</testsuites>\n""") | |
92 | ||
93 | ||
94 | class XmlResultTestRead(unittest.TestCase): | |
95 | ||
96 | def setUp(self): | |
97 | # lazy-import to get coverage | |
98 | global junitxml | |
99 | if junitxml is None: | |
100 | import rosunit.junitxml | |
101 | junitxml = rosunit.junitxml | |
102 | ||
103 | self.directory = tempfile.mkdtemp() | |
104 | ||
105 | # setting up mock results as dict so results can be checked individually | |
106 | self.mockresults={ | |
107 | "empty": MockResult(self.directory, "empty.xml", []), | |
108 | "emptysuite": MockResult(self.directory, "emptysuite.xml", [MockSuite([], "emptySuite", 0, 0, 0, 0)]), | |
109 | "succ1": MockResult(self.directory, "succ1.xml", [MockSuite([MockCase("succCase")],"succ1suite", 1, 0, 0, 1)]), | |
110 | "err1": MockResult(self.directory, "err1.xml", [MockSuite([MockCase("errCase")],"err1suite", 1, 1, 0, 1)]), | |
111 | "fail1": MockResult(self.directory, "fail1.xml", [MockSuite([MockCase("failCase")],"fail1suite", 1, 0, 1, 1)]), | |
112 | "noroot": MockResult(self.directory, "succ1.xml", [MockSuite([MockCase("succCase")],"succ1suite", 1, 0, 0, 1)], noSuitesRoot = True), | |
113 | "multicase": MockResult(self.directory, | |
114 | "multicase.xml", | |
115 | [MockSuite([MockCase("succCase"), | |
116 | MockCase("errCase"), | |
117 | MockCase("failCase")], | |
118 | "succ1suite", 3, 1, 1, time = 3)]), | |
119 | "multisuite": MockResult(self.directory, | |
120 | "multisuite.xml", | |
121 | [MockSuite([MockCase("succCase")],"succ1suite", 1, 0, 0, 1), | |
122 | MockSuite([MockCase("errCase")],"err1suite", 1, 1, 0, 1), | |
123 | MockSuite([MockCase("failCase")],"fail1suite", 1, 0, 1, 1)]) | |
124 | } | |
125 | ||
126 | ||
127 | for name, result in self.mockresults.items(): | |
128 | _writeMockResultFile(result) | |
129 | ||
130 | def tearDown(self): | |
131 | shutil.rmtree(self.directory) | |
132 | #pass | |
133 | ||
134 | def testReadNoSuites(self): | |
135 | result = junitxml.read(self.mockresults["empty"].filename, "fooname") | |
136 | self.assert_(result is not None) | |
137 | self.assertEquals(0.0, result.time) | |
138 | self.assertEquals(0, result.num_tests) | |
139 | self.assertEquals(0, result.num_errors) | |
140 | self.assertEquals(0, result.num_failures) | |
141 | ||
142 | def testReadEmptySuite(self): | |
143 | result = junitxml.read(self.mockresults["emptysuite"].filename, "fooname") | |
144 | self.assert_(result is not None) | |
145 | self.assertEquals(0.0, result.time) | |
146 | self.assertEquals(0, result.num_tests) | |
147 | self.assertEquals(0, result.num_errors) | |
148 | self.assertEquals(0, result.num_failures) | |
149 | ||
150 | def testReadSuccess(self): | |
151 | result = junitxml.read(self.mockresults["succ1"].filename, "fooname") | |
152 | self.assert_(result is not None) | |
153 | self.assertEquals(1.0, result.time) | |
154 | self.assertEquals(1, result.num_tests) | |
155 | self.assertEquals(0, result.num_errors) | |
156 | self.assertEquals(0, result.num_failures) | |
157 | ||
158 | def testReadError(self): | |
159 | result = junitxml.read(self.mockresults["err1"].filename, "fooname") | |
160 | self.assert_(result is not None) | |
161 | self.assertEquals(1.0, result.time) | |
162 | self.assertEquals(1, result.num_tests) | |
163 | self.assertEquals(1, result.num_errors) | |
164 | self.assertEquals(0, result.num_failures) | |
165 | ||
166 | def testReadFail(self): | |
167 | result = junitxml.read(self.mockresults["fail1"].filename, "fooname") | |
168 | self.assert_(result is not None) | |
169 | self.assertEquals(1.0, result.time) | |
170 | self.assertEquals(1, result.num_tests) | |
171 | self.assertEquals(0, result.num_errors) | |
172 | self.assertEquals(1, result.num_failures) | |
173 | ||
174 | def testReadMulticase(self): | |
175 | result = junitxml.read(self.mockresults["multicase"].filename, "fooname") | |
176 | self.assert_(result is not None) | |
177 | self.assertEquals(3.0, result.time) | |
178 | self.assertEquals(3, result.num_tests) | |
179 | self.assertEquals(1, result.num_errors) | |
180 | self.assertEquals(1, result.num_failures) | |
181 | ||
182 | def testReadMultisuite(self): | |
183 | result = junitxml.read(self.mockresults["multisuite"].filename, "fooname") | |
184 | self.assert_(result is not None) | |
185 | self.assertEquals(3.0, result.time) | |
186 | self.assertEquals(3, result.num_tests) | |
187 | self.assertEquals(1, result.num_errors) | |
188 | self.assertEquals(1, result.num_failures) |