From 722214c255d3e0a4b220f1b20c06adf6545faca1 Mon Sep 17 00:00:00 2001 From: Divyanshu Madan Date: Tue, 6 Jul 2021 22:49:54 +0530 Subject: [PATCH 1/5] [cmake_frontend.py] cmake file api frontend --- clang_bind/cmake_frontend.py | 162 +++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 clang_bind/cmake_frontend.py diff --git a/clang_bind/cmake_frontend.py b/clang_bind/cmake_frontend.py new file mode 100644 index 0000000..3ee1402 --- /dev/null +++ b/clang_bind/cmake_frontend.py @@ -0,0 +1,162 @@ +import os +import json + + +class Codemodel: + def __init__(self, reply_dir): + self.reply_dir = reply_dir + self.codemodel = {} + self.codemodel_targets = {} + + def set_codemodel(self, codemodel_file=None): + if not codemodel_file: + for file in os.listdir(self.reply_dir): + if file.startswith("codemodel"): + codemodel_file = os.path.join(self.reply_dir, file) + with open(codemodel_file) as f: + self.codemodel = json.load(f) + + def get_codemodel(self): + if not self.codemodel: + self.set_codemodel() + return self.codemodel + + def set_codemodel_targets(self, codemodel=None): + if not codemodel: + codemodel = self.get_codemodel() + configurations = codemodel.get("configurations", []) + for configuration in configurations: + targets = configuration.get("targets", []) + for target in targets: + self.codemodel_targets.update({target["name"]: target["jsonFile"]}) + + def get_codemodel_targets(self): + if not self.codemodel_targets: + self.set_codemodel_targets() + return self.codemodel_targets + + +class Target: + def __init__(self, reply_dir, target_name): + self.reply_dir = reply_dir + self.target_file = Codemodel(reply_dir).get_codemodel_targets().get(target_name) + self.target = {} + + def set_target(self, target_file=None): + if not target_file: + target_file = self.target_file + with open(os.path.join(self.reply_dir, target_file)) as f: + self.target = json.load(f) + + def get_target(self): + if not self.target: + self.set_target() + return self.target + + def get_artifacts(self): + return [ + artifact.get("path") for artifact in self.get_target().get("artifacts", []) + ] + + def get_commands(self): + return self.get_target().get("backtraceGraph", {}).get("commands") + + def get_compile_groups(self): + return [ + { + "fragments": [ + compile_command_fragment.get("fragment") + for compile_command_fragment in compile_group.get( + "compileCommandFragments", [] + ) + ], + "defines": [ + define.get("define") for define in compile_group.get("defines", []) + ], + "includes": [ + include.get("path") for include in compile_group.get("includes", []) + ], + "language": compile_group.get("language"), + } + for compile_group in self.get_target().get("compileGroups", []) + ] + + def get_dependencies(self): + return [ + dependency.get("id") + for dependency in self.get_target().get("dependencies", []) + ] + + def get_files(self): + return self.get_target().get("backtraceGraph", {}).get("files") + + def get_folder(self): + return self.get_target().get("folder", {}).get("name") + + def get_id(self): + return self.get_target().get("id") + + def get_install(self): + install = self.get_target().get("install", {}) + return { + "destinations": [ + destination.get("path") + for destination in install.get("destinations", []) + ], + "prefix": install.get("prefix", {}).get("path"), + } + + def get_link(self): + link = self.get_target().get("link", {}) + command_fragments = link.get("commandFragments", []) + return { + "flags_fragments": [ + command_fragment.get("fragment") + for command_fragment in command_fragments + if command_fragment.get("role") == "flags" + ], + "libraries_fragments": [ + command_fragment.get("fragment") + for command_fragment in command_fragments + if command_fragment.get("role") == "libraries" + ], + "language": link.get("language"), + } + + def get_name(self): + return self.get_target().get("name") + + def get_name_on_disk(self): + return self.get_target().get("nameOnDisk") + + def get_paths(self): + return self.get_target().get("paths") + + def get_sources(self): + return [sources.get("path") for sources in self.get_target().get("sources", [])] + + def get_type(self): + return self.get_target().get("type") + + +class CMakeFileAPI: + def __init__(self, build_dir): + reply_dir = os.path.join(build_dir, ".cmake", "api", "v1", "reply") + self.targets = {} + for codemodel_target in Codemodel(reply_dir).get_codemodel_targets(): + target = Target(reply_dir, codemodel_target) + self.targets[target.get_name()] = target + + def get_dependencies(self, target=None): + targets = [self.targets.get(target)] if target else self.targets.values() + return { + target.get_name(): list( + map(lambda x: x.split("::")[0], target.get_dependencies()) + ) + for target in targets + } + + def get_sources(self, target=None): + targets = [self.targets.get(target)] if target else self.targets.values() + return {target.get_name(): target.get_sources() for target in targets} + From 53adb4ac43fbe5c1937286c3b2fe713c95dd46c6 Mon Sep 17 00:00:00 2001 From: Divyanshu Madan Date: Sun, 18 Jul 2021 06:56:43 +0530 Subject: [PATCH 2/5] [cmake_frontend.py] compilation db frontend --- clang_bind/cmake_frontend.py | 37 +++++++++++++++++++++++++++ clang_bind/compilation_database.py | 40 ------------------------------ 2 files changed, 37 insertions(+), 40 deletions(-) delete mode 100644 clang_bind/compilation_database.py diff --git a/clang_bind/cmake_frontend.py b/clang_bind/cmake_frontend.py index 3ee1402..2134ab5 100644 --- a/clang_bind/cmake_frontend.py +++ b/clang_bind/cmake_frontend.py @@ -1,5 +1,42 @@ import os import json +import clang.cindex as clang + + +class CompilationDatabase: + """ + Build a compilation database from a given directory + """ + + def __init__(self, build_dir): + self.compilation_database = clang.CompilationDatabase.fromDirectory( + buildDir=build_dir + ) + + def get_compilation_arguments(self, filename=None): + """ + Returns the compilation commands extracted from the compilation database + + Parameters: + - filename (optional): To get compilaton commands of a file + + Returns: + - compilation_arguments (dict): {filename: compiler arguments} + """ + + if filename: + # Get compilation commands from the compilation database for the given file + compilation_commands = self.compilation_database.getCompileCommands( + filename=filename + ) + else: + # Get all compilation commands from the compilation database + compilation_commands = self.compilation_database.getAllCompileCommands() + + return { + command.filename: list(command.arguments)[1:-1] + for command in compilation_commands + } class Codemodel: diff --git a/clang_bind/compilation_database.py b/clang_bind/compilation_database.py deleted file mode 100644 index 7883ac6..0000000 --- a/clang_bind/compilation_database.py +++ /dev/null @@ -1,40 +0,0 @@ -import clang.cindex as clang - - -class CompilationDatabase: - """ - Build a compilation database from a given directory - """ - - def __init__(self, compilation_database_path): - self.compilation_database = clang.CompilationDatabase.fromDirectory( - buildDir=compilation_database_path - ) - - def get_compilation_arguments(self, filename=None): - """ - Returns the compilation commands extracted from the compilation database - - Parameters: - - compilation_database_path: The path to `compile_commands.json` - - filename (optional): To get compilaton commands of a file - - Returns: - - compilation_arguments (dict): {filename: compiler arguments} - """ - - if filename: - # Get compilation commands from the compilation database for the given file - compilation_commands = self.compilation_database.getCompileCommands( - filename=filename - ) - else: - # Get all compilation commands from the compilation database - compilation_commands = self.compilation_database.getAllCompileCommands() - - # {file: compiler arguments} - compilation_arguments = { - command.filename: list(command.arguments)[1:-1] - for command in compilation_commands - } - return compilation_arguments From 0cda4a1d365b86e5c5aa64fad456eef22df95bed Mon Sep 17 00:00:00 2001 From: Divyanshu Madan Date: Fri, 13 Aug 2021 19:14:32 +0530 Subject: [PATCH 3/5] [cmake_frontend.py] use pathlib instead of os; add documentation --- clang_bind/cmake_frontend.py | 149 +++++++++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 17 deletions(-) diff --git a/clang_bind/cmake_frontend.py b/clang_bind/cmake_frontend.py index 2134ab5..f6d4879 100644 --- a/clang_bind/cmake_frontend.py +++ b/clang_bind/cmake_frontend.py @@ -1,12 +1,10 @@ -import os import json import clang.cindex as clang +from pathlib import Path class CompilationDatabase: - """ - Build a compilation database from a given directory - """ + """Class to get information from a CMake compilation database.""" def __init__(self, build_dir): self.compilation_database = clang.CompilationDatabase.fromDirectory( @@ -14,14 +12,12 @@ def __init__(self, build_dir): ) def get_compilation_arguments(self, filename=None): - """ - Returns the compilation commands extracted from the compilation database - - Parameters: - - filename (optional): To get compilaton commands of a file + """Returns the compilation commands extracted from the compilation database - Returns: - - compilation_arguments (dict): {filename: compiler arguments} + :param filename: Get compilation arguments of the file, defaults to None: get for all files + :type filename: str, optional + :return: ilenames and their compiler arguments: {filename: compiler arguments} + :rtype: dict """ if filename: @@ -40,25 +36,42 @@ def get_compilation_arguments(self, filename=None): class Codemodel: + """Class to get information about the Codemodel generated by the CMake file API.""" + def __init__(self, reply_dir): self.reply_dir = reply_dir self.codemodel = {} self.codemodel_targets = {} def set_codemodel(self, codemodel_file=None): + """Set the codemodel variable. + + :param codemodel_file: codemodel file to read from, defaults to None + :type codemodel_file: str, optional + """ if not codemodel_file: - for file in os.listdir(self.reply_dir): - if file.startswith("codemodel"): - codemodel_file = os.path.join(self.reply_dir, file) + for file in Path(self.reply_dir).iterdir(): + if file.name.startswith("codemodel"): + codemodel_file = Path(self.reply_dir, file) with open(codemodel_file) as f: self.codemodel = json.load(f) def get_codemodel(self): + """Get the codemodel. + + :return: JSON codemodel object. + :rtype: dict + """ if not self.codemodel: self.set_codemodel() return self.codemodel def set_codemodel_targets(self, codemodel=None): + """Set targets variable. + + :param codemodel: Codemodel dict object, defaults to None + :type codemodel: dict, optional + """ if not codemodel: codemodel = self.get_codemodel() configurations = codemodel.get("configurations", []) @@ -68,37 +81,69 @@ def set_codemodel_targets(self, codemodel=None): self.codemodel_targets.update({target["name"]: target["jsonFile"]}) def get_codemodel_targets(self): + """Get codemodel targets. + + :return: dict of targets and the corresponding file + :rtype: dict + """ if not self.codemodel_targets: self.set_codemodel_targets() return self.codemodel_targets class Target: + """Class to get information about targets found from the CMake file API.""" + def __init__(self, reply_dir, target_name): self.reply_dir = reply_dir self.target_file = Codemodel(reply_dir).get_codemodel_targets().get(target_name) self.target = {} def set_target(self, target_file=None): + """Set target variable. + + :param target_file: File to get target from, defaults to None + :type target_file: str, optional + """ if not target_file: target_file = self.target_file - with open(os.path.join(self.reply_dir, target_file)) as f: + with open(Path(self.reply_dir, target_file)) as f: self.target = json.load(f) def get_target(self): + """Get target. + + :return: JSON target + :rtype: dict + """ if not self.target: self.set_target() return self.target def get_artifacts(self): + """Get artifacts from the target. + + :return: Artifacts' paths + :rtype: list + """ return [ artifact.get("path") for artifact in self.get_target().get("artifacts", []) ] def get_commands(self): + """Get commands from the target. + + :return: Commands concerning the build system. + :rtype: list + """ return self.get_target().get("backtraceGraph", {}).get("commands") def get_compile_groups(self): + """Get compile groups from the target. + + :return: list of dictionaries of compile groups + :rtype: list + """ return [ { "fragments": [ @@ -119,21 +164,46 @@ def get_compile_groups(self): ] def get_dependencies(self): + """Get dependencies from the target. + + :return: Dependencies ids' list + :rtype: list + """ return [ dependency.get("id") for dependency in self.get_target().get("dependencies", []) ] def get_files(self): + """Get files from the target. + + :return: Files concerning the build system. + :rtype: list + """ return self.get_target().get("backtraceGraph", {}).get("files") def get_folder(self): + """Get folder from the target. + + :return: Folder of the target. + :rtype: str + """ return self.get_target().get("folder", {}).get("name") def get_id(self): + """Get id from the target. + + :return: ID of the target + :rtype: str + """ return self.get_target().get("id") def get_install(self): + """Get install info from the target. + + :return: Install info + :rtype: dict + """ install = self.get_target().get("install", {}) return { "destinations": [ @@ -144,6 +214,11 @@ def get_install(self): } def get_link(self): + """Get link info from the target. + + :return: Link info + :rtype: dict + """ link = self.get_target().get("link", {}) command_fragments = link.get("commandFragments", []) return { @@ -161,30 +236,64 @@ def get_link(self): } def get_name(self): + """Get name from the target. + + :return: Name of the target + :rtype: str + """ return self.get_target().get("name") def get_name_on_disk(self): + """Get name on disk from the target. + + :return: Name on disk of the target + :rtype: str + """ return self.get_target().get("nameOnDisk") def get_paths(self): + """Get paths from the target. + + :return: Paths of the target. + :rtype: dict + """ return self.get_target().get("paths") def get_sources(self): + """Get sources from the target. + + :return: Sources of the target. + :rtype: list + """ return [sources.get("path") for sources in self.get_target().get("sources", [])] def get_type(self): + """Get type from the target. + + :return: Type of the target. + :rtype: str + """ return self.get_target().get("type") class CMakeFileAPI: + """CMake File API front end.""" + def __init__(self, build_dir): - reply_dir = os.path.join(build_dir, ".cmake", "api", "v1", "reply") + reply_dir = Path(build_dir, ".cmake", "api", "v1", "reply") self.targets = {} for codemodel_target in Codemodel(reply_dir).get_codemodel_targets(): target = Target(reply_dir, codemodel_target) self.targets[target.get_name()] = target def get_dependencies(self, target=None): + """Get dependencies of the target(s). + + :param target: Target to get the dependencies, defaults to None + :type target: str, optional + :return: Dependencies of the target(s). + :rtype: dict + """ targets = [self.targets.get(target)] if target else self.targets.values() return { target.get_name(): list( @@ -194,6 +303,12 @@ def get_dependencies(self, target=None): } def get_sources(self, target=None): + """Get sources of the target(s). + + :param target: Target to get the dependencies, defaults to None + :type target: str, optional + :return: Sources of the target(s). + :rtype: dict + """ targets = [self.targets.get(target)] if target else self.targets.values() return {target.get_name(): target.get_sources() for target in targets} - From 9ceca8df565059f2f7b22efba9922dea986d7168 Mon Sep 17 00:00:00 2001 From: Divyanshu Madan Date: Fri, 13 Aug 2021 21:52:16 +0530 Subject: [PATCH 4/5] [cmake_frontend.py] remove Codemodel class and relevant changes --- clang_bind/cmake_frontend.py | 95 ++++++++---------------------------- 1 file changed, 21 insertions(+), 74 deletions(-) diff --git a/clang_bind/cmake_frontend.py b/clang_bind/cmake_frontend.py index f6d4879..96c97e8 100644 --- a/clang_bind/cmake_frontend.py +++ b/clang_bind/cmake_frontend.py @@ -35,79 +35,11 @@ def get_compilation_arguments(self, filename=None): } -class Codemodel: - """Class to get information about the Codemodel generated by the CMake file API.""" - - def __init__(self, reply_dir): - self.reply_dir = reply_dir - self.codemodel = {} - self.codemodel_targets = {} - - def set_codemodel(self, codemodel_file=None): - """Set the codemodel variable. - - :param codemodel_file: codemodel file to read from, defaults to None - :type codemodel_file: str, optional - """ - if not codemodel_file: - for file in Path(self.reply_dir).iterdir(): - if file.name.startswith("codemodel"): - codemodel_file = Path(self.reply_dir, file) - with open(codemodel_file) as f: - self.codemodel = json.load(f) - - def get_codemodel(self): - """Get the codemodel. - - :return: JSON codemodel object. - :rtype: dict - """ - if not self.codemodel: - self.set_codemodel() - return self.codemodel - - def set_codemodel_targets(self, codemodel=None): - """Set targets variable. - - :param codemodel: Codemodel dict object, defaults to None - :type codemodel: dict, optional - """ - if not codemodel: - codemodel = self.get_codemodel() - configurations = codemodel.get("configurations", []) - for configuration in configurations: - targets = configuration.get("targets", []) - for target in targets: - self.codemodel_targets.update({target["name"]: target["jsonFile"]}) - - def get_codemodel_targets(self): - """Get codemodel targets. - - :return: dict of targets and the corresponding file - :rtype: dict - """ - if not self.codemodel_targets: - self.set_codemodel_targets() - return self.codemodel_targets - - class Target: """Class to get information about targets found from the CMake file API.""" - def __init__(self, reply_dir, target_name): - self.reply_dir = reply_dir - self.target_file = Codemodel(reply_dir).get_codemodel_targets().get(target_name) - self.target = {} - - def set_target(self, target_file=None): - """Set target variable. - - :param target_file: File to get target from, defaults to None - :type target_file: str, optional - """ - if not target_file: - target_file = self.target_file - with open(Path(self.reply_dir, target_file)) as f: + def __init__(self, target_file): + with open(target_file) as f: self.target = json.load(f) def get_target(self): @@ -280,11 +212,26 @@ class CMakeFileAPI: """CMake File API front end.""" def __init__(self, build_dir): - reply_dir = Path(build_dir, ".cmake", "api", "v1", "reply") + self.reply_dir = Path(build_dir, ".cmake", "api", "v1", "reply") self.targets = {} - for codemodel_target in Codemodel(reply_dir).get_codemodel_targets(): - target = Target(reply_dir, codemodel_target) - self.targets[target.get_name()] = target + self._set_targets_from_codemodel() + + def _set_targets_from_codemodel(self): + """Populate targets dict by accessing values in the codemodel file.""" + + for file in Path(self.reply_dir).iterdir(): # iterate for all files + if file.name.startswith("codemodel"): # find the codemodel file + codemodel_file = Path(self.reply_dir, file) + with open(codemodel_file) as f: + codemodel = json.load(f) # load the JSON codemodel + + for configuration in codemodel.get( + "configurations", [] + ): # for each configuration + for target in configuration.get("targets", []): # for each targets + target_file = target["jsonFile"] # get the target file + target_obj = Target(Path(self.reply_dir, target_file)) + self.targets[target_obj.get_name()] = target_obj def get_dependencies(self, target=None): """Get dependencies of the target(s). From ba2e993d7db3494e6891265b491bbe75a4663597 Mon Sep 17 00:00:00 2001 From: Divyanshu Madan Date: Fri, 13 Aug 2021 21:56:10 +0530 Subject: [PATCH 5/5] [cmake_frontend.py] remove unnecessary `get_target` function --- clang_bind/cmake_frontend.py | 41 ++++++++++++------------------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/clang_bind/cmake_frontend.py b/clang_bind/cmake_frontend.py index 96c97e8..b3d5bf6 100644 --- a/clang_bind/cmake_frontend.py +++ b/clang_bind/cmake_frontend.py @@ -42,25 +42,13 @@ def __init__(self, target_file): with open(target_file) as f: self.target = json.load(f) - def get_target(self): - """Get target. - - :return: JSON target - :rtype: dict - """ - if not self.target: - self.set_target() - return self.target - def get_artifacts(self): """Get artifacts from the target. :return: Artifacts' paths :rtype: list """ - return [ - artifact.get("path") for artifact in self.get_target().get("artifacts", []) - ] + return [artifact.get("path") for artifact in self.target.get("artifacts", [])] def get_commands(self): """Get commands from the target. @@ -68,7 +56,7 @@ def get_commands(self): :return: Commands concerning the build system. :rtype: list """ - return self.get_target().get("backtraceGraph", {}).get("commands") + return self.target.get("backtraceGraph", {}).get("commands") def get_compile_groups(self): """Get compile groups from the target. @@ -92,7 +80,7 @@ def get_compile_groups(self): ], "language": compile_group.get("language"), } - for compile_group in self.get_target().get("compileGroups", []) + for compile_group in self.target.get("compileGroups", []) ] def get_dependencies(self): @@ -102,8 +90,7 @@ def get_dependencies(self): :rtype: list """ return [ - dependency.get("id") - for dependency in self.get_target().get("dependencies", []) + dependency.get("id") for dependency in self.target.get("dependencies", []) ] def get_files(self): @@ -112,7 +99,7 @@ def get_files(self): :return: Files concerning the build system. :rtype: list """ - return self.get_target().get("backtraceGraph", {}).get("files") + return self.target.get("backtraceGraph", {}).get("files") def get_folder(self): """Get folder from the target. @@ -120,7 +107,7 @@ def get_folder(self): :return: Folder of the target. :rtype: str """ - return self.get_target().get("folder", {}).get("name") + return self.target.get("folder", {}).get("name") def get_id(self): """Get id from the target. @@ -128,7 +115,7 @@ def get_id(self): :return: ID of the target :rtype: str """ - return self.get_target().get("id") + return self.target.get("id") def get_install(self): """Get install info from the target. @@ -136,7 +123,7 @@ def get_install(self): :return: Install info :rtype: dict """ - install = self.get_target().get("install", {}) + install = self.target.get("install", {}) return { "destinations": [ destination.get("path") @@ -151,7 +138,7 @@ def get_link(self): :return: Link info :rtype: dict """ - link = self.get_target().get("link", {}) + link = self.target.get("link", {}) command_fragments = link.get("commandFragments", []) return { "flags_fragments": [ @@ -173,7 +160,7 @@ def get_name(self): :return: Name of the target :rtype: str """ - return self.get_target().get("name") + return self.target.get("name") def get_name_on_disk(self): """Get name on disk from the target. @@ -181,7 +168,7 @@ def get_name_on_disk(self): :return: Name on disk of the target :rtype: str """ - return self.get_target().get("nameOnDisk") + return self.target.get("nameOnDisk") def get_paths(self): """Get paths from the target. @@ -189,7 +176,7 @@ def get_paths(self): :return: Paths of the target. :rtype: dict """ - return self.get_target().get("paths") + return self.target.get("paths") def get_sources(self): """Get sources from the target. @@ -197,7 +184,7 @@ def get_sources(self): :return: Sources of the target. :rtype: list """ - return [sources.get("path") for sources in self.get_target().get("sources", [])] + return [sources.get("path") for sources in self.target.get("sources", [])] def get_type(self): """Get type from the target. @@ -205,7 +192,7 @@ def get_type(self): :return: Type of the target. :rtype: str """ - return self.get_target().get("type") + return self.target.get("type") class CMakeFileAPI: