$search
00001 #! /usr/bin/env python 00002 00003 # Copyright (c) 2008, Willow Garage, Inc. 00004 # All rights reserved. 00005 # 00006 # Redistribution and use in source and binary forms, with or without 00007 # modification, are permitted provided that the following conditions are met: 00008 # 00009 # * Redistributions of source code must retain the above copyright 00010 # notice, this list of conditions and the following disclaimer. 00011 # * Redistributions in binary form must reproduce the above copyright 00012 # notice, this list of conditions and the following disclaimer in the 00013 # documentation and/or other materials provided with the distribution. 00014 # * Neither the name of Willow Garage, Inc. nor the names of its 00015 # contributors may be used to endorse or promote products derived from 00016 # this software without specific prior written permission. 00017 # 00018 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 00019 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00020 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00021 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 00022 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 00023 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 00024 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 00025 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 00026 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 00027 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00028 # POSSIBILITY OF SUCH DAMAGE. 00029 00030 from __future__ import with_statement 00031 00032 import os 00033 import re 00034 import distutils.version 00035 import sys, string 00036 import subprocess 00037 import getopt 00038 import roslib 00039 import roslib.rospack 00040 import roslib.rosenv 00041 import random 00042 import tempfile 00043 import shutil 00044 00045 from math import sqrt 00046 from optparse import OptionParser 00047 00048 00049 license_map = { 00050 "bsd": "green", 00051 "zlib": "green", 00052 "apache": "green", 00053 "free": "green", 00054 "bsl1.0": "green", 00055 "lgpl": "dodgerblue", 00056 "mit": "dodgerblue", 00057 "gpl": "orange", 00058 "research-only": "red", 00059 } 00060 00061 status_map = { 00062 "doc reviewed": "green", 00063 "api cleared": "dodgerblue", 00064 "api conditionally cleared": "orange", 00065 "proposal cleared": "pink", 00066 "unreviewed": "red", 00067 "experimental": "yellow", 00068 "3rdparty": "white", 00069 "3rdparty doc reviewed": "green", 00070 "na": "gray85", 00071 "test": "gray85", 00072 "deprecated": "magenta", 00073 "ROS_BUILD_BLACKLIST": "black" 00074 } 00075 00076 00077 class PackageCharacteristics: 00078 def __init__(self): 00079 self.color_by = None 00080 00081 self._file_colors = None 00082 self._status_colors = {} 00083 self._rosmake_result_colors = {} 00084 self._license_color = {} 00085 self._license = {} 00086 self._review_status = {} 00087 self._review_notes = {} 00088 00089 00090 def set_color_by(self, method, option=None): 00091 self.color_by = method 00092 if self.color_by == "file": 00093 self._color_filename = option 00094 00095 def load_color_from_file(self): 00096 if self._file_colors: 00097 return True 00098 with open(self._color_filename,'r') as color_file: 00099 self._file_colors = {} 00100 for line in color_file: 00101 try: 00102 k, v = line.split() 00103 self.file_colors[k] = v 00104 except: 00105 print "ERROR: badly formatted color line: %s"%line 00106 00107 def get_file_color(self, pkg): 00108 self.load_color_from_file() 00109 return self._file_colors.get(pkg, 'purple') 00110 00111 def get_license(self, pkg): 00112 if pkg in self._license: 00113 return self._license[pkg] 00114 self._license[pkg] = roslib.manifest.parse_file(roslib.manifest.manifest_file(pkg)).license.lower() 00115 return self._license[pkg] 00116 00117 def get_license_color(self, pkg): 00118 if pkg in self._license_color: 00119 return self._license_color[pkg] 00120 00121 license = self.get_license(pkg) 00122 self._license_color[pkg] = license_map.get(license, 'purple') 00123 00124 if not license in license_map: 00125 print "Unknown: ", pkg, license 00126 00127 return self._license_color[pkg] 00128 00129 def get_review_status(self, pkg): 00130 if pkg in self._review_status: 00131 return self._review_status[pkg] 00132 f = roslib.manifest.manifest_file(pkg) 00133 try: 00134 self._review_status[pkg] = roslib.manifest.parse_file(f).status.lower() 00135 except: 00136 print "error parsing manifest '%s'"%f 00137 00138 # show blacklisting 00139 if os.path.exists(os.path.join(os.path.dirname(f), "ROS_BUILD_BLACKLIST")): 00140 #print f, "is blacklisted" 00141 self._review_status[pkg] = "ROS_BUILD_BLACKLIST" 00142 return self._review_status[pkg] 00143 00144 def get_review_notes(self, pkg): 00145 if pkg in self._review_notes: 00146 return self._review_notes[pkg] 00147 f = roslib.manifest.manifest_file(pkg) 00148 try: 00149 self._review_notes[pkg] = roslib.manifest.parse_file(f).notes 00150 except: 00151 print "error parsing manifest '%s'"%f 00152 00153 # show blacklisting 00154 if os.path.exists(os.path.join(os.path.dirname(f), "ROS_BUILD_BLACKLIST")): 00155 #print f, "is blacklisted" 00156 self._review_notes[pkg] +=" ROS_BUILD_BLACKLIST" 00157 # show ROS_NOBUILD flag 00158 if os.path.exists(os.path.join(os.path.dirname(f), "ROS_NOBUILD")): 00159 print f, "is installed" 00160 self._review_notes[pkg] +=" ROS_NOBUILD" 00161 return self._review_notes[pkg] 00162 00163 def get_review_color(self, pkg): 00164 status = self.get_review_status(pkg) 00165 return status_map.get(status, 'purple') 00166 00167 00168 def get_color(self, pkg): 00169 if self.color_by == "file": 00170 return self.get_file_color(pkg) 00171 if self.color_by == "review": 00172 return self.get_review_color(pkg) 00173 if self.color_by == "license": 00174 return self.get_license_color(pkg) 00175 else: 00176 return "gainsboro" 00177 00178 def get_color_palate(self): 00179 if self.color_by == "review": 00180 return status_map 00181 if self.color_by == "license": 00182 return license_map 00183 else: 00184 return None 00185 00186 rosmakeall_color_map = { "rosmakeall-testfailures.txt": "orange", "rosmakeall-buildfailures.txt": "red"} 00187 00188 def get_rosmakeall_color(): 00189 path = roslib.rosenv.get_ros_root() 00190 color_dict = {} 00191 for f in rosmakeall_color_map: 00192 c = rosmakeall_color_map[f] 00193 try: 00194 with open(path+"/"+f) as input: 00195 for l in input: 00196 color_dict[l.strip()] = c 00197 except IOError: 00198 print >> sys.stderr, "Couldn't find %s"%f 00199 sys.exit(1) 00200 return color_dict 00201 00202 00203 00204 class HelperMethods: 00205 def __init__(self): 00206 self._stack_of = {} 00207 self._packages_of = {} 00208 self._deps = {} 00209 self._deps1 = {} 00210 self._stack_deps = {} 00211 self._stack_deps1 = {} 00212 self._deps_on = {} 00213 self._deps_on1 = {} 00214 self.verbose = False 00215 self.display_test_packages = True 00216 00217 def get_stack_of(self, pkg): 00218 if pkg not in self._stack_of: 00219 stack = roslib.stacks.stack_of(pkg) 00220 if not stack: 00221 stack = "None" 00222 self._stack_of[pkg] = stack 00223 #print "hit cache" 00224 return self._stack_of[pkg] 00225 00226 def get_packages_of(self, stack): 00227 if stack == "None": 00228 return set() 00229 if stack not in self._packages_of: 00230 packages = roslib.stacks.packages_of(stack) 00231 self._packages_of[stack] = packages 00232 #print "hit cache" 00233 return self._packages_of[stack] 00234 00235 def get_deps(self, pkg): 00236 if pkg not in self._deps: 00237 stack = roslib.rospack.rospack_depends(pkg) 00238 self._deps[pkg] = stack 00239 #print "hit cache" 00240 return self._deps[pkg] 00241 00242 def get_deps1(self, pkg): 00243 if pkg not in self._deps1: 00244 stack = roslib.rospack.rospack_depends_1(pkg) 00245 self._deps1[pkg] = stack 00246 #print "hit cache" 00247 return self._deps1[pkg] 00248 00249 def get_stack_deps(self, stack): 00250 if stack not in self._stack_deps: 00251 deps = roslib.rospack.rosstack_depends(stack) 00252 self._stack_deps[stack] = deps 00253 #print "hit cache" 00254 return self._stack_deps[stack] 00255 00256 def get_stack_deps1(self, stack): 00257 if stack not in self._stack_deps1: 00258 deps = roslib.rospack.rosstack_depends_1(stack) 00259 self._stack_deps1[stack] = deps 00260 #print "hit cache" 00261 return self._stack_deps1[stack] 00262 00263 def get_deps_on(self, pkg): 00264 if pkg not in self._deps_on: 00265 stack = roslib.rospack.rospack_depends_on(pkg) 00266 self._deps_on[pkg] = stack 00267 #print "hit cache" 00268 return self._deps_on[pkg] 00269 00270 def get_deps_on1(self, pkg): 00271 if pkg not in self._deps_on1: 00272 stack = roslib.rospack.rospack_depends_on_1(pkg) 00273 self._deps_on1[pkg] = stack 00274 #print "hit cache" 00275 return self._deps_on1[pkg] 00276 00277 ## Get all the dependencies of dependent packages (deduplicated) 00278 def get_child_deps(self, pkg): 00279 accum = set() 00280 for dep in self.get_deps1(pkg): 00281 accum.update(self.get_deps(dep)) 00282 return accum 00283 00284 ## Get all the dependencies of dependent packages (deduplicated) 00285 def get_child_stack_deps(self, stack): 00286 accum = set() 00287 for dep in self.get_stack_deps1(stack): 00288 accum.update(self.get_stack_deps(dep)) 00289 return accum 00290 00291 def get_internal_child_deps(self, pkg, stack = None): 00292 if stack == "None": 00293 return set() 00294 if not stack: 00295 stack = self.get_stack_of(pkg) 00296 if not stack or stack == "None": 00297 return set() 00298 00299 internal_deps = self.get_internal_pkg_dependencies(pkg, stack) 00300 accum = set() 00301 for dep in internal_deps: 00302 accum.update(self.get_deps1(dep)) 00303 00304 return accum 00305 00306 def get_depth(self, all_pkgs, pkg, depth): 00307 for pkg_dep in (set(self.get_deps1(pkg)) & all_pkgs): 00308 depth = max(depth, self.get_depth(all_pkgs, pkg_dep, depth + 1)) 00309 return depth 00310 00311 def build_rank(self, all_pkgs): 00312 rank = {} 00313 for pkg in all_pkgs: 00314 depth = self.get_depth(all_pkgs, pkg, 0) 00315 if depth in rank: 00316 rank[depth].append(pkg) 00317 else: 00318 rank[depth] = [pkg] 00319 00320 return rank 00321 00322 def get_external_pkg_dependencies(self, pkg, stack=None): 00323 if not stack: 00324 stack = self.get_stack_of(pkg) 00325 dependent_pkgs = self.get_deps1(pkg) 00326 return [ext for ext in dependent_pkgs if not self.get_stack_of(ext) == stack] 00327 00328 def get_internal_pkg_dependencies(self, pkg, stack = None): 00329 if not stack: 00330 stack = self.get_stack_of(pkg) 00331 dependent_pkgs = self.get_deps1(pkg) 00332 return [ext for ext in dependent_pkgs if self.get_stack_of(ext) == stack] 00333 00334 00335 def group_pkgs_by_stack(self, pkgs): 00336 stacks = {} 00337 for p in pkgs: 00338 stack = self.get_stack_of(p) 00339 if not stack: 00340 continue 00341 if stack in stacks: 00342 stacks[stack].add(p) 00343 else: 00344 stacks[stack] = set([p]) 00345 return stacks 00346 00347 def get_stack_depth(self, pkg, depth): 00348 for stack_dep in self.get_deps1(pkg): 00349 depth = max(depth, get_stack_depth(stack_dep, depth + 1)) 00350 return depth 00351 00352 def compute_stack_ranks(self): 00353 rank = {} 00354 for stack in roslib.stacks.list_stacks(): 00355 depth = self.get_stack_depth(stack, 0) 00356 if depth in rank: 00357 rank[depth].append(stack) 00358 else: 00359 rank[depth] = [stack] 00360 00361 return rank 00362 00363 00364 # cluster definitons 00365 def build_stack_list(self, stack):#, include, exclude): 00366 if stack == "None": 00367 stack_contents = set() 00368 else: 00369 stack_contents = set(roslib.stacks.packages_of(stack)) 00370 00371 external_dependencies = set() 00372 for pkg in stack_contents: 00373 external_dependencies.update(self.get_external_pkg_dependencies(pkg)) 00374 00375 external_stack_dependencies = self.group_pkgs_by_stack(external_dependencies) 00376 00377 return stack_contents, external_stack_dependencies 00378 00379 00380 def dict_to_set(self, dict): 00381 accum = set() 00382 for key, val in dict.iteritems(): 00383 accum.update(val) 00384 return accum 00385 00386 def generate_stack(self, stack, type, pkg_characterists): 00387 00388 print "Generating Graphviz Intermediate" 00389 outfile = tempfile.NamedTemporaryFile() 00390 outfile.write( """digraph ros { 00391 //size="11,8"; 00392 //size="100,40"; 00393 //clusterrank=global; 00394 node [color=gainsboro, style=filled]; 00395 ranksep = 2.0; 00396 compound=true; 00397 00398 """) 00399 colors = ['red', 'blue', 'green', 'yellow', 'gold', 'orange','chocolate', 'gray44', 'deeppink', 'black'] 00400 # create the legend 00401 color_palate = pkg_characterists.get_color_palate() 00402 if False and color_palate: 00403 outfile.write(' subgraph cluster__legend { style=bold; color=black; label = "Color Legend"; ') 00404 for type in color_palate: 00405 text_color="black" 00406 bg_color = color_palate[type] 00407 if bg_color == "black": 00408 text_color = "white" 00409 outfile.write('"%s" [color="%s", fontcolor="%s"];\n '%(type, bg_color, text_color)) 00410 outfile.write('}\n') 00411 base_color = colors[random.randrange(0, len(colors))] 00412 try: 00413 stack_dir = roslib.stacks.get_stack_dir(stack) 00414 except roslib.exceptions.ROSLibException, ex: 00415 stack_dir = "None" 00416 outfile.write(' subgraph cluster__%s { style=bold; color=%s; label = "%s \\n (%s)"; '%(stack, base_color, stack, stack_dir)) 00417 internal, external = self.build_stack_list(stack) 00418 if not self.display_test_packages: 00419 internal = set([p for p in internal if not p.startswith("test_")]) 00420 for s in external: 00421 external[s] = set([p for p in external[s] if not p.startswith("test_")]) 00422 00423 00424 for pkg in internal: 00425 outfile.write(' "%s" ;'%pkg) 00426 for s in external: 00427 try: 00428 stack_dir = roslib.stacks.get_stack_dir(s) 00429 except roslib.exceptions.ROSLibException, ex: 00430 stack_dir = "None" 00431 outfile.write(' subgraph cluster__%s_%s { style=bold; color=%s; label = "Stack: %s \\n (%s)"; '%(stack, s, base_color, s, stack_dir)) 00432 for p in external[s]: 00433 outfile.write(' "%s.%s.%s" [ label = "%s"];'%(stack, s, p, p)) 00434 outfile.write('}\n') 00435 outfile.write('}\n') 00436 00437 external_deps_connected = set() 00438 for pkg in internal: 00439 deps = self.get_deps1(pkg) 00440 node_args = [] 00441 ##Coloring 00442 color = pkg_characterists.get_color(pkg) 00443 node_args.append("color=%s"%color) 00444 if color == 'black': 00445 node_args.append("fontcolor=white") 00446 00447 notes = pkg_characterists.get_review_notes(pkg) 00448 if len(notes) > 0: 00449 node_args.append('label="%s\\n(%s)"' % (pkg, notes)) 00450 00451 if pkg in internal: 00452 outfile.write(' "%s" [%s];\n'%(pkg, ', '.join(node_args))) 00453 00454 00455 00456 00457 ## Edges 00458 for dep in deps: 00459 if not self.verbose and dep in (self.get_internal_child_deps(pkg)):# & (internal | self.dict_to_set(external))): 00460 #print "Failed to find", dep, "in ", self.get_internal_child_deps(pkg) 00461 continue 00462 00463 if dep in (internal | self.dict_to_set(external)): #Draw edges to all dependencies 00464 local_stack = self.get_stack_of(pkg) 00465 dependent_stack = self.get_stack_of(dep) 00466 #if local_stack not in args or not local_stack: 00467 # continue 00468 if dependent_stack == local_stack: 00469 outfile.write( ' "%s" -> "%s";\n' % (pkg, dep)) 00470 elif dependent_stack: 00471 intermediate = "%s.%s.%s"%(local_stack, dependent_stack, dep) 00472 outfile.write( ' "%s" -> "%s"[color="blue", style="dashed"];\n' % (pkg, intermediate)) 00473 # outfile.write('}\n') 00474 00475 if False: 00476 rank = self.build_rank(internal) 00477 print rank 00478 for key in rank: 00479 if len(rank[key]) > 1: 00480 outfile.write('{ rank = same;') 00481 print "key", key 00482 for pkg_rank in rank[key]: 00483 #if pkg_rank in rank.keys(): 00484 outfile.write(' "%s" ;'%pkg_rank) 00485 outfile.write('}\n') 00486 00487 outfile.write( '}\n') 00488 outfile.flush() # write to disk, but don't delete 00489 00490 #shutil.copyfile(outfile.name, "NO#COMMIT.gv") 00491 00492 00493 try: 00494 output_filename = "stack_%s.%s"%(stack, type) 00495 print "Generating output %s"%output_filename 00496 subprocess.check_call(["dot", "-T%s"%type, outfile.name, "-o", output_filename]) 00497 print "%s generated"%output_filename 00498 except subprocess.CalledProcessError: 00499 print >> sys.stderr, "failed to generate %s"%output_filename 00500 00501 return output_filename 00502 00503 def generate_composite(self, stacks, type, pkg_characterists, display_image = True): 00504 stack_files = {} 00505 if display_image: 00506 for s in stacks: 00507 stack_files[s] = self.generate_stack(s, type, pkg_characterists) 00508 00509 print "Generating Graphviz Intermediate" 00510 outfile = tempfile.NamedTemporaryFile() 00511 outfile.write( """digraph ros { 00512 //size="11,8"; 00513 //size="100,40"; 00514 //clusterrank=global; 00515 node [color=gainsboro, style=filled, shape=box]; 00516 ranksep = 2.0; 00517 compound=true; 00518 00519 """) 00520 colors = ['red', 'blue', 'green', 'yellow', 'gold', 'orange','chocolate', 'gray44', 'deeppink', 'black'] 00521 # create the legend 00522 color_palate = pkg_characterists.get_color_palate() 00523 if False and color_palate: 00524 outfile.write(' subgraph cluster__legend { style=bold; color=black; label = "Color Legend"; ') 00525 for type in color_palate: 00526 text_color="black" 00527 bg_color = color_palate[type] 00528 if bg_color == "black": 00529 text_color = "white" 00530 outfile.write('"%s" [color="%s", fontcolor="%s"];\n '%(type, bg_color, text_color)) 00531 outfile.write('}\n') 00532 #base_color = colors[random.randrange(0, len(colors))] 00533 #try: 00534 # stack_dir = roslib.stacks.get_stack_dir(stack) 00535 #except roslib.exceptions.ROSLibException, ex: 00536 # stack_dir = "None" 00537 00538 if False: 00539 outfile.write(' subgraph cluster__%s { style=bold; color=%s; label = "%s \\n (%s)"; '%(stack, base_color, stack, stack_dir)) 00540 internal, external = self.build_stack_list(stack) 00541 for pkg in internal: 00542 outfile.write(' "%s" ;'%pkg) 00543 for s in external: 00544 try: 00545 stack_dir = roslib.stacks.get_stack_dir(s) 00546 except roslib.exceptions.ROSLibException, ex: 00547 stack_dir = "None" 00548 outfile.write(' subgraph cluster__%s_%s { style=bold; color=%s; label = "Stack: %s \\n (%s)"; '%(stack, s, base_color, s, stack_dir)) 00549 for p in external[s]: 00550 outfile.write(' "%s.%s.%s" [ label = "%s"];'%(stack, s, p, p)) 00551 outfile.write('}\n') 00552 outfile.write('}\n') 00553 00554 for stack in stacks: 00555 #deps = self.get_deps1(pkg) 00556 deps = roslib.rospack.rosstack_depends_1(stack) 00557 node_args = [] 00558 00559 00560 if stack in stack_files and display_image: 00561 node_args.append('image="%s"'%stack_files[stack]) 00562 ##Coloring 00563 #color = pkg_characterists.get_color(pkg) 00564 #node_args.append("color=%s"%color) 00565 #if color == 'black': 00566 # node_args.append("fontcolor=white") 00567 00568 #notes = pkg_characterists.get_review_notes(pkg) 00569 #if len(notes) > 0: 00570 # node_args.append('label="%s\\n(%s)"' % (pkg, notes)) 00571 00572 #if pkg in all_pkgs: 00573 # outfile.write(' "%s" [%s];\n'%(pkg, ', '.join(node_args))) 00574 outfile.write(' "%s" [%s];\n'%(stack, ', '.join(node_args))) 00575 00576 00577 00578 00579 ## Edges 00580 for dep in deps: 00581 if not self.verbose and dep in self.get_child_stack_deps(stack): 00582 #print "Failed to find", dep, "in ", self.get_child_stack_deps(stack) 00583 continue 00584 if dep in stacks: 00585 outfile.write( ' "%s" -> "%s";\n' % (stack, dep)) 00586 if False:#dep in all_pkgs_plus_1: #Draw edges to all dependencies 00587 local_stack = self.get_stack_of(pkg) 00588 dependent_stack = self.get_stack_of(dep) 00589 #if local_stack not in args or not local_stack: 00590 # continue 00591 if dependent_stack == local_stack: 00592 outfile.write( ' "%s" -> "%s";\n' % (pkg, dep)) 00593 elif dependent_stack: 00594 intermediate = "%s.%s.%s"%(local_stack, dependent_stack, dep) 00595 outfile.write( ' "%s" -> "%s"[color="blue", style="dashed"];\n' % (pkg, intermediate)) 00596 # outfile.write('}\n') 00597 00598 00599 00600 outfile.write( '}\n') 00601 outfile.flush() # write to disk, but don't delete 00602 00603 try: 00604 output_filename = "%s_deps.%s"%("composite", type) 00605 print "Generating output %s"%output_filename 00606 subprocess.check_call(["dot", "-T%s"%type, outfile.name, "-o", output_filename]) 00607 print "%s generated"%output_filename 00608 except subprocess.CalledProcessError: 00609 print >> sys.stderr, "failed to generate %s"%output_filename 00610 00611 return output_filename 00612 00613 00614 def vdmain(): 00615 parser = OptionParser(usage="usage: %prog [options]", prog='rxdeps') 00616 #parser.add_option("-r", "--rosmake", dest="rosmake", default=False, 00617 # action="store_true", help="color by rosmakeall results") 00618 parser.add_option("-s", "--review", dest="review_status", default=False, 00619 action="store_true", help="color by review status") 00620 parser.add_option("--size", dest="size_by_deps", default=False, 00621 action="store_true", help="whether to size by number of depends on 1") 00622 parser.add_option("-l", "--license", dest="license", default=False, 00623 action="store_true", help="color by license type") 00624 parser.add_option("-v", dest="verbose", default=False, 00625 action="store_true", help="don't deduplicate dependencies") 00626 parser.add_option("--one", dest="target1", default=False, 00627 action="store_true", help="only draw one away from target") 00628 parser.add_option("-q", "--quiet", dest="quiet", default=False, 00629 action="store_true", help="quiet (remove links to common packages)") 00630 parser.add_option("--hide_single", dest="hide", default=False, 00631 action="store_true", help="hide (remove packages without links)") 00632 parser.add_option("--rank", dest="rank", default=False, 00633 action="store_true", help="layer by maximum dependency depth") 00634 parser.add_option("--cluster", dest="cluster", default=False, 00635 action="store_true", help="group by main subdir") 00636 parser.add_option("--color", metavar="FILENAME", 00637 dest="color_file", default=None, 00638 type="string", help="use color file") 00639 parser.add_option("-o", metavar="FILENAME", 00640 dest="output_filename", default=None, 00641 type="string", help="output_filename") 00642 parser.add_option("--graphviz-output", metavar="FILENAME", 00643 dest="graphviz_output_filename", default=None, 00644 type="string", help="Save the intermediate output to this filename.") 00645 parser.add_option("--target", metavar="PKG_NAMES_LIST,COMMA_SEPERATED", 00646 dest="targets", default='', 00647 type="string", help="target packages") 00648 parser.add_option("--stack", metavar="STACK_NAMES_LIST,COMMA_SEPERATED", 00649 dest="stacks", default='', 00650 type="string", help="target stacks") 00651 parser.add_option("--exclude", metavar="PKG_NAMES_LIST,COMMA_SEPERATED", 00652 dest="exclude", default='', 00653 type="string", help="exclude packages") 00654 parser.add_option("--message", metavar="full_message_name", 00655 dest="message", default='', 00656 type="string", help="target_message") 00657 parser.add_option("-T", metavar="File Type", 00658 dest="output_type", default="png", 00659 type="string", help="output file type") 00660 parser.add_option("--no-stack-details", 00661 dest="display_image", default=True, 00662 action="store_false", help="Do Not display stack details") 00663 parser.add_option("--new", 00664 dest="use_new_version", default=False, 00665 action="store_true", help="Use the new version") 00666 00667 00668 00669 options, args = parser.parse_args() 00670 if options.exclude: 00671 exclude = options.exclude.split(',') 00672 else: 00673 exclude = [] 00674 if options.targets: 00675 targets = options.targets.split(',') 00676 all_pkgs = roslib.packages.list_pkgs() 00677 invalidtarget = [t for t in targets if t not in all_pkgs] 00678 if invalidtarget: 00679 parser.error("Invalid packages set as targets, %s"%invalidtarget) 00680 else: 00681 targets = [] 00682 00683 if options.stacks: 00684 stacks = options.stacks.split(',') 00685 else: 00686 stacks = [] 00687 if options.quiet: 00688 exclude.extend(['roscpp','std_msgs', 'rospy', 'std_srvs', 'robot_msgs', 'robot_srvs', 'rosconsole', 'tf']) 00689 00690 pkg_characterists = PackageCharacteristics() 00691 if options.review_status: 00692 pkg_characterists.set_color_by("review") 00693 elif options.license: 00694 pkg_characterists.set_color_by("license") 00695 elif options.color_file: 00696 pkg_characterists.set_color_by("file", options.color_file) 00697 00698 00699 if False: 00700 if options.rosmake: 00701 print "Coloring by rosmakeall results" 00702 color_dict = get_rosmakeall_color() 00703 color_palate = rosmakeall_color_map 00704 00705 if options.output_filename: 00706 output_filename = os.path.realpath(options.output_filename) 00707 else: 00708 output_filename = "deps.pdf" 00709 00710 helper = HelperMethods() 00711 helper.verbose = options.verbose 00712 helper.display_test_packages = not options.quiet 00713 00714 all_stacks = roslib.stacks.list_stacks() 00715 invalid_args = set([stack for stack in args if stack not in all_stacks]) 00716 if invalid_args: 00717 parser.error("Invalid stacks passed as arguments %s"%invalid_args) 00718 00719 if not args and options.use_new_version: 00720 helper.generate_composite(roslib.stacks.list_stacks(), options.output_type, pkg_characterists, options.display_image) 00721 elif options.use_new_version: 00722 helper.generate_composite(args, options.output_type, pkg_characterists, options.display_image) 00723 00724 all_pkgs = set(targets) 00725 args = set(args) 00726 args.update([helper.get_stack_of(p) for p in targets]) 00727 if not args and options.cluster: 00728 parser.error("Cluster option requires stacks as arguments or --target options") 00729 for s in args: 00730 all_pkgs.update(helper.get_packages_of(s)) 00731 all_pkgs_plus_1 = set() 00732 for p in all_pkgs: 00733 all_pkgs_plus_1.update(helper.get_deps1(p)) 00734 00735 00736 print "Generating Graphviz Intermediate" 00737 outfile = tempfile.NamedTemporaryFile() 00738 outfile.write( """digraph ros { 00739 //size="11,8"; 00740 size="100,40"; 00741 //clusterrank=global; 00742 node [color=gainsboro, style=filled]; 00743 ranksep = 2.0; 00744 compound=true; 00745 00746 """) 00747 colors = ['red', 'blue', 'green', 'yellow', 'gold', 'orange','chocolate', 'gray44', 'deeppink', 'black'] 00748 # create the legend 00749 color_palate = pkg_characterists.get_color_palate() 00750 if color_palate: 00751 outfile.write(' subgraph cluster__legend { style=bold; color=black; label = "Color Legend"; ') 00752 for type in color_palate: 00753 text_color="black" 00754 bg_color = color_palate[type] 00755 if bg_color == "black": 00756 text_color = "white" 00757 outfile.write('"%s" [color="%s", fontcolor="%s"];\n '%(type, bg_color, text_color)) 00758 outfile.write('}\n') 00759 for cl in args:#roslib.stacks.list_stacks(): 00760 if options.cluster: 00761 base_color = colors[random.randrange(0, len(colors))] 00762 try: 00763 stack_dir = roslib.stacks.get_stack_dir(cl) 00764 except roslib.exceptions.ROSLibException, ex: 00765 stack_dir = "None" 00766 outfile.write(' subgraph cluster__%s { style=bold; color=%s; label = "%s \\n (%s)"; '%(cl, base_color, cl, stack_dir)) 00767 internal, external = helper.build_stack_list(cl) 00768 for pkg in internal: 00769 outfile.write(' "%s" ;'%pkg) 00770 for s in external: 00771 try: 00772 stack_dir = roslib.stacks.get_stack_dir(s) 00773 except roslib.exceptions.ROSLibException, ex: 00774 stack_dir = "None" 00775 outfile.write(' subgraph cluster__%s_%s { style=bold; color=%s; label = "Stack: %s \\n (%s)"; '%(cl, s, base_color, s, stack_dir)) 00776 for p in external[s]: 00777 outfile.write(' "%s.%s.%s" [ label = "%s"];'%(cl, s, p, p)) 00778 outfile.write('}\n') 00779 outfile.write('}\n') 00780 00781 external_deps_connected = set() 00782 for pkg in all_pkgs: 00783 deps = helper.get_deps1(pkg) 00784 node_args = [] 00785 ##Coloring 00786 color = pkg_characterists.get_color(pkg) 00787 node_args.append("color=%s"%color) 00788 if color == 'black': 00789 node_args.append("fontcolor=white") 00790 00791 ## Shape 00792 if pkg in exclude: 00793 node_args.append("shape=box") 00794 00795 # Size 00796 if options.size_by_deps: 00797 num_deps = len(roslib.rospack.rospack_depends_on(pkg)) 00798 node_args.append("width=%s,height=%s"%(.75 + .1 * sqrt(num_deps), .5 + .1* sqrt(num_deps))) 00799 00800 ##Perimeter 00801 if pkg in targets: 00802 node_args.append('peripheries=6, style=bold') 00803 elif pkg in exclude: 00804 node_args.append('peripheries=3, style=dashed') 00805 00806 notes = pkg_characterists.get_review_notes(pkg) 00807 if len(notes) > 0: 00808 node_args.append('label="%s\\n(%s)"' % (pkg, notes)) 00809 00810 if options.hide: 00811 if len(roslib.rospack.rospack_depends_on(pkg)) == 0 and len(helper.get_deps(pkg)) == 0: #TODO: This is pretty slow 00812 print "Hiding unattached package %s"%pkg 00813 continue 00814 00815 if pkg in all_pkgs: 00816 outfile.write(' "%s" [%s];\n'%(pkg, ', '.join(node_args))) 00817 00818 00819 00820 00821 ## Edges 00822 for dep in deps: 00823 if not options.verbose and dep in helper.get_internal_child_deps(pkg): 00824 continue 00825 if dep in all_pkgs_plus_1: #Draw edges to all dependencies 00826 local_stack = helper.get_stack_of(pkg) 00827 dependent_stack = helper.get_stack_of(dep) 00828 if local_stack not in args or not local_stack: 00829 continue 00830 if not (options.cluster and not dependent_stack == local_stack): 00831 outfile.write( ' "%s" -> "%s";\n' % (pkg, dep)) 00832 elif options.cluster and dependent_stack: 00833 intermediate = "%s.%s.%s"%(local_stack, dependent_stack, dep) 00834 deduplication = "%s.%s"%(local_stack, dependent_stack) 00835 outfile.write( ' "%s" -> "%s"[color="blue", style="dashed"];\n' % (pkg, intermediate)) 00836 if not deduplication in external_deps_connected and dependent_stack in args: 00837 outfile.write( ' "%s" -> "%s"[color="red", style="dashed", ltail=cluster__%s_%s, lhead=cluster__%s];\n' % (intermediate, dep, local_stack, dependent_stack, dependent_stack)) 00838 external_deps_connected.add(deduplication) 00839 # outfile.write('}\n') 00840 ## rank 00841 if options.rank: 00842 rank = helper.build_rank(all_pkgs) 00843 for key in rank: 00844 if len(rank[key]) > 1: 00845 outfile.write('{ rank = same;') 00846 for pkg_rank in rank[key]: 00847 if pkg_rank in all_pkgs: 00848 outfile.write(' "%s" ;'%pkg_rank) 00849 outfile.write('}\n') 00850 outfile.write( '}\n') 00851 outfile.flush() # write to disk, but don't delete 00852 00853 if options.graphviz_output_filename: 00854 graphviz_output_filename = os.path.realpath(options.graphviz_output_filename) 00855 shutil.copyfile(outfile.name, graphviz_output_filename) 00856 00857 try: 00858 print "Generating output %s"%output_filename 00859 # Check version, make postscript if too old to make pdf 00860 args = ["dot", "-V"] 00861 vstr = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[1] 00862 v = distutils.version.StrictVersion('2.16') 00863 r = re.compile(".*version ([^ ]*).*") 00864 print vstr 00865 m = r.search(vstr) 00866 if not m or not m.group(1): 00867 print 'Warning: failed to determine your version of dot. Assuming v2.16' 00868 else: 00869 version = distutils.version.StrictVersion(m.group(1)) 00870 print 'Detected dot version %s' % (version) 00871 if version > distutils.version.StrictVersion('2.8'): 00872 subprocess.check_call(["dot", "-Tpdf", outfile.name, "-o", output_filename]) 00873 print "%s generated"%output_filename 00874 else: 00875 orig = output_filename 00876 if output_filename.lower().endswith('.pdf'): 00877 output_filename = output_filename[:-3]+'ps' 00878 else: 00879 print "ERROR", output_filename 00880 subprocess.check_call(["dot", "-Tps2", outfile.name, "-o", output_filename]) 00881 print "%s generated"%output_filename 00882 00883 print "Trying to create %s..."%orig 00884 subprocess.check_call(["ps2pdf", output_filename, orig]) 00885 print "%s generated"%orig 00886 except subprocess.CalledProcessError: 00887 print >> sys.stderr, "failed to generate %s"%output_filename 00888 00889 if __name__ == '__main__': 00890 random.seed() 00891 vdmain()