Last active
August 2, 2024 08:01
-
-
Save valgur/d95a0ea47cf5364206ce5da735aa0373 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class VtkConan(ConanFile): | |
def generate(self): | |
deps = CMakeDeps(self) | |
deps.generate() | |
# Store a mapping from CMake to Conan targets for later use in package_info() | |
targets_map = self._get_cmake_to_conan_targets_map(deps) | |
save(self, self._cmake_targets_map_json, json.dumps(targets_map, indent=2)) | |
@property | |
def _cmake_targets_map_json(self): | |
return os.path.join(self.generators_folder, "cmake_to_conan_targets.json") | |
@property | |
@functools.lru_cache() | |
def _cmake_targets_map(self): | |
return json.loads(load(self, self._cmake_targets_map_json)) | |
def _get_cmake_to_conan_targets_map(self, deps): | |
""" | |
Returns a dict of Conan targets corresponding to generated CMake targets. E.g.: | |
'WebP::webpdecoder': 'libwebp::webpdecoder', | |
'WebP::webpdemux': 'libwebp::webpdemux', | |
'ZLIB::ZLIB': 'zlib::zlib', | |
""" | |
def _get_targets(*args): | |
targets = [deps.get_property("cmake_target_name", *args), | |
deps.get_property("cmake_module_target_name", *args)] | |
targets += deps.get_property("cmake_target_aliases", *args) or [] | |
return list(filter(None, targets)) | |
cmake_targets_map = {} | |
for req, dependency in self.dependencies.host.items(): | |
dep_name = req.ref.name | |
for target in _get_targets(dependency): | |
cmake_targets_map[target] = f"{dep_name}::{dep_name}" | |
for component, _ in dependency.cpp_info.components.items(): | |
for target in _get_targets(dependency, component): | |
cmake_targets_map[target] = f"{dep_name}::{component}" | |
# System recipes need special handling since they rely on CMake's Find<Package> modules | |
cmake_targets_map.update({ | |
"OpenMP::OpenMP_C": "openmp::openmp", | |
"OpenMP::OpenMP_CXX": "openmp::openmp", | |
"OpenGL::EGL": "egl::egl", | |
"OpenGL::GL": "opengl::opengl", | |
"OpenGL::GLES2": "opengl::opengl", | |
"OpenGL::GLES3": "opengl::opengl", | |
"OpenGL::GLU": "glu::glu", | |
"OpenGL::GLX": "opengl::opengl", | |
"OpenGL::OpenGL": "opengl::opengl", | |
"X11::X11": "xorg::x11", | |
"X11::Xcursor": "xorg::xcursor", | |
}) | |
return cmake_targets_map | |
def _parse_cmake_targets(self, targets_file): | |
# Parse info from VTK-targets.cmake | |
# Example: | |
# add_library(VTK::IOOggTheora SHARED IMPORTED) | |
# set_target_properties(VTK::IOOggTheora PROPERTIES | |
# INTERFACE_COMPILE_DEFINITIONS "VTK_HAS_OGGTHEORA_SUPPORT" | |
# INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include/vtk" | |
# INTERFACE_LINK_LIBRARIES "VTK::CommonExecutionModel;VTK::IOMovie" | |
# ) | |
# The following properties are used by the project: | |
# compile_definitions | |
# compile_features (only sets CXX_STANDARD) | |
# include_directories (always "${_IMPORT_PREFIX}/include/vtk") | |
# link_libraries | |
# system_include_directories (still include/vtk, but silent) | |
txt = load(self, targets_file) | |
raw_targets = re.findall(r"add_library\(VTK::(\S+) (\S+) IMPORTED\)", txt) | |
targets = {name: {"is_interface": kind == "INTERFACE"} for name, kind in raw_targets if name not in ["vtkbuild"]} | |
props_raw = re.findall(r"set_target_properties\(VTK::(\S+) PROPERTIES\n((?: *.+\n)+)\)", txt) | |
for name, body in props_raw: | |
for prop, value in re.findall(r"^ *INTERFACE_(\w+)\b \"(.+)\"$", body, re.M): | |
value = value.split(";") | |
targets[name][prop.lower()] = value | |
return targets | |
def _is_matching_cmake_platform(self, platform_id): | |
if platform_id == "Darwin": | |
return is_apple_os(self) | |
if platform_id == "Linux": | |
return self.settings.os in ["Linux", "FreeBSD"] | |
if platform_id in ["WIN32", "Windows"]: | |
return self.settings.os == "Windows" | |
if platform_id == "MinGW": | |
return self.settings.os == "Windows" and self.settings.compiler == "gcc" | |
if platform_id in ["Android", "Emscripten", "SunOS"]: | |
return self.settings.os == platform_id | |
raise ConanException(f"Unexpected CMake PLATFORM_ID: '{platform_id}'") | |
def _cmake_target_to_conan_requirement(self, target): | |
if target.startswith("VTK::"): | |
# Internal target | |
return target[5:] | |
else: | |
# External target | |
req = self._cmake_targets_map.get(target, target) | |
if not req: | |
raise ConanException(f"Unexpected CMake target: '{target}'") | |
return req | |
@property | |
def _known_system_libs(self): | |
return ["m", "dl", "pthread", "rt", "log", "embind", "socket", "nsl", "wsock32", "ws2_32"] | |
def _transform_link_libraries(self, values): | |
# Converts a list of LINK_LIBRARIES values into a list of component requirements, system_libs and frameworks. | |
requires = [] | |
system_libs = [] | |
frameworks = [] | |
for v in values: | |
# strip "\$<LINK_ONLY:FontConfig::FontConfig>" etc. | |
v = re.sub(r"^\\\$<LINK_ONLY:(.*)>$", r"\1", v) | |
if not v: | |
continue | |
if "-framework " in v: | |
frameworks += re.findall(r"-framework (\S+)", v) | |
elif "PLATFORM_ID" in v: | |
# e.g. "\$<\$<PLATFORM_ID:WIN32>:wsock32>" | |
platform_id = re.search(r"PLATFORM_ID:(\w+)", v).group(1) | |
if self._is_matching_cmake_platform(platform_id): | |
lib = re.search(":(.+)>$", v).group(1) | |
system_libs.append(lib) | |
elif v.lower().replace(".lib", "") in self._known_system_libs: | |
system_libs.append(v) | |
elif v == "Threads::Threads": | |
if self.settings.os in ["Linux", "FreeBSD"]: | |
system_libs.append("pthread") | |
else: | |
requires.append(self._cmake_target_to_conan_requirement(v)) | |
return requires, system_libs, frameworks | |
@staticmethod | |
def _vtk_component_to_libname(component): | |
if component.startswith("vtk"): | |
return component | |
return f"vtk{component}" | |
def _cmake_targets_to_conan_components(self, targets_info): | |
# Fill in components based on VTK-targets.cmake | |
components = {} | |
for component_name, target_info in targets_info.items(): | |
component = {} | |
if not target_info["is_interface"]: | |
component["libs"] = [self._vtk_component_to_libname(component_name)] | |
for definition in target_info.get("compile_definitions", []): | |
if definition.startswith("-D"): | |
definition = definition[2:] | |
component["defines"].append(definition) | |
requires, system_libs, frameworks = self._transform_link_libraries(target_info.get("link_libraries", [])) | |
if component_name in requires: | |
self.output.warning(f"CMake target VTK::{component_name} has a circular dependency on itself") | |
requires = [req for req in requires if req != component_name] | |
if requires: | |
component["requires"] = requires | |
if system_libs: | |
component["system_libs"] = system_libs | |
if frameworks: | |
component["frameworks"] = frameworks | |
components[component_name] = component | |
return components | |
@property | |
def _components_json(self): | |
return os.path.join(self.package_folder, "share", "conan_components.json") | |
def package(self): | |
cmake = CMake(self) | |
cmake.install() | |
# Parse the VTK-targets.cmake file to generate Conan components | |
targets_config = os.path.join(self.package_folder, "lib", "cmake", "vtk", "VTK-targets.cmake") | |
cmake_target_props = self._parse_cmake_targets(targets_config) | |
components = self._cmake_targets_to_conan_components(cmake_target_props) | |
save(self, self._components_json, json.dumps(components, indent=2)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment