- rewrote doxygen documentation generation integration with Scons (still need some clean-up): list of sources is explicitly passed to a doxyfile builder which is used as input of a doxygen builder. Hence, the doxyfile depends on all the sources.

- documentation is now correctly generated once when source are changed on the first scons run.
This commit is contained in:
Baptiste Lepilleur 2008-01-20 16:49:53 +00:00
parent 4882d0a595
commit f66d370741
5 changed files with 208 additions and 209 deletions

View File

@ -15,15 +15,15 @@ options.Add( EnumOption('platform',
try: try:
platform = ARGUMENTS['platform'] platform = ARGUMENTS['platform']
if platform == 'linux-gcc': if platform == 'linux-gcc':
CXX = 'g++' # not quite right, but env is not yet available. CXX = 'g++' # not quite right, but env is not yet available.
import commands import commands
version = commands.getoutput('%s -dumpversion' %CXX) version = commands.getoutput('%s -dumpversion' %CXX)
platform = 'linux-gcc-%s' %version platform = 'linux-gcc-%s' %version
print "Using platform '%s'" %platform print "Using platform '%s'" %platform
LD_LIBRARY_PATH = os.environ.get('LD_LIBRARY_PATH', '') LD_LIBRARY_PATH = os.environ.get('LD_LIBRARY_PATH', '')
LD_LIBRARY_PATH = "%s:libs/%s" %(LD_LIBRARY_PATH, platform) LD_LIBRARY_PATH = "%s:libs/%s" %(LD_LIBRARY_PATH, platform)
os.environ['LD_LIBRARY_PATH'] = LD_LIBRARY_PATH os.environ['LD_LIBRARY_PATH'] = LD_LIBRARY_PATH
print "LD_LIBRARY_PATH =", LD_LIBRARY_PATH print "LD_LIBRARY_PATH =", LD_LIBRARY_PATH
except KeyError: except KeyError:
print 'You must specify a "platform"' print 'You must specify a "platform"'
sys.exit(2) sys.exit(2)
@ -95,6 +95,7 @@ env.Tool('doxygen')
env.Tool('substinfile') env.Tool('substinfile')
env.Tool('targz') env.Tool('targz')
env.Tool('srcdist') env.Tool('srcdist')
env.Tool('glob')
env.Append( CPPPATH = ['#include'], env.Append( CPPPATH = ['#include'],
LIBPATH = lib_dir ) LIBPATH = lib_dir )
@ -165,9 +166,6 @@ def runJSONTests_action( target, source = None, env = None ):
def runJSONTests_string( target, source = None, env = None ): def runJSONTests_string( target, source = None, env = None ):
return 'RunJSONTests("%s")' % source return 'RunJSONTests("%s")' % source
##def buildDoc( doxyfile_path ):
## doc_cmd = env.Doxygen( doxyfile_path )
import SCons.Action import SCons.Action
ActionFactory = SCons.Action.ActionFactory ActionFactory = SCons.Action.ActionFactory
RunJSONTests = ActionFactory(runJSONTests_action, runJSONTests_string ) RunJSONTests = ActionFactory(runJSONTests_action, runJSONTests_string )
@ -182,4 +180,5 @@ env.Alias( 'src-dist', srcdist_cmd )
buildProjectInDirectory( 'src/jsontestrunner' ) buildProjectInDirectory( 'src/jsontestrunner' )
buildProjectInDirectory( 'src/lib_json' ) buildProjectInDirectory( 'src/lib_json' )
buildProjectInDirectory( 'doc' ) buildProjectInDirectory( 'doc' )
#print env.Dump()

View File

@ -21,6 +21,8 @@
- look into iconv, icu and windows API - look into iconv, icu and windows API
\section ms_strict Adds a strict mode to reader/parser \section ms_strict Adds a strict mode to reader/parser
Strict JSON support as specific in RFC 4627 (http://www.ietf.org/rfc/rfc4627.txt?number=4627). Strict JSON support as specific in RFC 4627 (http://www.ietf.org/rfc/rfc4627.txt?number=4627).
- Enforce only object or array as root element
- Disable comment support
\section ms_separation Expose json reader/writer API that do not impose using Json::Value. \section ms_separation Expose json reader/writer API that do not impose using Json::Value.
Some typical use-case involve an application specific structure to/from a JSON document. Some typical use-case involve an application specific structure to/from a JSON document.
- Performance oriented parser/writer: - Performance oriented parser/writer:

View File

@ -2,25 +2,59 @@ Import( 'env' )
import os.path import os.path
if 'doxygen' in env['TOOLS']: if 'doxygen' in env['TOOLS']:
doc_topdir = env['ROOTBUILD_DIR'] doc_topdir = str(env['ROOTBUILD_DIR'])
doxyfile = env.SubstInFile( '#doc/doxyfile', 'doxyfile.in', html_dir = 'jsoncpp-api-doc'
SUBST_DICT = {
'%JSONCPP_VERSION%' : env['JSONCPP_VERSION'],
'%TOPDIR%' : env.Dir('#').abspath,
'%DOC_TOPDIR%' : str(doc_topdir) } )
doc_cmd = env.Doxygen( doxyfile )
alias_doc_cmd = env.Alias('doc', doc_cmd )
env.AlwaysBuild(alias_doc_cmd)
for dir in doc_cmd: doxygen_inputs = env.Glob( includes = '*.dox', dir = '#doc' ) \
env.Alias('doc', env.Install( '#' + dir.path, '#README.txt' ) ) + env.Glob( includes = '*.h', dir = '#include/json/' ) \
filename = os.path.split(dir.path)[1] + env.Glob( includes = ('*.dox','*.h','*.inl','*.cpp'),
targz_path = os.path.join( env['DIST_DIR'], '%s.tar.gz' % filename ) dir = '#src/lib_json' )
zip_doc_cmd = env.TarGz( targz_path, [env.Dir(dir)], ## for p in doxygen_inputs:
TARGZ_BASEDIR = doc_topdir ) ## print p.abspath
env.Depends( zip_doc_cmd, alias_doc_cmd )
env.Alias( 'doc-dist', zip_doc_cmd )
# When doxyfile gets updated, I get errors on the first pass. top_dir = env.Dir('#').abspath
# I have to run scons twice. Something is wrong with the dependencies include_top_dir = env.Dir('#include').abspath
# here, but I avoid it by running "scons doc/doxyfile" first. env['DOXYFILE_DICT'] = { 'PROJECT_NAME': 'JsonCpp',
'PROJECT_NUMBER': env['JSONCPP_VERSION'],
'STRIP_FROM_PATH': top_dir,
'STRIP_FROM_INC_PATH': include_top_dir,
'HTML_OUTPUT': html_dir,
'HTML_HEADER': env.File('#doc/header.html').abspath,
'HTML_FOOTER': env.File('#doc/footer.html').abspath,
'INCLUDE_PATH': include_top_dir,
'PREDEFINED': 'JSONCPP_DOC_EXCLUDE_IMPLEMENTATION JSON_VALUE_USE_INTERNAL_MAP'
}
env['DOXYFILE_FILE'] = 'doxyfile.in'
doxfile_nodes = env.Doxyfile( os.path.join( doc_topdir, 'doxyfile' ), doxygen_inputs )
html_doc_path = os.path.join( doc_topdir, html_dir )
doc_nodes = env.Doxygen( source = doxfile_nodes,
target = os.path.join( html_doc_path, 'index.html' ) )
alias_doc_cmd = env.Alias('doc', doc_nodes )
env.Alias('doc', env.Install( html_doc_path, '#README.txt' ) )
targz_path = os.path.join( env['DIST_DIR'], '%s.tar.gz' % html_dir )
zip_doc_cmd = env.TarGz( targz_path, [env.Dir(html_doc_path)],
TARGZ_BASEDIR = env['ROOTBUILD_DIR'] )
env.Depends( zip_doc_cmd, alias_doc_cmd )
env.Alias( 'doc-dist', zip_doc_cmd )
##
## doxyfile = env.SubstInFile( '#doc/doxyfile', 'doxyfile.in',
## SUBST_DICT = {
## '%JSONCPP_VERSION%' : env['JSONCPP_VERSION'],
## '%TOPDIR%' : env.Dir('#').abspath,
## '%DOC_TOPDIR%' : str(doc_topdir) } )
## doc_cmd = env.Doxygen( doxyfile )
## alias_doc_cmd = env.Alias('doc', doc_cmd )
## env.AlwaysBuild(alias_doc_cmd)
##
## for dir in doc_cmd:
## env.Alias('doc', env.Install( '#' + dir.path, '#README.txt' ) )
## filename = os.path.split(dir.path)[1]
## targz_path = os.path.join( env['DIST_DIR'], '%s.tar.gz' % filename )
## zip_doc_cmd = env.TarGz( targz_path, [env.Dir(dir)],
## TARGZ_BASEDIR = doc_topdir )
## env.Depends( zip_doc_cmd, alias_doc_cmd )
## env.Alias( 'doc-dist', zip_doc_cmd )
##
## # When doxyfile gets updated, I get errors on the first pass.
## # I have to run scons twice. Something is wrong with the dependencies
## # here, but I avoid it by running "scons doc/doxyfile" first.

View File

@ -2,201 +2,112 @@
# emitter depends on doxyfile which is generated from doxyfile.in. # emitter depends on doxyfile which is generated from doxyfile.in.
# build fails after cleaning and relaunching the build. # build fails after cleaning and relaunching the build.
# Todo:
# Add helper function to environment like for glob
# Easier passage of header/footer
# Automatic deduction of index.html path based on custom parameters passed to doxyfile
import os import os
import os.path import os.path
import glob import glob
from fnmatch import fnmatch from fnmatch import fnmatch
import SCons
def DoxyfileParse(file_contents): def Doxyfile_emitter(target, source, env):
""" """
Parse a Doxygen source file and return a dictionary of all the values. Modify the target and source lists to use the defaults if nothing
Values will be strings and lists of strings. else has been specified.
Dependencies on external HTML documentation references are also
appended to the source list.
""" """
data = {} doxyfile_template = env.File(env['DOXYFILE_FILE'])
source.insert(0, doxyfile_template)
import shlex return target, source
lex = shlex.shlex(instream = file_contents, posix = True)
lex.wordchars += "*+./-:"
lex.whitespace = lex.whitespace.replace("\n", "")
lex.escape = ""
lineno = lex.lineno def Doxyfile_Builder(target, source, env):
last_backslash_lineno = lineno """Input:
token = lex.get_token() DOXYFILE_FILE
key = token # the first token should be a key Path of the template file for the output doxyfile
last_token = ""
key_token = False
next_key = False
new_data = True
def append_data(data, key, new_data, token): DOXYFILE_DICT
if new_data or len(data[key]) == 0: A dictionnary of parameter to append to the generated doxyfile
data[key].append(token)
else:
data[key][-1] += token
while token:
if token in ['\n']:
if last_token not in ['\\']:
key_token = True
elif token in ['\\']:
pass
elif key_token:
key = token
key_token = False
else:
if token == "+=":
if not data.has_key(key):
data[key] = list()
elif token == "=":
data[key] = list()
else:
append_data( data, key, new_data, token )
new_data = True
last_token = token
token = lex.get_token()
if last_token == '\\' and token != '\n':
new_data = False
append_data( data, key, new_data, '\\' )
# compress lists of len 1 into single strings
for (k, v) in data.items():
if len(v) == 0:
data.pop(k)
# items in the following list will be kept as lists and not converted to strings
if k in ["INPUT", "FILE_PATTERNS", "EXCLUDE_PATTERNS"]:
continue
if len(v) == 1:
data[k] = v[0]
return data
def DoxySourceScan(node, env, path):
""" """
Doxygen Doxyfile source scanner. This should scan the Doxygen file and add subdir = os.path.split(source[0].abspath)[0]
any files used to generate docs to the list of source files. doc_top_dir = os.path.split(target[0].abspath)[0]
""" doxyfile_path = source[0].abspath
default_file_patterns = [ doxy_file = file( target[0].abspath, 'wt' )
'*.c', '*.cc', '*.cxx', '*.cpp', '*.c++', '*.java', '*.ii', '*.ixx', try:
'*.ipp', '*.i++', '*.inl', '*.h', '*.hh ', '*.hxx', '*.hpp', '*.h++', # First, output the template file
'*.idl', '*.odl', '*.cs', '*.php', '*.php3', '*.inc', '*.m', '*.mm', try:
'*.py', f = file(doxyfile_path, 'rt')
] doxy_file.write( f.read() )
f.close()
default_exclude_patterns = [ doxy_file.write( '\n' )
'*~', doxy_file.write( '# Generated content:\n' )
] except:
raise SCons.Errors.UserError, "Can't read doxygen template file '%s'" % doxyfile_path
sources = [] # Then, the input files
doxy_file.write( 'INPUT = \\\n' )
data = DoxyfileParse(node.get_contents()) for source in source:
if source.abspath != doxyfile_path: # skip doxyfile path, which is the first source
if data.get("RECURSIVE", "NO") == "YES": doxy_file.write( '"%s" \\\n' % source.abspath )
recursive = True doxy_file.write( '\n' )
else: # Dot...
recursive = False values_dict = { 'HAVE_DOT': env.get('DOT') and 'YES' or 'NO',
'DOT_PATH': env.get('DOT') and os.path.split(env['DOT'])[0] or '',
file_patterns = data.get("FILE_PATTERNS", default_file_patterns) 'OUTPUT_DIRECTORY': doc_top_dir,
exclude_patterns = data.get("EXCLUDE_PATTERNS", default_exclude_patterns) 'WARN_LOGFILE': target[0].abspath + '-warning.log'}
values_dict.update( env['DOXYFILE_DICT'] )
doxyfile_dir = str( node.dir ) # Finally, output user dictionary values which override any of the previously set parameters.
for key, value in values_dict.iteritems():
## print 'running from', os.getcwd() doxy_file.write ('%s = "%s"\n' % (key, str(value)))
for node in data.get("INPUT", []): finally:
node_real_path = os.path.normpath( os.path.join( doxyfile_dir, node ) ) doxy_file.close()
if os.path.isfile(node_real_path):
## print str(node), 'is a file'
sources.append(node)
elif os.path.isdir(node_real_path):
## print str(node), 'is a directory'
if recursive:
for root, dirs, files in os.walk(node):
for f in files:
filename = os.path.join(root, f)
pattern_check = reduce(lambda x, y: x or bool(fnmatch(filename, y)), file_patterns, False)
exclude_check = reduce(lambda x, y: x and fnmatch(filename, y), exclude_patterns, True)
if pattern_check and not exclude_check:
sources.append(filename)
## print ' adding source', os.path.abspath( filename )
else:
for pattern in file_patterns:
sources.extend(glob.glob(os.path.join( node, pattern)))
## else:
## print str(node), 'is neither a file nor a directory'
sources = map( lambda path: env.File(path), sources )
return sources
def DoxySourceScanCheck(node, env):
"""Check if we should scan this file"""
return os.path.isfile(node.path)
def DoxyEmitter(source, target, env):
"""Doxygen Doxyfile emitter"""
# possible output formats and their default values and output locations
output_formats = {
"HTML": ("YES", "html"),
"LATEX": ("YES", "latex"),
"RTF": ("NO", "rtf"),
"MAN": ("YES", "man"),
"XML": ("NO", "xml"),
}
## print '#### DoxyEmitter:', source[0].abspath, os.path.exists( source[0].abspath )
data = DoxyfileParse(source[0].get_contents())
targets = []
out_dir = data.get("OUTPUT_DIRECTORY", ".")
# add our output locations
for (k, v) in output_formats.items():
if data.get("GENERATE_" + k, v[0]) == "YES":
targets.append(env.Dir( os.path.join(out_dir, data.get(k + "_OUTPUT", v[1]))) )
# don't clobber targets
for node in targets:
env.Precious(node)
# set up cleaning stuff
for node in targets:
clean_cmd = env.Clean(node, node)
env.Depends( clean_cmd, source )
return (targets, source)
def generate(env): def generate(env):
""" """
Add builders and construction variables for the Add builders and construction variables for the
Doxygen tool. This is currently for Doxygen 1.4.6. Doxygen tool.
""" """
doxyfile_scanner = env.Scanner( ## Doxyfile builder
DoxySourceScan, def doxyfile_message (target, source, env):
"DoxySourceScan", return "creating Doxygen config file '%s'" % target[0]
scan_check = DoxySourceScanCheck,
)
doxyfile_builder = env.Builder( doxyfile_variables = [
action = env.Action("cd ${SOURCE.dir} && ${DOXYGEN} ${SOURCE.file}", 'DOXYFILE_DICT',
varlist=['$SOURCES']), 'DOXYFILE_FILE'
emitter = DoxyEmitter, ]
target_factory = env.fs.Entry,
single_source = True,
source_scanner = doxyfile_scanner,
)
env.Append(BUILDERS = { doxyfile_action = SCons.Action.Action( Doxyfile_Builder, doxyfile_message,
'Doxygen': doxyfile_builder, doxyfile_variables )
})
env.AppendUnique( doxyfile_builder = SCons.Builder.Builder( action = doxyfile_action,
DOXYGEN = 'doxygen', emitter = Doxyfile_emitter )
)
env['BUILDERS']['Doxyfile'] = doxyfile_builder
env['DOXYFILE_DICT'] = {}
env['DOXYFILE_FILE'] = 'doxyfile.in'
## Doxygen builder
def Doxygen_emitter(target, source, env):
output_dir = str( source[0].dir )
if str(target[0]) == str(source[0]):
target = env.File( os.path.join( output_dir, 'html', 'index.html' ) )
return target, source
doxygen_action = SCons.Action.Action( [ '$DOXYGEN_COM'] )
doxygen_builder = SCons.Builder.Builder( action = doxygen_action,
emitter = Doxygen_emitter )
env['BUILDERS']['Doxygen'] = doxygen_builder
env['DOXYGEN_COM'] = '$DOXYGEN $DOXYGEN_FLAGS $SOURCE'
env['DOXYGEN_FLAGS'] = ''
env['DOXYGEN'] = 'doxygen'
dot_path = env.WhereIs("dot")
if dot_path:
env['DOT'] = dot_path
def exists(env): def exists(env):
""" """

53
scons-tools/glob.py Normal file
View File

@ -0,0 +1,53 @@
import fnmatch
import os
def generate( env ):
def Glob( env, includes = None, excludes = None, dir = '.' ):
"""Adds Glob( includes = Split( '*' ), excludes = None, dir = '.')
helper function to environment.
Glob both the file-system files.
includes: list of file name pattern included in the return list when matched.
excludes: list of file name pattern exluced from the return list.
Example:
sources = env.Glob( ("*.cpp", '*.h'), "~*.cpp", "#src" )
"""
def filterFilename(path):
abs_path = os.path.join( dir, path )
if not os.path.isfile(abs_path):
return 0
fn = os.path.basename(path)
match = 0
for include in includes:
if fnmatch.fnmatchcase( fn, include ):
match = 1
break
if match == 1 and not excludes is None:
for exclude in excludes:
if fnmatch.fnmatchcase( fn, exclude ):
match = 0
break
return match
if includes is None:
includes = ('*',)
elif type(includes) in ( type(''), type(u'') ):
includes = (includes,)
if type(excludes) in ( type(''), type(u'') ):
excludes = (excludes,)
dir = env.Dir(dir).abspath
paths = os.listdir( dir )
def makeAbsFileNode( path ):
return env.File( os.path.join( dir, path ) )
nodes = filter( filterFilename, paths )
return map( makeAbsFileNode, nodes )
from SCons.Script import Environment
Environment.Glob = Glob
def exists(env):
"""
Tool always exists.
"""
return True