Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binding flow #28

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
90 changes: 90 additions & 0 deletions clang_bind/bind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from pathlib import Path

from clang_bind.cmake_frontend import CMakeFileAPI, CompilationDatabase
from clang_bind.parse import Parse


class Bind:
"""Class to bind cpp modules.

:param source_dir: Source dir of cpp library.
:type source_dir: str
:param build_dir: CMake build dir of cpp library, containing .cmake dir or compile_commands.json
:type build_dir: str
:param output_dir: Output dir
:type output_dir: str
:param output_module_name: Module name in python
:type output_module_name: str
:param cpp_modules: List of cpp modules to bind, defaults to []: bind all.
:type cpp_modules: list, optional
:param allow_inclusions_from_other_modules: Allow inclusions from other modules, which are not specified in cpp_modules, defaults to True
:type allow_inclusions_from_other_modules: bool, optional
"""

def __init__(
self,
source_dir,
build_dir,
output_dir,
output_module_name,
cpp_modules=[],
allow_inclusions_from_other_modules=True,
):
all_cpp_modules = CMakeFileAPI(build_dir).get_library_targets()
if not cpp_modules:
cpp_modules = all_cpp_modules # bind all cpp modules

all_inclusion_sources = []
for module in all_cpp_modules: # for all cpp modules, populate the variable
sources = CMakeFileAPI(build_dir).get_sources(module) # module's sources
cpp_sources = list(
filter(lambda source: source.endswith(".cpp"), sources)
) # sources ending with .cpp
all_inclusion_sources += list(
set(sources) - set(cpp_sources)
) # other sources like .h and .hpp files

self.binding_db = {} # binding database
for module in cpp_modules:
sources = CMakeFileAPI(build_dir).get_sources(module) # module's sources
cpp_sources = list(
filter(lambda source: source.endswith(".cpp"), sources)
) # sources ending with .cpp
inclusion_sources = (
all_inclusion_sources
if allow_inclusions_from_other_modules
else list(set(sources) - set(cpp_sources))
) # other sources like .h and .hpp files

self.binding_db[module] = {
"inclusion_sources": inclusion_sources, # inclusions for the module
"files": [ # list of files' information
{
"source_path": Path(source_dir, cpp_source),
"output_path": Path(output_dir, cpp_source),
"compiler_arguments": CompilationDatabase(
build_dir
).get_compilation_arguments(cpp_source),
}
for cpp_source in cpp_sources
],
}

def _parse(self):
"""For all input files, get the parsed tree and update the db."""
for module in self.binding_db.values():
for file in module.get("files"):
# for each file, update the db with the parsed tree
file.update(
{
"parsed_tree": Parse(
file.get("source_path"),
module.get("inclusion_sources"),
file.get("compiler_arguments"),
).get_tree()
}
)

def bind(self):
"""Function to bind the input files."""
self._parse()
93 changes: 51 additions & 42 deletions clang_bind/cmake_frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,38 @@


class CompilationDatabase:
"""Class to get information from a CMake compilation database."""
"""Class to get information from a CMake compilation database.

:param build_dir: Build directory path, where compile_commands.json is present.
:type build_dir: str
"""

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
def get_compilation_arguments(self, filename):
"""Returns the compilation commands extracted from the compilation database.

: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
:param filename: Get compilation arguments of the file.
:type filename: str
:return: Compiler arguments.
:rtype: list
"""

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
}
compilation_arguments = []
for command in self.compilation_database.getCompileCommands(filename=filename):
compilation_arguments += list(command.arguments)[1:-1]
return compilation_arguments


class Target:
"""Class to get information about targets found from the CMake file API."""
"""Class to get information about targets found from the CMake file API.

:param target_file: Target file path.
:type target_file: str
"""

def __init__(self, target_file):
with open(target_file) as f:
Expand Down Expand Up @@ -196,7 +195,11 @@ def get_type(self):


class CMakeFileAPI:
"""CMake File API front end."""
"""CMake File API front end.

:param build_dir: Build directory path, where .cmake directory is present.
:type build_dir: str
"""

def __init__(self, build_dir):
self.reply_dir = Path(build_dir, ".cmake", "api", "v1", "reply")
Expand All @@ -220,29 +223,35 @@ def _set_targets_from_codemodel(self):
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).
def get_library_targets(self):
"""Get all library targets' names.

:param target: Target to get the dependencies, defaults to None
:type target: str, optional
:return: Dependencies of the target(s).
:rtype: dict
:return: Library targets.
:rtype: list
"""
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
}
library_targets_objs = filter(
lambda target: target.get_type() == "SHARED_LIBRARY", self.targets.values()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about STATIC build?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't see it in pcl, so didn't get to test how it is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PCL_SHARED_LIBS can be used to choose between shared and static library for pcl

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please provide a reference link or example?

Copy link
Member

@kunaltyagi kunaltyagi Aug 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmake -DPCL_SHARED_LIBS:BOOL=FALSE or similar

)
return list(map(lambda target: target.get_name(), library_targets_objs))

def get_dependencies(self, target):
"""Get dependencies of the target.

:param target: Target to get the dependencies of.
:type target: str
:return: Dependencies of the target.
:rtype: list
"""
return list(
map(lambda x: x.split("::")[0], self.targets.get(target).get_dependencies())
)

def get_sources(self, target=None):
def get_sources(self, target):
"""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
:param target: Target to get the sources of.
:type target: str
:return: Sources of the target.
:rtype: list
"""
targets = [self.targets.get(target)] if target else self.targets.values()
return {target.get_name(): target.get_sources() for target in targets}
return self.targets.get(target).get_sources()