mirror of
https://github.com/cesanta/mongoose.git
synced 2024-11-27 20:59:00 +08:00
198 lines
6.8 KiB
Python
Executable File
198 lines
6.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Generate a reversible amalgamation of several C source files
|
|
# along with their required internal headers.
|
|
#
|
|
# This script assumes that there are a bunch of C files, a bunch
|
|
# of private header files and one public header file.
|
|
#
|
|
# The script takes a list of C file names, parses `#include` directives
|
|
# found in them and recursively resolves dependencies in such a way
|
|
# that a header referenced from an included header will be emitted before the
|
|
# header that depends on it. All headers will always be emitted before the
|
|
# source files.
|
|
#
|
|
# The embedded include files usually contain private internals.
|
|
# However sometimes it's necessary for some other tools or for advanced users
|
|
# to have access to internal definitions. One such example is the generated
|
|
# C source containing frozen heap. The amalgamation script will allow users
|
|
# to include the amalgamated C file and cause extract only the internal headers:
|
|
#
|
|
# #define NS_EXPORT_INTERNAL_HEADERS
|
|
# #include "v7.c"
|
|
#
|
|
# Where `NS` can be overridden via the --prefix flag.
|
|
# This feature can be enabled with the --exportable-headers, and basically
|
|
# all it does is to wrap the C body in a preprocessor guard.
|
|
#
|
|
# TODO(mkm): make it work also for mongoose where we also generate
|
|
# the public header from a bunch of unamalgamated header files.
|
|
# Currently this script can handle mongoose amalgamation because it doesn't
|
|
# flip the --autoinc flag.
|
|
#
|
|
|
|
import argparse
|
|
import io
|
|
import re
|
|
import sys
|
|
import os
|
|
|
|
parser = argparse.ArgumentParser(description='Produce an amalgamated source')
|
|
parser.add_argument('--prefix', default="NS",
|
|
help='prefix for MODULE_LINES guard')
|
|
parser.add_argument('--srcdir', default=".", help='source dir')
|
|
parser.add_argument('--ignore', default="",
|
|
help='comma separated list of files to not amalgam')
|
|
# hack, teach amalgam to render the LICENSE file instead
|
|
parser.add_argument('--first', type=str, help='put this file in first position.'
|
|
' Usually contains licensing info')
|
|
parser.add_argument('--public-header', dest="public",
|
|
help='name of the public header file that will be'
|
|
' included at the beginning of the file')
|
|
parser.add_argument('--autoinc', action='store_true',
|
|
help='automatically embed include files')
|
|
parser.add_argument('--strict', action='store_true',
|
|
help='fail loudly if an include file cannot be resolved')
|
|
parser.add_argument('--norel', action='store_true',
|
|
help="do not try to compute a friendly relative path")
|
|
parser.add_argument('--exportable-headers', dest="export", action='store_true',
|
|
help='allow exporting internal headers')
|
|
parser.add_argument('-I', default=['.'], dest='include_path', help='include path', action='append')
|
|
parser.add_argument('sources', nargs='*', help='sources')
|
|
parser.add_argument('--license', dest="license", help='License file')
|
|
|
|
class File(object):
|
|
def __init__(self, name, parent_name):
|
|
self.name = name
|
|
self.parent_name = parent_name
|
|
self.buf = io.StringIO()
|
|
emit_file(self.buf, self.name, self.parent_name)
|
|
|
|
def emit(self):
|
|
print('%s' % self.buf.getvalue(), end='')
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
sources = []
|
|
includes = []
|
|
|
|
already_included = set()
|
|
|
|
ignore_files = [i.strip() for i in args.ignore.split(',')]
|
|
|
|
def should_ignore(name, parent_name):
|
|
return (name in already_included
|
|
or not (args.strict or os.path.exists(resolve(name, parent_name)))
|
|
or name in ignore_files)
|
|
|
|
def resolve(path, parent_name):
|
|
path_from_parent = None
|
|
if parent_name != None and not os.path.isabs(path):
|
|
# calculate the path relative to the "parent_name" file, i.e. to the file
|
|
# which includes the current one.
|
|
path_from_parent = os.path.join(os.path.dirname(parent_name), path)
|
|
|
|
if os.path.isabs(path) or os.path.exists(path):
|
|
p = path
|
|
elif path_from_parent != None and os.path.exists(path_from_parent):
|
|
p = path_from_parent
|
|
else:
|
|
for ip in args.include_path:
|
|
p = os.path.join(ip, path)
|
|
if os.path.exists(p):
|
|
break
|
|
if os.path.exists(p) and not args.norel:
|
|
p = os.path.realpath(p).replace('%s%s' % (os.getcwd(), os.sep), '')
|
|
return p.replace(os.sep, '/')
|
|
|
|
def emit_line_directive(out, name, parent_name):
|
|
print ('''#ifdef %(prefix)s_MODULE_LINES
|
|
#line 1 "%(name)s"
|
|
#endif''' % dict(
|
|
prefix = args.prefix,
|
|
name = resolve(name, parent_name),
|
|
), file=out)
|
|
|
|
def emit_body(out, name, parent_name):
|
|
resolved_name = resolve(name, parent_name)
|
|
if not args.strict and not os.path.exists(resolved_name):
|
|
print('#include "%s"' % name, file=out)
|
|
return
|
|
|
|
with open(resolved_name) as f:
|
|
leading_spaces = True
|
|
in_comment = False
|
|
comment = ''
|
|
for l in f:
|
|
if leading_spaces:
|
|
if not l.strip():
|
|
continue
|
|
leading_spaces = False
|
|
if in_comment:
|
|
comment += l
|
|
if re.match('\s*\*/$', l):
|
|
in_comment = False
|
|
if not re.match('.*Copyright.*Cesanta', comment, re.M | re.S):
|
|
out.write(comment)
|
|
continue
|
|
|
|
if re.match('/\*$', l):
|
|
in_comment = True
|
|
comment = l
|
|
continue
|
|
|
|
match = re.match('( *#include "(.*)")', l)
|
|
if match:
|
|
all, path_to_include = match.groups()
|
|
if args.autoinc:
|
|
if not should_ignore(path_to_include, parent_name):
|
|
already_included.add(path_to_include)
|
|
includes.append(File(path_to_include, resolved_name))
|
|
print('/* Amalgamated: %s */' % all, file=out)
|
|
else:
|
|
out.write(l)
|
|
|
|
|
|
def emit_file(out, name, parent_name):
|
|
emit_line_directive(out, name, parent_name)
|
|
emit_body(out, name, parent_name)
|
|
|
|
for i in args.sources:
|
|
sources.append(File(i, None))
|
|
|
|
if args.first:
|
|
for inc in reversed(args.first.split(',')):
|
|
for i, f in enumerate(includes):
|
|
if f.name == inc:
|
|
del includes[i]
|
|
includes.insert(0, f)
|
|
break
|
|
|
|
# emitting
|
|
|
|
if sys.platform == "win32":
|
|
import os, msvcrt
|
|
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
|
|
|
|
|
if args.license:
|
|
print('/*')
|
|
for l in open(args.license):
|
|
print((' * %s' % l.rstrip()).rstrip())
|
|
print(' */')
|
|
print('')
|
|
|
|
if args.public:
|
|
print('#include "%s"' % args.public)
|
|
|
|
for i in includes:
|
|
i.emit()
|
|
|
|
if args.export:
|
|
print('#ifndef %s_EXPORT_INTERNAL_HEADERS' % (args.prefix,))
|
|
for i in sources:
|
|
i.emit()
|
|
if args.export:
|
|
print('#endif /* %s_EXPORT_INTERNAL_HEADERS */' % (args.prefix,))
|