summaryrefslogtreecommitdiffstats
path: root/src/rocksdb/build_tools/amalgamate.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/rocksdb/build_tools/amalgamate.py')
-rwxr-xr-xsrc/rocksdb/build_tools/amalgamate.py168
1 files changed, 168 insertions, 0 deletions
diff --git a/src/rocksdb/build_tools/amalgamate.py b/src/rocksdb/build_tools/amalgamate.py
new file mode 100755
index 000000000..f79e9075e
--- /dev/null
+++ b/src/rocksdb/build_tools/amalgamate.py
@@ -0,0 +1,168 @@
+#!/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.
+
+from __future__ import print_function
+
+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('#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()