rosdoc: ported rosdoc to new config-style system for non-doxygen builds and added landing page for packages with multiple documentation sets. Also removed msgenator from doxygenator build. Much still to do with rosdoc in terms of refactoring doxygenator and also giving landing page better look and feel

This commit is contained in:
Ken Conley 2009-09-23 03:18:01 +00:00
parent 3e6fc574b6
commit fcda5f921e
17 changed files with 311 additions and 112 deletions

View File

@ -7,9 +7,10 @@
<author>Ken Conley/kwc@willowgarage.com, Morgan Quigley/mquigley@cs.stanford.edu</author>
<license>BSD</license>
<review status="API cleared" notes=""/>
<url>http://pr.willowgarage.com/wiki/roslib</url>
<url>http://ros.org/wiki/roslib</url>
<export>
<cpp cflags="-I${prefix}/msg/cpp -I${prefix}/include `rosboost-cfg --cflags`" lflags="-Wl,-rpath,${prefix}/lib -L${prefix}/lib -lroslib"/>
<rosdoc config="${prefix}/rosdoc.yaml" />
</export>
<depend package="genmsg_cpp"/>

6
core/roslib/rosdoc.yaml Normal file
View File

@ -0,0 +1,6 @@
- builder: epydoc
output_dir: python
- builder: doxygen
name: C++ API
output_dir: c++
file_patterns: '*.c *.cpp *.h *.cc *.hh *.dox'

View File

@ -31,7 +31,7 @@
<depend package="roslang"/>
<export>
<roslang cmake="${prefix}/cmake/rospy.cmake"/>
<rosdoc builder="epydoc"/>
<rosdoc config="rosdoc.yaml"/>
</export>
</package>

1
core/rospy/rosdoc.yaml Normal file
View File

@ -0,0 +1 @@
- builder: epydoc

View File

@ -84,11 +84,10 @@ def main():
else:
sphinx_success = set()
# Generate Doxygen on remainder
# Generate Doxygen
if 1:
print "building doxygen packages"
import doxygenator
ctx.packages = dict([(p, ctx.packages[p]) for p in (set(ctx.packages) - sphinx_success - epyenator_success)])
doxy_success = doxygenator.generate_doxygen(ctx, quiet=options.quiet)
else:
doxy_success = []
@ -109,6 +108,11 @@ def main():
traceback.print_exc()
print >> sys.stderr, "msgenator failed"
if 1:
# Generate landing page
import landing_page
landing_page.generate_landing_page(ctx)
if 1:
# Generate Documentation Index
import docindex
@ -128,10 +132,5 @@ def main():
styles_css = os.path.join(ctx.docdir, 'styles.css')
print "copying",styles_in, "to", styles_css
shutil.copyfile(styles_in, styles_css)
# have to copy doxygen.css for external packages that we fake-doxygenate
dstyles_in = os.path.join(ctx.template_dir, 'doxygen.css')
dstyles_css = os.path.join(ctx.docdir, 'doxygen.css')
shutil.copyfile(dstyles_in, dstyles_css)
except:
traceback.print_exc()

View File

@ -49,46 +49,6 @@ external_template = load_tmpl('external.html')
header_template = load_tmpl('header.html')
footer_template = load_tmpl('footer.html')
manifest_template = load_tmpl('manifest.html')
wiki_header_template = load_tmpl('wiki-header.html')
# routines for including links to msgenator files
def _href(url, text):
return '<a href="%(url)s">%(text)s</a>'%locals()
def msg_link(msg):
return _href('msg/%(msg)s.html'%locals(), msg)
def srv_link(srv):
return _href('srv/%(srv)s.html'%locals(), srv)
# table of msgs/srvs provided by package. this is a duplicate of the
# PackageHeader.py code in the roswiki Moin
def msgenator_html(msgs, srvs):
# include table of msgs/srvs
msg_str = ''
if msgs or srvs:
if msgs and srvs:
msg_str += '<h2>ROS Message and Service Types</h2>'
elif msgs:
msg_str += '<h2>ROS Message Types</h2>\n'
elif srvs:
msg_str += '<h2>ROS Service Types</h2>\n'
msg_str += '<table border="0">\n<tr>\n'
if msgs:
if srvs:
msg_str += '<th>ROS Message Types</th>\n'
if srvs:
if msgs:
msg_str += '<th>ROS Service Types</th>\n'
msg_str += '</tr><tr>'
if msgs:
msg_str += '<td valign="top">\n'+\
'<br />\n'.join([msg_link(m) for m in msgs])+\
'</td>\n'
if srvs:
msg_str += '<td valign="top">\n'+\
'<br />\n'.join([srv_link(s) for s in srvs])+\
'</td>\n'
msg_str += '</tr></table>'
return msg_str
# other templates
@ -117,14 +77,14 @@ def generate_msg_srv_includes(package, tmp, to_delete):
html_file.write(_msg_srv_tmpl(ext, type_, f.read()))
## @param package str: package name
## @param rd_config dict: rosdoc configuration parameters for this doxygen build
## @param m Manifest : package manifest
## @param docdir str: directory to store documentation in
def create_package_template(package, m, path, docdir, header_filename, footer_filename):
#replace vars in the template file to point to package we are documenting
if not os.path.exists(docdir):
os.mkdir(docdir)
#TODO: replace with general purpose key/value parser/substitution to enable <export><doxygen key="foo" val="var"></export> feature
## @param html_dir str: directory to store doxygen files
def create_package_template(package, rd_config, m, path, html_dir,
header_filename, footer_filename):
# TODO: allow rd_config to specify excludes and whatnot
# TODO: replace with general purpose key/value parser/substitution to enable <export><doxygen key="foo" val="var"></export> feature
# determine the value of overridable keys
file_patterns = '*.c *.cpp *.h *.cc *.hh *.py *.dox'
@ -133,16 +93,22 @@ def create_package_template(package, m, path, docdir, header_filename, footer_fi
for e in m.get_export('doxygen', 'excludes'):
# prepend the packages path
excludes = '%s/%s'%(path, e)
# rd_config wins
if rd_config and 'excludes' in rd_config:
excludes = rd_config['excludes']
# last one wins
for e in m.get_export('doxygen', 'file-patterns'):
file_patterns = e
# rd_config wins
if rd_config and 'file_patterns' in rd_config:
file_patterns = rd_config['file_patterns']
html_output = html_path(package, docdir)
vars = { '$INPUT': path, '$PROJECT_NAME': package,
'$EXCLUDE_PROP': excludes, '$FILE_PATTERNS': file_patterns,
'$HTML_OUTPUT': os.path.abspath(html_dir),
'$HTML_HEADER': header_filename, '$HTML_FOOTER': footer_filename,
'$OUTPUT_DIRECTORY': os.path.join(docdir, package), '$HTML_OUTPUT': html_output}
'$OUTPUT_DIRECTORY': html_dir}
return instantiate_template(doxy_template, vars)
## Processes manifest for package and then generates templates for
@ -153,7 +119,7 @@ def create_package_template(package, m, path, docdir, header_filename, footer_fi
## @return (str, str, str): header, footer, manifest
def load_manifest_vars(ctx, package, path, docdir, m):
author = license = dependencies = description = usedby = status = notes = li_vc = li_url = brief = ''
wiki_url = 'http://pr.willowgarage.com/wiki/%s'%package
wiki_url = 'http://ros.org/wiki/%s'%package
project_link = '<a href="%s">%s</a>'%(wiki_url, package)
if m:
license = m.license or ''
@ -187,9 +153,6 @@ def load_manifest_vars(ctx, package, path, docdir, m):
# include links to msgs/srvs
msgs = roslib.msgs.list_msg_types(package, False)
srvs = roslib.srvs.list_srv_types(package, False)
msgen = msgenator_html(msgs, srvs)
if msgen is None:
raise Exception('msgen is none')
return {'$package': package,
'$projectlink': project_link, '$license': license,
@ -197,7 +160,7 @@ def load_manifest_vars(ctx, package, path, docdir, m):
'$description': description, '$brief': brief,
'$author': author, '$status':status,
'$notes':notes, '$li_vc': li_vc, '$li_url': li_url,
'$msgenator': msgen}
}
## utility to write string data to files and handle unicode
def _write_to_file(f, tmpl):
@ -228,10 +191,10 @@ If you are on Ubuntu/Debian, you can install doxygen by typing:
"""
sys.exit(1)
def run_rxdeps(package, dir):
if 1: return
#TODO: move elsewhere
def run_rxdeps(package, pkg_doc_dir):
try:
command = ['rxdeps', '-s', '--target=%s'%package, '--cluster', '-o', os.path.join(dir, package, 'html', '%s_deps.pdf'%package)]
command = ['rxdeps', '-s', '--target=%s'%package, '--cluster', '-o', os.path.join(pkg_doc_dir, '%s_deps.pdf'%package)]
print "rxdeping %s [%s]"%(package, ' '.join(command))
Popen(command, stdout=PIPE).communicate()
except OSError, (errno, strerr):
@ -242,6 +205,10 @@ Package dependency tree links will not work properly.
## Main entrypoint into creating doxygen files
## @return [str]: list of packages that were successfully generated
def generate_doxygen(ctx, quiet=False):
#TODO: move external generator into its own generator
#TODO: move rxdeps into its own generator
# setup temp directory
tmp = 'tmp'
if not os.path.exists(tmp):
@ -255,22 +222,47 @@ def generate_doxygen(ctx, quiet=False):
# list of packages that we are documenting
doc_packages = ctx.doc_packages
external_docs = ctx.external_docs
rd_configs = ctx.rd_configs
manifests = ctx.manifests
tmpls = [header_template, footer_template, manifest_template, wiki_header_template]
tmpls = [header_template, footer_template, manifest_template]
try:
for package, path in packages.iteritems():
if not package in doc_packages:
if not package in doc_packages or not ctx.has_builder(package, 'doxygen'):
continue
html_dir = os.path.join(dir, package, 'html')
# have to makedirs for external packages
if not os.path.exists(html_dir):
os.makedirs(html_dir)
# the logic for the doxygen builder is different from
# others as doxygen is the default builder if no config is
# declared
rd_config = rd_configs.get(package, None)
if rd_config:
# currently only allow one doxygen build per package. This is not inherent, it
# just requires rewriting higher-level logic
rd_config = [d for d in ctx.rd_configs[package] if d['builder'] == 'doxygen'][0]
# Configuration (all are optional)
#
# name: Documentation set name (e.g. C++ API)
# output_dir: Directory to store files (default '.')
# file-patterns: override FILE_PATTERNS
# excludes: override EXCLUDES
# doxygenator currently does some non-doxygen work.
# pkg_doc_dir is the pointer to the directory for these non-doxygen
# tools. html_dir is the path for doxygen
pkg_doc_dir = html_path(package, ctx.docdir)
# compute the html directory for doxygen
html_dir = html_path(package, ctx.docdir)
if rd_config and 'output_dir' in rd_config:
html_dir = os.path.join(html_dir, rd_config['output_dir'])
# have to makedirs for external packages
if not os.path.exists(pkg_doc_dir):
os.makedirs(pkg_doc_dir)
files = []
try:
#TODO: cleanup the temporary files
header_file = tempfile.NamedTemporaryFile('w+')
footer_file = tempfile.NamedTemporaryFile('w+')
doxygen_file = tempfile.NamedTemporaryFile('w+')
@ -285,11 +277,14 @@ def generate_doxygen(ctx, quiet=False):
# - instantiate the templates
manifest_ = manifests[package] if package in manifests else None
vars = load_manifest_vars(ctx, package, path, dir, manifest_)
header, footer, manifest_html, wiki_header = [instantiate_template(t, vars) for t in tmpls]
header, footer, manifest_html = [instantiate_template(t, vars) for t in tmpls]
run_rxdeps(package, dir)
run_rxdeps(package, pkg_doc_dir)
if package not in external_docs:
doxy = create_package_template(package, manifest_, path, dir, header_file.name, footer_file.name)
doxy = \
create_package_template(package, rd_config, manifest_,
path, html_dir,
header_file.name, footer_file.name)
for f, tmpl in zip(files, [header, footer, manifest_html, doxy]):
_write_to_file(f, tmpl)
# doxygenate
@ -300,18 +295,27 @@ def generate_doxygen(ctx, quiet=False):
# it is time consuming for packages that provide their own docs
external_link = ctx.external_docs[package]
# Override mainpage title if 'name' is in config
title = 'Main Page'
if rd_config:
title = rd_config.get('name', title)
vars = { '$package': package, '$external_link': external_link,
'$header': header, '$footer': footer,
'$manifest': manifest_html,
# doxygen vars
'$relpath$': '../../',
'$title': package+': Main Page',
'$title': package+': '+title,
}
with open(os.path.join(html_dir, 'index.html'), 'w') as ext_html_file:
_write_to_file(ext_html_file, instantiate_template(external_template, vars))
with open(os.path.join(html_dir, 'wiki_header.html'), 'w') as wiki_header_file:
_write_to_file(wiki_header_file, wiki_header)
with open(os.path.join(pkg_doc_dir, 'index.html'), 'w') as ext_html_file:
_write_to_file(ext_html_file, instantiate_template(external_template, vars))
# support files (stylesheets)
import shutil
dstyles_in = os.path.join(ctx.template_dir, 'doxygen.css')
dstyles_css = os.path.join(html_dir, 'doxygen.css')
shutil.copyfile(dstyles_in, dstyles_css)
success.append(package)
finally:

View File

@ -37,19 +37,32 @@ from __future__ import with_statement
import os, sys
from subprocess import Popen, PIPE
## Main entrypoint into creating Eepydoc documentation
from rosdoc.rdcore import *
## Main entrypoint into creating Epydoc documentation
## @return [str]: list of packages that were successfully generated
def generate_epydoc(ctx):
success = []
for package, path in ctx.packages.iteritems():
if package in ctx.doc_packages and ctx.should_document(package):
builder = ctx.builder[package]
if builder != 'epydoc':
continue
if package in ctx.doc_packages and ctx.should_document(package) \
and ctx.has_builder(package, 'epydoc'):
# currently only allow one epydoc build per package. This
# is not inherent, it just requires rewriting higher-level
# logic
rd_config = [d for d in ctx.rd_configs[package] if d['builder'] == 'epydoc'][0]
# Configuration Properties (all optional):
#
# output_dir: directory_name (default: '.')
# name: Documentation Set Name (default: Python API)
try:
html_dir = os.path.join(ctx.docdir, package, 'html')
html_dir = html_path(package, ctx.docdir)
if 'output_dir' in rd_config:
html_dir = os.path.join(html_dir, rd_config['output_dir'])
if not os.path.isdir(html_dir):
os.makedirs(html_dir)
command = ['epydoc', '--html', package, '-o', html_dir]
# determine the python path of the package
@ -64,5 +77,5 @@ def generate_epydoc(ctx):
Popen(command, stdout=PIPE, env=env).communicate()
success.append(package)
except Exception, e:
print >> sys.stderr, "Unable to generate eepydoc for [%s]. This is probably because eepydoc is not installed.\nThe exact error is:\n\t%s"%(package, str(e))
print >> sys.stderr, "Unable to generate epydoc for [%s]. This is probably because epydoc is not installed.\nThe exact error is:\n\t%s"%(package, str(e))
return success

View File

@ -0,0 +1,130 @@
#!/usr/bin/env python
# Software License Agreement (BSD License)
#
# Copyright (c) 2008, Willow Garage, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Willow Garage, Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
from __future__ import with_statement
import os, sys
import time
from subprocess import Popen, PIPE
import roslib.msgs
import roslib.srvs
from rosdoc.rdcore import *
def _href(location, text):
return '<a href="%(location)s">%(text)s</a>'%locals()
def link_name(rd_config):
if 'name' in rd_config:
n = rd_config['name']
else:
if rd_config['builder'] == 'doxygen':
return 'C++ API'
elif rd_config['builder'] in ['epydoc', 'sphinx']:
return 'Python API'
else:
return builder
return n
def generate_links(ctx, package, base_dir, rd_configs):
output_dirs = [c.get('output_dir', None) for c in rd_configs]
# filter out empties
output_dirs = [d for d in output_dirs if d and d != '.']
# length check. if these are unequal, cannot generate landing
# page. this is often true if the config is merely generating
# local
if len(output_dirs) != len(rd_configs):
return None
links = []
for c, d in zip(rd_configs, output_dirs):
links.append(_href(d, link_name(c)))
msgs = roslib.msgs.list_msg_types(package, False)
srvs = roslib.srvs.list_srv_types(package, False)
if msgs or srvs:
if msgs and srvs:
title = 'msg/srv API'
elif msgs and not srvs:
title = 'msg API'
elif srvs and not msgs:
title = 'srv API'
#TODO: this shouldn't be hardcoded to index-msg.html
links.append(_href('index-msg.html', title))
url = ctx.manifests[package].url
if url:
links.append(_href(url, '%s Package Documentation'%package))
return links
## Generate landing page in the event that there are multiple documentation sets
## @return [str]: list of packages for which there are landing pages generated
def generate_landing_page(ctx):
success = []
template = load_tmpl('landing.template')
for package, path in ctx.packages.iteritems():
if package in ctx.doc_packages and ctx.should_document(package) and \
package in ctx.rd_configs:
rd_configs = ctx.rd_configs[package]
links = generate_links(ctx, package, ctx.docdir, rd_configs)
# if links is empty, it means that the rd_configs builds
# to the base directory and no landing page is required
# (or it means that the config is corrupt)
if not links:
print "ignoring landing page for", package
return
try:
html_dir = html_path(package, ctx.docdir)
if not os.path.isdir(html_dir):
os.makedirs(html_dir)
links_html = '\n'.join(['<li class="landing-li">%s</li>'%l for l in links])
date = str(time.strftime('%a, %d %b %Y %H:%M:%S'))
vars = {
'$package': package,
'$links': links_html,
'$date': date,
}
print "generating landing page", html_dir
with open(os.path.join(html_dir, 'index.html'), 'w') as f:
f.write(instantiate_template(template, vars))
success.append(package)
except Exception, e:
print >> sys.stderr, "Unable to generate landing_page for [%s]:\n\t%s"%(package, str(e))
return success

View File

@ -32,6 +32,8 @@
#
# Revision $Id: doxyutil.py 3727 2009-02-06 22:42:26Z sfkwc $
from __future__ import with_statement
import os
import sys
from subprocess import Popen, PIPE
@ -63,10 +65,23 @@ class RosdocContext(object):
self.stacks = {}
self.external_docs = {}
self.manifests = {}
self.builder = {}
self.stack_manifests = {}
self.stack_manifests = {}
# advanced per-package config
self.rd_configs = {}
self.template_dir = None
## @return: True if package is configured to use builder. NOTE: if
## there is no config, package is assumed to define a doxygen
## builder
def has_builder(self, package, builder):
rd_config = self.rd_configs.get(package, None)
if not rd_config:
return builder == 'doxygen'
try:
return len([d for d in rd_config if d['builder'] == builder]) > 0
except KeyError:
print >> sys.stderr, "config file for [%s] is invalid, missing required 'builder' key"%package
## @return bool True if \a package should be document
def should_document(self, package):
@ -107,9 +122,9 @@ class RosdocContext(object):
## Crawl manifest.xml dependencies
def _crawl_deps(self):
builder = self.builder
external_docs = self.external_docs
manifests = self.manifests
rd_configs = self.rd_configs
stacks = self.stacks = {}
@ -130,22 +145,29 @@ class RosdocContext(object):
try:
manifests[package] = m = roslib.manifest.parse_file(f)
#NOTE: the behavior is undefined if the users uses
#both config and export properties directly
# #1650 for backwards compatibility, we ready the old
# 'doxymaker' tag, which is deprecated
# this is a loop but we only accept one value
# - this is a loop but we only accept one value
for e in m.get_export('doxymaker', 'external'):
external_docs[package] = e
for e in m.get_export('rosdoc', 'external'):
external_docs[package] = e
builder[package] = 'doxygen'
for e in m.get_export('rosdoc', 'builder'):
builder[package] = e.lower()
if builder[package] not in ['epydoc', 'doxygen', 'sphinx']:
print >> sys.stderr, "ERROR: unknown builder [%s]. Using doxygen instead"%builder[package]
builder[package] = 'doxygen'
# load in any external config files
for e in m.get_export('rosdoc', 'config'):
import yaml
try:
e = e.replace('${prefix}', path)
config_p = os.path.join(path, e)
with open(config_p, 'r') as config_f:
rd_configs[package] = yaml.load(config_f)
except Exception, e:
print >> sys.stderr, "ERROR: unable to load rosdoc config file [%s]: %s"%(config_p, str(e))
except:
print >> sys.stderr, "WARN: Package '%s' does not have a valid manifest.xml file, manifest information will not be included in docs"%package

View File

@ -43,11 +43,18 @@ from subprocess import Popen, PIPE
def generate_sphinx(ctx):
success = []
for package, path in ctx.packages.iteritems():
if package in ctx.doc_packages and ctx.should_document(package):
if package in ctx.doc_packages and ctx.should_document(package) and \
ctx.has_builder(package, 'sphinx'):
try:
builder = ctx.builder[package]
if builder != 'sphinx':
continue
# currently only allow one sphinx build per package. This
# is not inherent, it just requires rewriting higher-level
# logic
rd_config = [d for d in ctx.rd_configs[package] if d['builder'] == 'sphinx'][0]
# rd_config is currently a flag. In the future, I imagine it pointing
# to the location of index.rst, among other things
if os.access(os.path.join(path, "index.rst"), os.R_OK):
oldcwd = os.getcwd()
os.chdir(path)

View File

@ -106,7 +106,7 @@ IGNORE_PREFIX =
# configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_OUTPUT = $HTML_OUTPUT
HTML_FILE_EXTENSION = .html
HTML_HEADER = $HTML_HEADER
HTML_FOOTER = $HTML_FOOTER
@ -125,7 +125,7 @@ TREEVIEW_WIDTH = 250
#---------------------------------------------------------------------------
# configuration options related to the LaTeX output
#---------------------------------------------------------------------------
GENERATE_LATEX = YES
GENERATE_LATEX = NO
LATEX_OUTPUT = latex
LATEX_CMD_NAME = latex
MAKEINDEX_CMD_NAME = makeindex

View File

@ -0,0 +1,17 @@
<html>
<head>
<title>$package Documentation</title>
<link type="text/css" rel="stylesheet" href="../../../styles.css" />
</head>
<body>
<h1>$package API Documentation</h1>
$links
<p class="landing-footer"><small>autogenerated on $date</small></p>
</body>
</html>

View File

@ -10,8 +10,5 @@ $li_url
$li_vc
<li><a href="$package_deps.pdf">Dependency Tree</a></li>
</ul>
$msgenator
</p>
</div>

View File

@ -13,7 +13,7 @@ rosservice contains the rosservice command-line tool for listing and querying RO
<depend package="roslib"/>
<export>
<rosdoc builder="epydoc"/>
<rosdoc config="rosdoc.yaml"/>
</export>
</package>

View File

@ -0,0 +1 @@
- builder: epydoc

View File

@ -21,7 +21,7 @@ internal-use only as its API is not stable.
<depend package="rosrecord"/>
<export>
<rosdoc builder="epydoc"/>
<rosdoc config="rosdoc.yaml"/>
</export>
</package>

View File

@ -0,0 +1 @@
- builder: epydoc