Skip to content

Commit

Permalink
New cppinfo and CMakeDeps as example of usage (#8766)
Browse files Browse the repository at this point in the history
* wip

* wip

* wip

* wip

* wip

* wip

* wip

* fix

* wip

* wip

* wip

* wip

* remove unused

* wip

* wip

* wip

* wip

* wip

* minor changes

* wip

* fix local flow

* wip

* simplify code

* change update function name

* generalize name

* start build modules support

* wip

* wip

* update test

* old behaviour

* wip

* wip

* add custom content to pkg-config generator

* add tests with components

* add some unit tests

* remove translate

* revert changes

* revert changes

* revert changes

* use other properties names

* update tests

* fix filename

* not bad

* review

* fix gets

* add tests

* fix test

* add note

* test multi

* move to integration

* fix name

* revert change

* add more tests

* remove argument

* Fix win test

* review

* review

* minor refactor

* Review

* Removed patterns vars

* Fixed tests

* change name

* Remove import

* Test passing

* Adadpter CMakeDeps

* removed version access

* lines

* Fixed tests

* PEPOCHO

* print

* Mock

* traces for test

* more asserts

* traces

* try host only

* Replace path windows

* Default get name to old mechanism

* review

* Renamed var

Co-authored-by: czoido <mrgalleta@gmail.com>
Co-authored-by: James <james@conan.io>
  • Loading branch information
3 people committed Apr 20, 2021
1 parent 1a16f96 commit b3d5245
Show file tree
Hide file tree
Showing 7 changed files with 581 additions and 177 deletions.
270 changes: 136 additions & 134 deletions conan/tools/cmake/cmakedeps.py

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions conans/model/conan_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from conans.model.build_info import DepsCppInfo
from conans.model.env_info import DepsEnvInfo
from conans.model.layout import Layout
from conans.model.new_build_info import NewCppInfo
from conans.model.options import Options, OptionsValues, PackageOptions
from conans.model.requires import Requirements
from conans.model.user_info import DepsUserInfo
Expand Down Expand Up @@ -156,6 +157,8 @@ def __init__(self, output, runner, display_name="", user=None, channel=None):
self._conan_node = None # access to container Node object, to access info, context, deps...
self.virtualenv = True # Set to false to opt-out automatic usage of VirtualEnv

self._conan_new_cpp_info = None # Will be calculated lazy in the getter

@property
def context(self):
return self._conan_node.context
Expand Down Expand Up @@ -213,6 +216,12 @@ def initialize(self, settings, env, buildenv=None):
if self.description is not None and not isinstance(self.description, six.string_types):
raise ConanException("Recipe 'description' must be a string.")

@property
def new_cpp_info(self):
if not self._conan_new_cpp_info:
self._conan_new_cpp_info = NewCppInfo.from_old_cppinfo(self.cpp_info)
return self._conan_new_cpp_info

@property
def source_folder(self):
return self.layout.source_folder
Expand Down
8 changes: 8 additions & 0 deletions conans/model/conanfile_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def __eq__(self, other):
def __ne__(self, other):
return not self.__eq__(other)

@property
def package_folder(self):
return self._conanfile.package_folder

@property
def ref(self):
return self._conanfile.ref
Expand All @@ -41,6 +45,10 @@ def runenv_info(self):
def cpp_info(self):
return self._conanfile.cpp_info

@property
def new_cpp_info(self):
return self._conanfile.new_cpp_info

@property
def settings(self):
return self._conanfile.settings
Expand Down
169 changes: 169 additions & 0 deletions conans/model/new_build_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import copy
from collections import OrderedDict

from conans.errors import ConanException
from conans.model.build_info import DefaultOrderedDict

_DIRS_VAR_NAMES = ["includedirs", "srcdirs", "libdirs", "resdirs", "bindirs", "builddirs",
"frameworkdirs"]
_FIELD_VAR_NAMES = ["system_libs", "frameworks", "libs", "defines", "cflags", "cxxflags",
"sharedlinkflags", "exelinkflags"]


class _NewComponent(object):

def __init__(self):

# ###### PROPERTIES
self._generator_properties = {}

# ###### DIRECTORIES
self.includedirs = [] # Ordered list of include paths
self.srcdirs = [] # Ordered list of source paths
self.libdirs = [] # Directories to find libraries
self.resdirs = [] # Directories to find resources, data, etc
self.bindirs = [] # Directories to find executables and shared libs
self.builddirs = []
self.frameworkdirs = []

# ##### FIELDS
self.system_libs = [] # Ordered list of system libraries
self.frameworks = [] # Macos .framework
self.libs = [] # The libs to link against
self.defines = [] # preprocessor definitions
self.cflags = [] # pure C flags
self.cxxflags = [] # C++ compilation flags
self.sharedlinkflags = [] # linker flags
self.exelinkflags = [] # linker flags

self.sysroot = ""
self.requires = []

@property
def required_component_names(self):
""" Names of the required components of the same package (not scoped with ::)"""
return [r for r in self.requires if "::" not in r]

def set_property(self, property_name, value, generator=None):
self._generator_properties.setdefault(generator, {})[property_name] = value

def get_property(self, property_name, generator=None):
if generator:
try:
return self._generator_properties[generator][property_name]
except KeyError:
pass
try:
return self._generator_properties[None][property_name]
except KeyError:
pass


class NewCppInfo(object):

def __init__(self):
super(NewCppInfo, self).__init__()
self.components = DefaultOrderedDict(lambda: _NewComponent())
self.components[None] = _NewComponent() # Main package is a component with None key

def __getattr__(self, attr):
return getattr(self.components[None], attr)

def __setattr__(self, attr, value):
if attr in ["components"]:
super(NewCppInfo, self).__setattr__(attr, value)
else:
setattr(self.components[None], attr, value)

@property
def has_components(self):
return len(self.components) > 1

@staticmethod
def from_old_cppinfo(old):
ret = NewCppInfo()
for varname in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
setattr(ret, varname, getattr(old, varname))

ret._generator_properties = copy.copy(old._generator_properties)

# COMPONENTS
for cname, c in old.components.items():
for varname in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
setattr(ret.components[cname], varname, getattr(c, varname))
ret.components[cname].requires = copy.copy(c.requires)
ret.components[cname]._generator_properties = copy.copy(c._generator_properties)
return ret

def get_sorted_components(self):
"""Order the components taking into account if they depend on another component in the
same package (not scoped with ::). First less dependant
return: {component_name: component}
"""
processed = [] # Names of the components ordered
# FIXME: Cache the sort
while (len(self.components) - 1) > len(processed):
for name, c in self.components.items():
if name is None:
continue
req_processed = [n for n in c.required_component_names if n not in processed]
if not req_processed and name not in processed:
processed.append(name)

return OrderedDict([(cname, self.components[cname]) for cname in processed])

def aggregate_components(self):
"""Aggregates all the components as global values"""
if self.has_components:
components = self.get_sorted_components()
cnames = list(components.keys())
cnames.reverse() # More dependant first

# Clean global values
for n in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
setattr(self.components[None], n, [])

for name in cnames:
component = components[name]
for n in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
dest = getattr(self.components[None], n)
dest += [i for i in getattr(component, n) if i not in dest]
self.components[None].requires.extend(component.requires)
# The generator properties are not aggregated, should be defined in the root
# cpp info if needed
# FIXME: What to do about sysroot?
# Leave only the aggregated value
main_value = self.components[None]
self.components = DefaultOrderedDict(lambda: _NewComponent())
self.components[None] = main_value

def merge(self, other):
# TODO: Still not used, to be used by global generators
# If we are merging isolated cppinfo objects is because the generator is "global"
# (dirs and flags in link order in a single list) so first call
# cpp_info.aggregate_components()
if self.has_components or other.has_components:
raise ConanException("Cannot aggregate two cppinfo objects with components. "
"Do cpp_info.aggregate_components() first")
for n in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
dest = getattr(self.components[None], n)
dest += [i for i in getattr(other, n) if i not in dest]

def copy(self):
ret = NewCppInfo()
ret._generator_properties = copy.copy(self._generator_properties)
ret.components = DefaultOrderedDict(lambda: _NewComponent())
for comp_name in self.components:
ret.components[comp_name] = copy.copy(self.components[comp_name])
return ret

@property
def required_components(self):
"""Returns a list of tuples with (require, component_name) required by the package
If the require is internal (to another component), the require will be None"""
# FIXME: Cache the value
ret = []
for comp in self.components.values():
ret.extend([r.split("::") for r in comp.requires if "::" in r and r not in ret])
ret.extend([(None, r) for r in comp.requires if "::" not in r and r not in ret])
return ret
163 changes: 163 additions & 0 deletions conans/test/functional/conan_build_info/new_build_info_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import pytest

from conans.errors import ConanException
from conans.model.build_info import CppInfo
from conans.model.new_build_info import NewCppInfo, _DIRS_VAR_NAMES, _FIELD_VAR_NAMES


def test_components_order():
cppinfo = NewCppInfo()
cppinfo.components["c1"].requires = ["c4", "OtherPackage::OtherComponent2"]
cppinfo.components["c2"].requires = ["OtherPackage::OtherComponent"]
cppinfo.components["c3"].requires = ["c2"]
cppinfo.components["c4"].requires = ["c3"]
sorted_c = list(cppinfo.get_sorted_components().keys())
assert sorted_c == ["c2", "c3", "c4", "c1"]


def test_generator_properties_copy():
cppinfo = NewCppInfo()
cppinfo.set_property("foo", "foo_value", "generator1")
cppinfo.set_property("foo", "var_value", "generator2")
cppinfo.set_property("foo2", "foo2_value", "generator1")

copied = cppinfo.copy()

assert copied.get_property("foo") is None
assert copied.get_property("foo", "generator1") == "foo_value"
assert copied.get_property("foo", "generator2") == "var_value"


def test_component_aggregation():
cppinfo = NewCppInfo()

cppinfo.includedirs = ["includedir"]
cppinfo.libdirs = ["libdir"]
cppinfo.srcdirs = ["srcdir"]
cppinfo.bindirs = ["bindir"]
cppinfo.builddirs = ["builddir"]
cppinfo.frameworkdirs = ["frameworkdir"]

cppinfo.components["c2"].includedirs = ["includedir_c2"]
cppinfo.components["c2"].libdirs = ["libdir_c2"]
cppinfo.components["c2"].srcdirs = ["srcdir_c2"]
cppinfo.components["c2"].bindirs = ["bindir_c2"]
cppinfo.components["c2"].builddirs = ["builddir_c2"]
cppinfo.components["c2"].frameworkdirs = ["frameworkdir_c2"]
cppinfo.components["c2"].cxxflags = ["cxxflags_c2"]
cppinfo.components["c2"].defines = ["defines_c2"]

cppinfo.components["c1"].requires = ["c2", "LIB_A::C1"]
cppinfo.components["c1"].includedirs = ["includedir_c1"]
cppinfo.components["c1"].libdirs = ["libdir_c1"]
cppinfo.components["c1"].srcdirs = ["srcdir_c1"]
cppinfo.components["c1"].bindirs = ["bindir_c1"]
cppinfo.components["c1"].builddirs = ["builddir_c1"]
cppinfo.components["c1"].frameworkdirs = ["frameworkdir_c1"]
cppinfo.components["c1"].cxxflags = ["cxxflags_c1"]
cppinfo.components["c1"].defines = ["defines_c1"]

ret = cppinfo.copy()
ret.aggregate_components()

assert ret.includedirs == ["includedir_c1", "includedir_c2"]
assert ret.libdirs == ["libdir_c1", "libdir_c2"]
assert ret.srcdirs == ["srcdir_c1", "srcdir_c2"]
assert ret.bindirs == ["bindir_c1", "bindir_c2"]
assert ret.builddirs == ["builddir_c1", "builddir_c2"]
assert ret.frameworkdirs == ["frameworkdir_c1", "frameworkdir_c2"]
assert ret.cxxflags == ["cxxflags_c1", "cxxflags_c2"]
assert ret.defines == ["defines_c1", "defines_c2"]

# If we change the internal graph the order is different
cppinfo.components["c1"].requires = []
cppinfo.components["c2"].requires = ["c1"]

ret = cppinfo.copy()
ret.aggregate_components()

assert ret.includedirs == ["includedir_c2", "includedir_c1"]
assert ret.libdirs == ["libdir_c2", "libdir_c1"]
assert ret.srcdirs == ["srcdir_c2", "srcdir_c1"]
assert ret.bindirs == ["bindir_c2", "bindir_c1"]
assert ret.builddirs == ["builddir_c2", "builddir_c1"]
assert ret.frameworkdirs == ["frameworkdir_c2", "frameworkdir_c1"]


def norm(paths):
return [d.replace("\\", "/") for d in paths]


def test_cpp_info_merge_with_components():
"""If we try to merge a cpp info with another one and some of them have components, assert"""
cppinfo = NewCppInfo()
cppinfo.components["foo"].cxxflags = ["var"]

other = NewCppInfo()
other.components["foo2"].cxxflags = ["var2"]

with pytest.raises(ConanException) as exc:
cppinfo.merge(other)

assert "Cannot aggregate two cppinfo objects with components" in str(exc.value)


def test_cpp_info_merge_aggregating_components_first():
cppinfo = NewCppInfo()
for n in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
setattr(cppinfo.components["foo"], n, ["var_{}_1".format(n), "var_{}_2".format(n)])
setattr(cppinfo.components["foo2"], n, ["var2_{}_1".format(n), "var2_{}_2".format(n)])

cppinfo.components["foo"].requires = ["foo2"] # Deterministic order

other = NewCppInfo()
for n in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
setattr(other.components["boo"], n, ["jar_{}_1".format(n), "jar_{}_2".format(n)])
setattr(other.components["boo2"], n, ["jar2_{}_1".format(n), "jar2_{}_2".format(n)])

other.components["boo"].requires = ["boo2"] # Deterministic order

cppinfo.aggregate_components()
other.aggregate_components()

cppinfo.merge(other)

for n in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
assert getattr(cppinfo, n) == ["var_{}_1".format(n), "var_{}_2".format(n),
"var2_{}_1".format(n), "var2_{}_2".format(n),
"jar_{}_1".format(n), "jar_{}_2".format(n),
"jar2_{}_1".format(n), "jar2_{}_2".format(n)]


def test_from_old_cppinfo_components():
oldcppinfo = CppInfo("ref", "/root/")
for n in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
setattr(oldcppinfo.components["foo"], n, ["var_{}_1".format(n), "var_{}_2".format(n)])
setattr(oldcppinfo.components["foo2"], n, ["var2_{}_1".format(n), "var2_{}_2".format(n)])

# The names and filenames are not copied to the new model
oldcppinfo.components["foo"].names["Gen"] = ["MyName"]
oldcppinfo.filenames["Gen"] = ["Myfilename"]

cppinfo = NewCppInfo.from_old_cppinfo(oldcppinfo)

assert isinstance(cppinfo, NewCppInfo)

for n in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
assert getattr(cppinfo.components["foo"], n) == ["var_{}_1".format(n),
"var_{}_2".format(n)]
assert getattr(cppinfo.components["foo2"], n) == ["var2_{}_1".format(n),
"var2_{}_2".format(n)]


def test_from_old_cppinfo_no_components():
oldcppinfo = CppInfo("ref", "/root/")
for n in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
setattr(oldcppinfo, n, ["var_{}_1".format(n), "var_{}_2".format(n)])

cppinfo = NewCppInfo.from_old_cppinfo(oldcppinfo)

assert isinstance(cppinfo, NewCppInfo)

for n in _DIRS_VAR_NAMES + _FIELD_VAR_NAMES:
assert getattr(cppinfo, n) == ["var_{}_1".format(n), "var_{}_2".format(n)]

0 comments on commit b3d5245

Please sign in to comment.