commit
7e4ee42315
@ -0,0 +1,110 @@ |
||||
#!/usr/bin/python |
||||
|
||||
# amalgamate.py creates an amalgamation from a unity build. |
||||
# It can be run with either Python 2 or 3. |
||||
# An amalgamation consists of a header that includes the contents of all public |
||||
# headers and a source file that includes the contents of all source files and |
||||
# private headers. |
||||
# |
||||
# This script works by starting with the unity build file and recursively expanding |
||||
# #include directives. If the #include is found in a public include directory, |
||||
# that header is expanded into the amalgamation header. |
||||
# |
||||
# A particular header is only expanded once, so this script will |
||||
# break if there are multiple inclusions of the same header that are expected to |
||||
# expand differently. Similarly, this type of code causes issues: |
||||
# |
||||
# #ifdef FOO |
||||
# #include "bar.h" |
||||
# // code here |
||||
# #else |
||||
# #include "bar.h" // oops, doesn't get expanded |
||||
# // different code here |
||||
# #endif |
||||
# |
||||
# The solution is to move the include out of the #ifdef. |
||||
|
||||
from __future__ import print_function |
||||
|
||||
import argparse |
||||
from os import path |
||||
import re |
||||
import sys |
||||
|
||||
include_re = re.compile('^[ \t]*#include[ \t]+"(.*)"[ \t]*$') |
||||
included = set() |
||||
excluded = set() |
||||
|
||||
def find_header(name, abs_path, include_paths): |
||||
samedir = path.join(path.dirname(abs_path), name) |
||||
if path.exists(samedir): |
||||
return samedir |
||||
for include_path in include_paths: |
||||
include_path = path.join(include_path, name) |
||||
if path.exists(include_path): |
||||
return include_path |
||||
return None |
||||
|
||||
def expand_include(include_path, f, abs_path, source_out, header_out, include_paths, public_include_paths): |
||||
if include_path in included: |
||||
return False |
||||
|
||||
included.add(include_path) |
||||
with open(include_path) as f: |
||||
print('#line 1 "{}"'.format(include_path), file=source_out) |
||||
process_file(f, include_path, source_out, header_out, include_paths, public_include_paths) |
||||
return True |
||||
|
||||
def process_file(f, abs_path, source_out, header_out, include_paths, public_include_paths): |
||||
for (line, text) in enumerate(f): |
||||
m = include_re.match(text) |
||||
if m: |
||||
filename = m.groups()[0] |
||||
# first check private headers |
||||
include_path = find_header(filename, abs_path, include_paths) |
||||
if include_path: |
||||
if include_path in excluded: |
||||
source_out.write(text) |
||||
expanded = False |
||||
else: |
||||
expanded = expand_include(include_path, f, abs_path, source_out, header_out, include_paths, public_include_paths) |
||||
else: |
||||
# now try public headers |
||||
include_path = find_header(filename, abs_path, public_include_paths) |
||||
if include_path: |
||||
# found public header |
||||
expanded = False |
||||
if include_path in excluded: |
||||
source_out.write(text) |
||||
else: |
||||
expand_include(include_path, f, abs_path, header_out, None, public_include_paths, []) |
||||
else: |
||||
sys.exit("unable to find {}, included in {} on line {}".format(filename, abs_path, line)) |
||||
|
||||
if expanded: |
||||
print('#line {} "{}"'.format(line+1, abs_path), file=source_out) |
||||
elif text != "#pragma once\n": |
||||
source_out.write(text) |
||||
|
||||
def main(): |
||||
parser = argparse.ArgumentParser(description="Transform a unity build into an amalgamation") |
||||
parser.add_argument("source", help="source file") |
||||
parser.add_argument("-I", action="append", dest="include_paths", help="include paths for private headers") |
||||
parser.add_argument("-i", action="append", dest="public_include_paths", help="include paths for public headers") |
||||
parser.add_argument("-x", action="append", dest="excluded", help="excluded header files") |
||||
parser.add_argument("-o", dest="source_out", help="output C++ file", required=True) |
||||
parser.add_argument("-H", dest="header_out", help="output C++ header file", required=True) |
||||
args = parser.parse_args() |
||||
|
||||
include_paths = list(map(path.abspath, args.include_paths or [])) |
||||
public_include_paths = list(map(path.abspath, args.public_include_paths or [])) |
||||
excluded.update(map(path.abspath, args.excluded or [])) |
||||
filename = args.source |
||||
abs_path = path.abspath(filename) |
||||
with open(filename) as f, open(args.source_out, 'w') as source_out, open(args.header_out, 'w') as header_out: |
||||
print('#line 1 "{}"'.format(filename), file=source_out) |
||||
print('#include "{}"'.format(header_out.name), file=source_out) |
||||
process_file(f, abs_path, source_out, header_out, include_paths, public_include_paths) |
||||
|
||||
if __name__ == "__main__": |
||||
main() |
Loading…
Reference in new issue