From 172b28da3bb5562f5e4660f9e687fdf9418d2215 Mon Sep 17 00:00:00 2001 From: memsharded Date: Thu, 3 Oct 2024 00:13:10 +0200 Subject: [PATCH] wip --- conan/cps/cps.py | 70 +----------- conans/model/build_info.py | 107 ++++++++++++++++-- test/integration/cps/test_cps.py | 26 +++++ .../integration/cps/test_extended_cpp_info.py | 29 ----- 4 files changed, 128 insertions(+), 104 deletions(-) delete mode 100644 test/integration/cps/test_extended_cpp_info.py diff --git a/conan/cps/cps.py b/conan/cps/cps.py index ee4c80be1e3..67007d8570a 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -1,11 +1,8 @@ -import glob import json import os from enum import Enum -from conan.api.output import ConanOutput from conans.model.build_info import CppInfo -from conans.model.pkg_type import PackageType from conans.util.files import save, load @@ -13,7 +10,7 @@ class CPSComponentType(Enum): DYLIB = "dylib" ARCHIVE = "archive" INTERFACE = "interface" - EXE = "exe" + EXE = "executable" JAR = "jar" UNKNOWN = "unknown" @@ -35,60 +32,6 @@ def from_conan(pkg_type): return CPSComponentType(_package_type_map.get(str(pkg_type), "unknown")) -def deduce_full_lib_info(libname, full_lib, cpp_info, pkg_type): - if full_lib.get("type") is not None: - assert "location" in full_lib, f"If 'type' is specified in library {libname}, 'location' too" - return - - # Recipe didn't specify things, need to auto deduce - libdirs = [x.replace("\\", "/") for x in cpp_info.libdirs] - bindirs = [x.replace("\\", "/") for x in cpp_info.bindirs] - - static_patterns = [f"{libname}.lib", f"{libname}.a", f"lib{libname}.a"] - shared_patterns = [f"lib{libname}.so", f"lib{libname}.so.*", f"lib{libname}.dylib", - f"lib{libname}.*dylib"] - dll_patterns = [f"{libname}.dll"] - - def _find_matching(patterns, dirs): - matches = set() - for pattern in patterns: - for d in dirs: - matches.update(glob.glob(f"{d}/{pattern}")) - if len(matches) == 1: - return next(iter(matches)) - - static_location = _find_matching(static_patterns, libdirs) - shared_location = _find_matching(shared_patterns, libdirs) - dll_location = _find_matching(dll_patterns, bindirs) - if static_location: - if shared_location: - ConanOutput().warning(f"Lib {libname} has both static {static_location} and " - f"shared {shared_location} in the same package") - if pkg_type is PackageType.STATIC: - full_lib["location"] = static_location - full_lib["type"] = PackageType.STATIC - else: - full_lib["location"] = shared_location - full_lib["type"] = PackageType.SHARED - elif dll_location: - full_lib["location"] = dll_location - full_lib["link_location"] = static_location - full_lib["type"] = PackageType.SHARED - else: - full_lib["location"] = static_location - full_lib["type"] = PackageType.STATIC - elif shared_location: - full_lib["location"] = shared_location - full_lib["type"] = PackageType.SHARED - elif dll_location: - # Only .dll but no link library - full_lib["location"] = dll_location - full_lib["type"] = PackageType.SHARED - if full_lib.get("type") != pkg_type: - ConanOutput().warning(f"Lib {libname} deduced as '{full_lib.get('type')}, " - f"but 'package_type={pkg_type}'") - - class CPSComponent: def __init__(self, component_type=None): self.includes = [] @@ -142,12 +85,10 @@ def from_cpp_info(cpp_info, pkg_type, libname=None): cps_comp.type = CPSComponentType.INTERFACE return cps_comp - libname = libname or next(iter(cpp_info.full_libs)) - full_lib = cpp_info.full_libs[libname] - deduce_full_lib_info(libname, full_lib, cpp_info, pkg_type) - cps_comp.type = CPSComponentType.from_conan(full_lib.get("type")) - cps_comp.location = full_lib.get("location") - cps_comp.link_location = full_lib.get("link_location") + cpp_info.deduce_cps(pkg_type) + cps_comp.type = CPSComponentType.from_conan(cpp_info.type) + cps_comp.location = cpp_info.location + cps_comp.link_location = cpp_info.link_location cps_comp.link_libraries = cpp_info.system_libs required = cpp_info.requires cps_comp.requires = [f":{c}" if "::" not in c else c.replace("::", ":") for c in required] @@ -309,6 +250,7 @@ def load(file): path, name = os.path.split(file) basename, ext = os.path.splitext(name) + # Find, load and merge configuration specific files for conf in "release", "debug": full_conf = os.path.join(path, f"{basename}@{conf}{ext}") if os.path.exists(full_conf): diff --git a/conans/model/build_info.py b/conans/model/build_info.py index b392c7f5547..8111226e900 100644 --- a/conans/model/build_info.py +++ b/conans/model/build_info.py @@ -1,10 +1,12 @@ import copy +import glob import json import os from collections import OrderedDict, defaultdict from conan.api.output import ConanOutput from conans.errors import ConanException +from conans.model.pkg_type import PackageType from conans.util.files import load, save _DIRS_VAR_NAMES = ["_includedirs", "_srcdirs", "_libdirs", "_resdirs", "_bindirs", "_builddirs", @@ -91,6 +93,11 @@ def __init__(self, set_defaults=False): self.libdirs = ["lib"] self.bindirs = ["bin"] + # CPS + self._type = None + self._location = None + self._link_location = None + def serialize(self): return { "includedirs": self._includedirs, @@ -111,7 +118,10 @@ def serialize(self): "objects": self._objects, "sysroot": self._sysroot, "requires": self._requires, - "properties": self._properties + "properties": self._properties, + "type": self._type, + "location": self._location, + "link_location": self._link_location } @staticmethod @@ -242,22 +252,36 @@ def frameworks(self, value): def libs(self): if self._libs is None: self._libs = [] - if isinstance(self._libs, dict): - return [self._libs.keys()] # Return a list to not break any interface - return self._libs - - @property - def full_libs(self): - if self._libs is None: - self._libs = [] - if isinstance(self._libs, list): - return {k: {} for k in self._libs} return self._libs @libs.setter def libs(self, value): self._libs = value + @property + def type(self): + return self._type + + @type.setter + def type(self, value): + self._type = value + + @property + def location(self): + return self._location + + @location.setter + def location(self, value): + self._location = value + + @property + def link_location(self): + return self._link_location + + @link_location.setter + def link_location(self, value): + self._link_location = value + @property def defines(self): if self._defines is None: @@ -429,6 +453,67 @@ def relocate(el): def parsed_requires(self): return [r.split("::", 1) if "::" in r else (None, r) for r in self.requires] + def deduce_cps(self, pkg_type): + if self._location or self._link_location: + if self._type is None or self._type is PackageType.HEADER: + raise ConanException("Incorrect cpp_info defining location without type or header") + return + if self._type not in [None, PackageType.SHARED, PackageType.STATIC, PackageType.APP]: + return + + # Recipe didn't specify things, need to auto deduce + libdirs = [x.replace("\\", "/") for x in self.libdirs] + bindirs = [x.replace("\\", "/") for x in self.bindirs] + + if len(self.libs) != 1: + raise ConanException("More than 1 library defined in cpp_info.libs, cannot deduce CPS") + + # TODO: Do a better handling of pre-defined type + libname = self.libs[0] + static_patterns = [f"{libname}.lib", f"{libname}.a", f"lib{libname}.a"] + shared_patterns = [f"lib{libname}.so", f"lib{libname}.so.*", f"lib{libname}.dylib", + f"lib{libname}.*dylib"] + dll_patterns = [f"{libname}.dll"] + + def _find_matching(patterns, dirs): + matches = set() + for pattern in patterns: + for d in dirs: + matches.update(glob.glob(f"{d}/{pattern}")) + if len(matches) == 1: + return next(iter(matches)) + + static_location = _find_matching(static_patterns, libdirs) + shared_location = _find_matching(shared_patterns, libdirs) + dll_location = _find_matching(dll_patterns, bindirs) + if static_location: + if shared_location: + ConanOutput().warning(f"Lib {libname} has both static {static_location} and " + f"shared {shared_location} in the same package") + if pkg_type is PackageType.STATIC: + self._location = static_location + self._type = PackageType.STATIC + else: + self._location = shared_location + self._type = PackageType.SHARED + elif dll_location: + self._location = dll_location + self._link_location = static_location + self._type = PackageType.SHARED + else: + self._location = static_location + self._type = PackageType.STATIC + elif shared_location: + self._location = shared_location + self._type = PackageType.SHARED + elif dll_location: + # Only .dll but no link library + self._location = dll_location + self._type = PackageType.SHARED + if self._type != pkg_type: + ConanOutput().warning(f"Lib {libname} deduced as '{self._type}, " + f"but 'package_type={pkg_type}'") + class CppInfo: diff --git a/test/integration/cps/test_cps.py b/test/integration/cps/test_cps.py index 5faf4d2dc75..e8252739864 100644 --- a/test/integration/cps/test_cps.py +++ b/test/integration/cps/test_cps.py @@ -154,3 +154,29 @@ def test_cps_merge(): cps = CPS.load(os.path.join(folder, "mypkg.cps")) json_cps = cps.serialize() print(json.dumps(json_cps, indent=2)) + + +def test_extended_cpp_info(): + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + def package_info(self): + self.cpp_info.libs = ["mylib"] + self.cpp_info.location = "my_custom_location" + self.cpp_info.type = "static-library" + """) + c.save({"conanfile.py": conanfile}) + c.run("create .") + + settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64" + c.run(f"install --requires=pkg/0.1 {settings} -g CPSDeps") + pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/pkg.cps")) + assert pkg["name"] == "pkg" + assert pkg["version"] == "0.1" + assert pkg["default_components"] == ["pkg"] + pkg_comp = pkg["components"]["pkg"] + assert pkg_comp["type"] == "archive" + assert pkg_comp["location"] == "my_custom_location" diff --git a/test/integration/cps/test_extended_cpp_info.py b/test/integration/cps/test_extended_cpp_info.py deleted file mode 100644 index 4308fd5b6e1..00000000000 --- a/test/integration/cps/test_extended_cpp_info.py +++ /dev/null @@ -1,29 +0,0 @@ -import json -import textwrap - -from conan.test.utils.tools import TestClient - - -def test_extended_cpp_info(): - c = TestClient() - conanfile = textwrap.dedent(""" - from conan import ConanFile - class Pkg(ConanFile): - name = "pkg" - version = "0.1" - def package_info(self): - self.cpp_info.libs = {"mylib": {"location": "my_custom_location", - "type": "static-library"}} - """) - c.save({"conanfile.py": conanfile}) - c.run("create .") - - settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64" - c.run(f"install --requires=pkg/0.1 {settings} -g CPSDeps") - pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/pkg.cps")) - assert pkg["name"] == "pkg" - assert pkg["version"] == "0.1" - assert pkg["default_components"] == ["pkg"] - pkg_comp = pkg["components"]["pkg"] - assert pkg_comp["type"] == "archive" - assert pkg_comp["location"] == "my_custom_location"