$search
00001 #!/usr/bin/env python 00002 # Software License Agreement (BSD License) 00003 # 00004 # Copyright (c) 2008, Willow Garage, Inc. 00005 # All rights reserved. 00006 # 00007 # Redistribution and use in source and binary forms, with or without 00008 # modification, are permitted provided that the following conditions 00009 # are met: 00010 # 00011 # * Redistributions of source code must retain the above copyright 00012 # notice, this list of conditions and the following disclaimer. 00013 # * Redistributions in binary form must reproduce the above 00014 # copyright notice, this list of conditions and the following 00015 # disclaimer in the documentation and/or other materials provided 00016 # with the distribution. 00017 # * Neither the name of Willow Garage, Inc. nor the names of its 00018 # contributors may be used to endorse or promote products derived 00019 # from this software without specific prior written permission. 00020 # 00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 00024 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 00025 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 00026 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 00027 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00028 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 00029 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00030 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 00031 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00032 # POSSIBILITY OF SUCH DAMAGE. 00033 # 00034 # Revision $Id$ 00035 00036 from __future__ import with_statement 00037 00038 from subprocess import Popen, PIPE 00039 import tempfile 00040 00041 import roslib.msgs 00042 import roslib.srvs 00043 import roslib.rospack 00044 00045 from rosdoc.rdcore import * 00046 00047 ## @param ext str: extension (msg or srv) 00048 ## @param type str: type name 00049 ## @param text str: full text definition of type 00050 def _msg_srv_tmpl(ext, type, text): 00051 tmpl = '<h2>%s.%s</h2><div style="border: 1px solid #333;"><p style="font-family: monospace;">'%(type, ext) 00052 for line in text.split('\n'): 00053 parts = line.split('#') 00054 if len(parts) > 1: 00055 tmpl = tmpl + parts[0]+'<font color="blue">#%s</font><br/>'%('#'.join(parts[1:])) 00056 else: 00057 tmpl = tmpl + "%s<br />"%parts[0] 00058 return tmpl+"</p></div>" 00059 00060 ## create include files for messages and services 00061 def generate_msg_srv_includes(package, tmp, to_delete): 00062 for ext, list_types, spec_file in [('msg', roslib.msgs.list_msg_types, roslib.msgs.msg_file),\ 00063 ('srv', roslib.srvs.list_srv_types, roslib.srvs.srv_file)]: 00064 for type_ in list_types(package, False): 00065 with open(spec_file(package, type_), 'r') as f: 00066 p = os.path.join(tmp, '%s.%s.html'%(type_, ext)) 00067 with open(p, 'w') as html_file: 00068 to_delete.append(p) 00069 html_file.write(_msg_srv_tmpl(ext, type_, f.read())) 00070 00071 ## @param package str: package name 00072 ## @param rd_config dict: rosdoc configuration parameters for this doxygen build 00073 ## @param m Manifest : package manifest 00074 ## @param html_dir str: directory to store doxygen files 00075 def create_package_template(package, rd_config, m, path, html_dir, 00076 header_filename, footer_filename, example_path): 00077 # TODO: replace with general purpose key/value parser/substitution 00078 00079 # set defaults for overridable keys 00080 file_patterns = '*.c *.cpp *.h *.cc *.hh *.hpp *.py *.dox *.java' 00081 excludes = '%s/build/'%path 00082 exclude_patterns = '' 00083 00084 # last one wins 00085 for e in m.get_export('doxygen', 'excludes'): 00086 # prepend the packages path 00087 excludes = '%s/%s'%(path, e) 00088 00089 # last one wins 00090 for e in m.get_export('doxygen', 'file-patterns'): 00091 file_patterns = e 00092 00093 # rd_config take precedence. default to empty dir so vars logic below works 00094 if not rd_config: 00095 rd_config = {} 00096 00097 include_path = roslib.rospack.rospackexec(['cflags-only-I',package]) 00098 00099 # example path is where htmlinclude operates 00100 dvars = { '$INPUT': path, '$PROJECT_NAME': package, 00101 '$EXAMPLE_PATH': "%s %s"%(path, example_path), 00102 '$EXCLUDE_PROP': rd_config.get('exclude', excludes), 00103 '$FILE_PATTERNS': rd_config.get('file_patterns', file_patterns), 00104 '$EXCLUDE_PATTERNS': rd_config.get('exclude_patterns', ''), 00105 '$HTML_OUTPUT': os.path.abspath(html_dir), 00106 '$HTML_HEADER': header_filename, '$HTML_FOOTER': footer_filename, 00107 '$OUTPUT_DIRECTORY': html_dir, 00108 '$INCLUDE_PATH': include_path, 00109 00110 '$JAVADOC_AUTOBRIEF': rd_config.get('javadoc_autobrief', 'NO'), 00111 '$MULTILINE_CPP_IS_BRIEF': rd_config.get('multiline_cpp_is_brief', 'NO'), 00112 '$TAB_SIZE': rd_config.get('tab_size', '8'), 00113 '$ALIASES': rd_config.get('aliases', ''), 00114 '$EXAMPLE_PATTERNS': rd_config.get('example_patterns', ''), 00115 '$IMAGE_PATH': rd_config.get('image_path', path), #default to $INPUT 00116 '$EXCLUDE_SYMBOLS': rd_config.get('exclude_symbols', ''), 00117 } 00118 return instantiate_template(doxy_template, dvars) 00119 00120 ## Processes manifest for package and then generates templates for 00121 ## header, footer, and manifest include file 00122 ## @param package: package to create templates for 00123 ## @type package: str 00124 ## @param rd_config: rosdoc configuration dictionary 00125 ## @type rd_config: dict 00126 ## @param path: file path to package 00127 ## @type path: str 00128 ## @param m: package manifest or None 00129 ## @type m: manifest.Manifest 00130 ## @return: header, footer, manifest 00131 ## @rtype: (str, str, str) 00132 def load_manifest_vars(ctx, rd_config, package, path, docdir, package_htmldir, m): 00133 author = license = dependencies = description = usedby = status = notes = li_vc = li_url = brief = '' 00134 00135 # by default, assume that packages are on wiki 00136 home_url = 'http://ros.org/wiki/%s'%package 00137 00138 if rd_config: 00139 if 'homepage' in rd_config: 00140 home_url = rd_config['homepage'] 00141 00142 project_link = '<a href="%s">%s</a>'%(home_url, package) 00143 if m is not None: 00144 license = m.license or '' 00145 author = m.author or '' 00146 description = m.description or '' 00147 status = m.status or '' 00148 notes = m.notes or '' 00149 00150 if m.brief: 00151 brief = ": "+m.brief 00152 00153 li_url = '<li>Homepage: <a href=\"%s\">%s</a></li>'%(m.url, m.url) 00154 if m.versioncontrol: 00155 vcurl = m.versioncontrol.url 00156 li_vc = '<li>Version Control (%s): <a href="%s">%s</a></li>'%(m.versioncontrol.type, vcurl, vcurl) 00157 00158 if m.depends: 00159 dependencies = "<ul>\n" + \ 00160 li_package_links(ctx, package, [d.package for d in m.depends], docdir, package_htmldir) 00161 else: 00162 dependencies = "None<br />" 00163 else: 00164 print "no manifest [%s]"%(package) 00165 00166 dependson1 = roslib.rospack.rospackexec(['depends-on1', package]).split('\n') 00167 # filter depends by what we're actually documenting 00168 dependson1 = [d for d in dependson1 if d and ctx.should_document(d)] 00169 if dependson1: 00170 usedby = "<ul>\n"+li_package_links(ctx, package, dependson1, docdir, package_htmldir) 00171 else: 00172 usedby = "None<br />" 00173 00174 # include links to msgs/srvs 00175 msgs = roslib.msgs.list_msg_types(package, False) 00176 srvs = roslib.srvs.list_srv_types(package, False) 00177 00178 return {'$package': package, 00179 '$projectlink': project_link, '$license': license, 00180 '$dependencies': dependencies, '$usedby': usedby, 00181 '$description': description, '$brief': brief, 00182 '$author': author, '$status':status, 00183 '$notes':notes, '$li_vc': li_vc, '$li_url': li_url, 00184 } 00185 00186 ## utility to write string data to files and handle unicode 00187 def _write_to_file(f, tmpl): 00188 try: 00189 if type(tmpl) == str: 00190 f.write(tmpl) 00191 else: #iso-8859-1 is the declared encoding of doxygen 00192 f.write(tmpl.encode('utf-8')) 00193 f.flush() 00194 except: 00195 print "ERROR, f[%s], tmpl[%s]"%(f, tmpl) 00196 raise 00197 00198 def get_doxygen_version(): 00199 try: 00200 command = ['doxygen', '--version'] 00201 version = Popen(command, stdout=PIPE, stderr=PIPE).communicate()[0].strip() 00202 except: 00203 version = None 00204 return version 00205 00206 # #3870: doxygen changed their template file syntax in a 'patch' 00207 # version. It's a bit ugly to inline this on import, but this entire 00208 # generator needs to be rewritten. 00209 def header_template_name(): 00210 doxygen_version = get_doxygen_version() 00211 # doxygen not available 00212 if doxygen_version is None: 00213 return None 00214 major, minor, patch = doxygen_version.split('.') 00215 # > 1.7.3 doxygen changed the template syntax 00216 if int(major) > 1 or int(minor) > 7 or int(patch) > 3: 00217 return 'header-1.7.4.html' 00218 else: 00219 return 'header.html' 00220 00221 00222 def run_doxygen(package, doxygen_file, quiet=False): 00223 try: 00224 command = ['doxygen', doxygen_file] 00225 if quiet: 00226 Popen(command, stdout=PIPE, stderr=PIPE).communicate() 00227 else: 00228 print "doxygen-ating %s [%s]"%(package, ' '.join(command)) 00229 Popen(command, stdout=PIPE).communicate() 00230 except OSError, (errno, strerr): 00231 #fatal 00232 print """\nERROR: It appears that you do not have doxygen installed. 00233 If you are on Ubuntu/Debian, you can install doxygen by typing: 00234 00235 sudo apt-get install doxygen 00236 """ 00237 sys.exit(1) 00238 00239 #TODO: move elsewhere 00240 def run_rxdeps(package, pkg_doc_dir): 00241 try: 00242 command = ['rxdeps', '-s', '--target=%s'%package, '--cluster', '-o', os.path.join(pkg_doc_dir, '%s_deps.pdf'%package)] 00243 print "rxdeping %s [%s]"%(package, ' '.join(command)) 00244 Popen(command, stdout=PIPE).communicate() 00245 except OSError, (errno, strerr): 00246 print >> sys.stderr, """\nERROR: It appears that you do not have rxdeps installed. 00247 Package dependency tree links will not work properly. 00248 """ 00249 except: 00250 print >> sys.stderr, "ERROR: rxdeps failed" 00251 00252 ## Main entrypoint into creating doxygen files 00253 ## @param disable_rxdeps: if True, don't generate rxdeps documenation (note: this parameter is volatile as rxdeps generation will be moved outside of doxygenator) 00254 ## @type disable_rxdeps: bool 00255 ## @return [str]: list of directories in which documentation was generated (aka the list of successful packages) 00256 def generate_doxygen(ctx, disable_rxdeps=False): 00257 quiet = ctx.quiet 00258 00259 #TODO: move external generator into its own generator 00260 #TODO: move rxdeps into its own generator 00261 00262 #TODO: success is now supposed to be the listed of generated 00263 #artifacts. There is some discontinuity here if the cwd is 00264 #changed. 00265 00266 # setup temp directory 00267 #UNIXONLY 00268 # TODO: dynamically generate 00269 tmp = '/tmp/rosdoc' 00270 if not os.path.exists(tmp): 00271 os.mkdir(tmp) 00272 example_path = tmp 00273 00274 success = [] 00275 00276 dir = ctx.docdir 00277 # dictionary mapping packages to paths 00278 packages = ctx.packages 00279 # list of packages that we are documenting 00280 doc_packages = ctx.doc_packages 00281 external_docs = ctx.external_docs 00282 rd_configs = ctx.rd_configs 00283 manifests = ctx.manifests 00284 00285 tmpls = [header_template, footer_template, manifest_template] 00286 try: 00287 for package, path in packages.iteritems(): 00288 if not package in doc_packages or \ 00289 not ctx.has_builder(package, 'doxygen'): 00290 continue 00291 00292 # the logic for the doxygen builder is different from 00293 # others as doxygen is the default builder if no config is 00294 # declared 00295 rd_config = rd_configs.get(package, None) 00296 if rd_config: 00297 # currently only allow one doxygen build per package. This is not inherent, it 00298 # just requires rewriting higher-level logic 00299 rd_config = [d for d in ctx.rd_configs[package] if d['builder'] == 'doxygen'][0] 00300 00301 # Configuration (all are optional) 00302 # 00303 # name: Documentation set name (e.g. C++ API) 00304 # output_dir: Directory to store files (default '.') 00305 # file_patterns: override FILE_PATTERNS 00306 # excludes: override EXCLUDES 00307 00308 # doxygenator currently does some non-doxygen work. 00309 # pkg_doc_dir is the pointer to the directory for these non-doxygen 00310 # tools. html_dir is the path for doxygen 00311 pkg_doc_dir = html_path(package, ctx.docdir) 00312 00313 # compute the html directory for doxygen 00314 html_dir = html_path(package, ctx.docdir) 00315 if rd_config and 'output_dir' in rd_config: 00316 html_dir = os.path.join(html_dir, rd_config['output_dir']) 00317 00318 # have to makedirs for external packages 00319 if not os.path.exists(pkg_doc_dir): 00320 os.makedirs(pkg_doc_dir) 00321 00322 files = [] 00323 try: 00324 header_file = tempfile.NamedTemporaryFile('w+') 00325 footer_file = tempfile.NamedTemporaryFile('w+') 00326 doxygen_file = tempfile.NamedTemporaryFile('w+') 00327 manifest_file = open(os.path.join(example_path, 'manifest.html'), 'w') 00328 files = [header_file, footer_file, manifest_file, doxygen_file] 00329 to_delete = [manifest_file] 00330 00331 # create the doxygen templates and wiki header 00332 00333 generate_msg_srv_includes(package, example_path, to_delete) 00334 00335 # - instantiate the templates 00336 manifest_ = manifests[package] if package in manifests else None 00337 00338 vars = load_manifest_vars(ctx, rd_config, package, path, dir, html_dir, manifest_) 00339 header, footer, manifest_html = [instantiate_template(t, vars) for t in tmpls] 00340 00341 if not disable_rxdeps: 00342 run_rxdeps(package, pkg_doc_dir) 00343 if package not in external_docs: 00344 doxy = \ 00345 create_package_template(package, rd_config, manifest_, 00346 path, html_dir, 00347 header_file.name, footer_file.name, 00348 example_path) 00349 for f, tmpl in zip(files, [header, footer, manifest_html, doxy]): 00350 _write_to_file(f, tmpl) 00351 # doxygenate 00352 run_doxygen(package, doxygen_file.name, quiet=quiet) 00353 else: 00354 # for external packages, we generate a landing page that is 00355 # similar to the doxygen, but we don't actually run doxygen as 00356 # it is time consuming for packages that provide their own docs 00357 00358 external_link = ctx.external_docs[package] 00359 00360 # Override mainpage title if 'name' is in config 00361 title = 'Main Page' 00362 if rd_config: 00363 title = rd_config.get('name', title) 00364 vars = { '$package': package, '$external_link': external_link, 00365 '$header': header, '$footer': footer, 00366 '$manifest': manifest_html, 00367 # doxygen vars 00368 '$relpath$': '../../', 00369 '$title': package+': '+title, 00370 } 00371 00372 with open(os.path.join(pkg_doc_dir, 'index.html'), 'w') as ext_html_file: 00373 _write_to_file(ext_html_file, instantiate_template(external_template, vars)) 00374 00375 # support files (stylesheets) 00376 import shutil 00377 dstyles_in = os.path.join(ctx.template_dir, 'doxygen.css') 00378 dstyles_css = os.path.join(html_dir, 'doxygen.css') 00379 shutil.copyfile(dstyles_in, dstyles_css) 00380 00381 success.append(package) 00382 except Exception as e: 00383 print >> sys.stderr, "ERROR: Doxygen of package [%s] failed: %s"%(package, str(e)) 00384 finally: 00385 for f in files: 00386 f.close() 00387 finally: 00388 pass 00389 return success 00390 00391 00392 # other templates 00393 00394 doxy_template = load_tmpl('doxy.template') 00395 external_template = load_tmpl('external.html') 00396 00397 header_template = load_tmpl(header_template_name()) 00398 footer_template = load_tmpl('footer.html') 00399 manifest_template = load_tmpl('manifest.html') 00400 00401