Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
memsharded authored and franramirez688 committed Oct 3, 2024
1 parent 3425f26 commit 172b28d
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 104 deletions.
70 changes: 6 additions & 64 deletions conan/cps/cps.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
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


class CPSComponentType(Enum):
DYLIB = "dylib"
ARCHIVE = "archive"
INTERFACE = "interface"
EXE = "exe"
EXE = "executable"
JAR = "jar"
UNKNOWN = "unknown"

Expand All @@ -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 = []
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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):
Expand Down
107 changes: 96 additions & 11 deletions conans/model/build_info.py
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:

Expand Down
26 changes: 26 additions & 0 deletions test/integration/cps/test_cps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
29 changes: 0 additions & 29 deletions test/integration/cps/test_extended_cpp_info.py

This file was deleted.

0 comments on commit 172b28d

Please sign in to comment.