import argparse import copy import re import shutil import sys import os class ConditionalFeature: def __init__(self, header_macro, custom_source_macro, header, files): self._header_macro = header_macro self._custom_source_macro = custom_source_macro self._files = files self._header = header def print_files(self, lib_dir, file_dumper): file_dumper.get_handle().write( '#if defined({}) && defined({})\n'.format(self._header_macro, self._custom_source_macro)) for entry in self._files: file_dumper.get_handle().write('#elif defined({}) && ({})\n'.format(self._header_macro, entry.condition)) file_dumper.dump_file(os.path.join(lib_dir, entry.source_path)) file_dumper.get_handle().write("#endif\n") def is_using_file(self, file_path): for f in self._files: if f.source_path == file_path: return True return False class ConditionalSource: def __init__(self, source_path, condition): self.source_path = source_path self.condition = condition skip_headers = ['hawktracer/event_macros_impl.h'] platform_features = [ ConditionalFeature( 'HT_PLATFORM_FEATURE_CPU_USAGE_ENABLED', 'HT_PLATFORM_FEATURE_CPU_USAGE_CUSTOM_SOURCE', 'hawktracer/cpu_usage.h', [ConditionalSource('platform/linux/cpu_usage.c', "defined(__unix__)")]), ConditionalFeature( 'HT_PLATFORM_FEATURE_MEMORY_USAGE_ENABLED', 'HT_PLATFORM_FEATURE_MEMORY_USAGE_CUSTOM_SOURCE', 'hawktracer/memory_usage.h', [ ConditionalSource('platform/linux/memory_usage.c', "defined(__unix__)"), ConditionalSource('platform/windows/memory_usage.c', "defined(_WIN32)") ]), ConditionalFeature( 'HT_PLATFORM_FEATURE_ALLOC_HOOKS_ENABLED', 'HT_PLATFORM_FEATURE_ALLOC_HOOKS_CUSTOM_SOURCE', 'hawktracer/alloc_hooks.h', [ConditionalSource('platform/linux/alloc_hooks.c', "defined(__unix__)")]) ] def is_platform_feature(file_path): for feature in platform_features: if feature.is_using_file(file_path): return True return False def load_file_list(root_dir, extensions, path_prefix, current_path=''): file_list = [] for entry in os.scandir(os.path.join(root_dir, current_path)): if entry.is_dir(): file_list += load_file_list(root_dir, extensions, path_prefix, os.path.join(current_path, entry.name)) else: ext = os.path.splitext(entry.name)[-1].lower() if ext in extensions: file_list.append(os.path.join(path_prefix, current_path, entry.name)) return file_list def get_header(line): header_regex = re.compile(r'#\s*include\s+[<"](?P.*?)[">]') m = header_regex.match(line) if not m or 1 != len(m.groups()): return None return m.groups()[0] class Graph: def __init__(self, nodes): self._nodes = {} for node in nodes: if node not in self._nodes: self._nodes[node] = [] def _get_free_node(self): for key, value in self._nodes.items(): if len(value) == 0: return key return None def add_edge(self, src_node, dest_node): if src_node not in self._nodes[dest_node]: self._nodes[dest_node].append(src_node) def topology_sort(self): sorted_nodes = [] init_nodes = copy.deepcopy(self._nodes) while self._nodes: node = self._get_free_node() assert node sorted_nodes.append(node) self.remove_node(node) self._nodes = init_nodes return sorted_nodes def remove_node(self, node): del self._nodes[node] for key, value in self._nodes.items(): if node in value: value.remove(node) class HeadersDB: _header_extensions = ('.h', '.hpp') config_header = 'hawktracer/ht_config.h' def __init__(self, lib_dir): self._include_dir = os.path.join(lib_dir, 'include') self._public_headers = load_file_list(os.path.join(self._include_dir, 'hawktracer'), self._header_extensions, 'hawktracer') + [self.config_header] self._internal_headers = load_file_list(os.path.join(self._include_dir, 'internal'), self._header_extensions, 'internal') def is_hawktracer_header(self, header_path): return header_path in self._public_headers or header_path in self._internal_headers def _get_headers_from_file(self, header_file): if header_file == self.config_header: return [] with open(os.path.join(self._include_dir, header_file)) as f: for line in f: header = get_header(line) if header: yield header def _get_header_graph(self, headers): g = Graph(headers) for header in headers: for dependency in self._get_headers_from_file(header): if dependency in headers: g.add_edge(dependency, header) return g def get_internal_header_graph(self): return self._get_header_graph(self._internal_headers) def get_public_header_graph(self): return self._get_header_graph(self._public_headers) class FileDumper: def __init__(self, file_handle, headers_db): self._file_handle = file_handle self._headers_db = headers_db @staticmethod def _is_header_guard(line): base_pattern = "\\s+HAWKTRACER_[A-Z_0-9]+_(H|HPP)\\s*" return re.match("^\s*#ifndef{}$".format(base_pattern), line, re.M) or re.match( "^\s*#define{}$".format(base_pattern), line, re.M) or re.match( "^\s*#endif\s*/\*{}\*/\s*$".format(base_pattern), line, re.M) def _is_line_ok_to_write(self, line): if FileDumper._is_header_guard(line): return False header = get_header(line) if not header: return True return not self._headers_db.is_hawktracer_header(header) def get_handle(self): return self._file_handle class HeaderFileDumper(FileDumper): def __init__(self, file_handle, headers_db, include_dir): FileDumper.__init__(self, file_handle, headers_db) self._include_dir = include_dir def dump_file(self, in_file_name): if in_file_name == HeadersDB.config_header: self._file_handle.write("#include \"ht_config.h\"\n") return with open(os.path.join(self._include_dir, in_file_name)) as f: for line in f: if self._is_line_ok_to_write(line): self._file_handle.write(line) class SourceFileDumper(FileDumper): def __init__(self, file_handle, headers_db): FileDumper.__init__(self, file_handle, headers_db) def dump_file(self, in_file_name): self._file_handle.write("/** File {} **/\n".format(in_file_name)) with open(in_file_name) as f: for line in f: if self._is_line_ok_to_write(line): self._file_handle.write(line) self._file_handle.write("/** File {} **/\n\n".format(in_file_name)) class Amalgamator: _source_extensions = ('.c', '.cpp') def __init__(self, root_dir, out_header, out_source): self._header_list = [] self._lib_dir = os.path.join(root_dir, 'lib') self._license_file = os.path.join(root_dir, "LICENSE") self._include_dir = os.path.join(self._lib_dir, 'include') self._source_list = load_file_list(self._lib_dir, self._source_extensions, '') self._out_header = out_header self._out_source = out_source self._headers = HeadersDB(self._lib_dir) def _dump_license(self, file_handle): with open(self._license_file, 'r') as license_file: file_handle.write("/************************************\n") for line in license_file: file_handle.write(" * " + line) file_handle.write(" ***********************************/\n\n") def _generate_header_file(self): print("Generate header file " + self._out_header) with open(self._out_header, "w") as f: header_file_dumper = HeaderFileDumper(f, self._headers, self._include_dir) self._dump_license(f) header_guard = "HAWKTRACER_ALL_H" f.write("#ifndef {}\n".format(header_guard)) f.write("#define {}\n\n".format(header_guard)) for header in self._headers.get_public_header_graph().topology_sort(): header_file_dumper.dump_file(header) f.write("#endif /* {} */\n".format(header_guard)) def _generate_source_file(self): print("Generate source file " + self._out_source) with open(self._out_source, "w") as f: source_file_dumper = SourceFileDumper(f, self._headers) header_file_dumper = HeaderFileDumper(f, self._headers, self._include_dir) self._dump_license(f) f.write("#define MKCREFLECT_IMPL /* define events declared in header */\n") header_file_dumper.dump_file("hawktracer/event_macros_impl.h") f.write("#include \"hawktracer.h\"\n\n") for header in self._headers.get_internal_header_graph().topology_sort(): header_file_dumper.dump_file(header) for source_file in self._source_list: if not is_platform_feature(source_file): source_file_dumper.dump_file(os.path.join(self._lib_dir, source_file)) for feature in platform_features: feature.print_files(self._lib_dir, source_file_dumper) def generate(self): print("Generating files...") self._generate_header_file() self._generate_source_file() print("Generating files completed successfully!") def run_program(argv): parser = argparse.ArgumentParser(description='Generate a single amalgamated source/header file for HawkTracer ' 'library.') parser.add_argument('--root-dir', help='Project root directory', required=True) parser.add_argument('--out-dir', help='Output directory', default='.') parser.add_argument('--config-path', help='Path to a ht_config.h file', required=True) parser.add_argument('--cpp', help='Generate C++ source file', action='store_true') args = parser.parse_args(argv) os.makedirs(args.out_dir, exist_ok=True) shutil.copyfile(args.config_path, os.path.join(args.out_dir, "ht_config.h")) header_file = os.path.join(args.out_dir, 'hawktracer.h') source_file = os.path.join(args.out_dir, 'hawktracer.' + ('cpp' if args.cpp else 'c')) Amalgamator(args.root_dir, header_file, source_file).generate() if __name__ == '__main__': run_program(sys.argv[1:])