| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- #!/usr/bin/python
- # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
- # 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.
- import argparse
- import re
- import sys
- from os import path
- 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(f'#line 1 "{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(f'#line {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(f'#line 1 "{filename}"', file=source_out)
- print(f'#include "{header_out.name}"', file=source_out)
- process_file(
- f, abs_path, source_out, header_out, include_paths, public_include_paths
- )
- if __name__ == "__main__":
- main()
|