memgraph/cmake/recursive_include
florijan 2aeef25881 Traversal namespace specialization added for real DB primitives
Summary: database/GraphDbAccessor and storage/...Accessor now returns iterators over lazily created Accessors (filtered on visibility). Dependecies and forward declarations reorganized to support new code

Reviewers: buda, teon.banek

Reviewed By: buda

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D83
2017-03-03 14:01:01 +01:00

179 lines
5.7 KiB
Python
Executable File

#!/usr/bin/env python
"""
A script for finding and [copying|printing] C++
headers that get recursively imported from one (or more)
starting points.
Supports absolute imports relative to some root folder
(project root) and relative imports relative to the
header that is doing the importing.
Does not support conditional imports (resulting from
#ifdef macros and such). All the #import statements
found in a header (one #import per line) are traversed.
Supports Python2 and Python3.
"""
__author__ = "Florijan Stamenkovic"
__copyright__ = "Copyright 2017, Memgraph"
import logging
import sys
import os
import re
import shutil
from argparse import ArgumentParser
# the prefix of an include directive
PREFIX = "#include"
log = logging.getLogger(__name__)
def parse_args():
argp = ArgumentParser(description=__doc__)
argp.add_argument("--logging", default="INFO", choices=["INFO", "DEBUG"],
help="Logging level")
argp.add_argument("--roots", required=True, nargs="+",
help="One or more paths in which headers are sought")
argp.add_argument("--start", required=True, nargs="+",
help="One or more headers from which to start scanning")
argp.add_argument("--stdout", action="store_true",
help="If found paths should be printed out to stdout")
argp.add_argument("--copy", default=None,
help="Prefix of the path where the headers should be copied")
return argp.parse_args()
def main():
args = parse_args()
logging.basicConfig(level=args.logging)
log.info("Recursively detecting used C/C++ headers in roots '%s' with starting point(s) '%s'",
args.roots, args.start)
args.roots = [os.path.abspath(p) for p in args.roots]
results = set()
for start in args.start:
find_recursive(start, args.roots, results)
results = list(sorted(results))
log.debug("Found %d paths:", len(results))
for r in results:
log.debug("\t%s", r)
# print out the results if required
if args.stdout:
for result in results:
print(result)
# copy the results if required
if args.copy is not None:
for root, path in results:
from_path = os.path.join(root, path)
to_path = os.path.join(args.copy, path)
log.debug("Copying '%s' to '%s'", from_path, to_path)
# create a directory if necessary, Py2 and Py3 compatible
to_dir = os.path.dirname(to_path)
if not os.path.exists(to_dir):
os.makedirs(to_dir)
shutil.copy(from_path, to_path)
def abs_to_relative(roots, path):
"""
Args:
roots: list of str, a list of possible prefixes
to the 'path'.
path: str, a path to a file.
Return:
A tuple (relative_path, root) where 'root' is one
of the given 'roots' and where
os.path.join(root, relative_path) equals the given
'path'
Raise:
An exception if none of the 'roots' is a prefix of
'path'
"""
for root in roots:
if path.startswith(root):
return (root, path[len(root) + 1:])
raise Exception("Failed to find prefix of '%s'in '%r'" % (
path, roots))
def find_recursive(path, roots, results):
"""
Recursivelly looks for headers and adds them to results.
Results are added as tuples of form (root, header_path)
where 'root' is one of the given roots: the one in which
the header was found, and 'header_path' is the found
header's path relative to 'root'.
Args:
path: str of tuple. If str, it's considered a path
that has one of the roots as prefix. If tuple
it's considered a (prefix, suffix) that defines
a path.
In both forms the path is to a header. This header is added
to results and scanned for #include statements of
other headers. For each #include (relative to current
`path` or to `project_root`) for which a file is found
this same function is called.
roots: list of str, List of folders in which headers are
sought. Must be absolute paths.
results: a collection into which the results are
added. The collection contains tuples of form
(root, path), see function description.
"""
log.debug("Processing path: %s", path)
if isinstance(path, str):
path = os.path.abspath(path)
path = abs_to_relative(roots, path)
# from this point onward 'path' is a tuple (root, suffix)
if path in results:
log.debug("Skipping already present path '%r'", path)
return
log.debug("Adding path '%r'", path)
results.add(path)
# go through files and look for include directives
with open(os.path.join(*path)) as f:
for line in filter(lambda l: l.startswith(PREFIX),
map(lambda l: l.strip(), f)):
include = line[len(PREFIX):].strip()
include = re.sub("[\"\'\>\<]", "", include)
log.debug("Processing include '%s'", include)
# search for the include relatively to the current header
include_rel = os.path.join(
os.path.dirname(os.path.join(*path)), include)
if os.path.exists(include_rel) and os.path.isfile(include_rel):
find_recursive(include_rel, roots, results)
continue
# search for file in roots
for root in roots:
include_abs = os.path.join(root, include)
if os.path.exists(include_abs) and os.path.isfile(include_abs):
find_recursive((root, include), roots, results)
continue
if __name__ == '__main__':
main()