Skip to content

Instantly share code, notes, and snippets.

@valgur
Last active August 2, 2024 08:01
Show Gist options
  • Save valgur/d95a0ea47cf5364206ce5da735aa0373 to your computer and use it in GitHub Desktop.
Save valgur/d95a0ea47cf5364206ce5da735aa0373 to your computer and use it in GitHub Desktop.
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