Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cpp_info.components first iteration #6653

Merged
merged 24 commits into from
Mar 23, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 181 additions & 5 deletions conans/model/build_info.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
from collections import OrderedDict
from collections import defaultdict, OrderedDict, Callable
from copy import deepcopy

import deprecation

Expand All @@ -12,6 +13,50 @@
DEFAULT_FRAMEWORK = "Frameworks"


class DefaultOrderedDict(OrderedDict):
# Source: http://stackoverflow.com/a/6190500/562769
def __init__(self, default_factory=None, *a, **kw):
if (default_factory is not None and
not isinstance(default_factory, Callable)):
raise TypeError('first argument must be callable')
OrderedDict.__init__(self, *a, **kw)
self.default_factory = default_factory

def __getitem__(self, key):
try:
return OrderedDict.__getitem__(self, key)
except KeyError:
return self.__missing__(key)

def __missing__(self, key):
if self.default_factory is None:
raise KeyError(key)
self[key] = value = self.default_factory()
return value

def __reduce__(self):
if self.default_factory is None:
args = tuple()
else:
args = self.default_factory,
return type(self), args, None, None, self.items()

def copy(self):
return self.__copy__()

def __copy__(self):
return type(self)(self.default_factory, self)

def __deepcopy__(self, memo):
import copy
return type(self)(self.default_factory,
copy.deepcopy(self.items()))

def __repr__(self):
return 'DefaultOrderedDict(%s, %s)' % (self.default_factory,
OrderedDict.__repr__(self))


class _CppInfo(object):
""" Object that stores all the necessary information to build in C/C++.
It is intended to be system independent, translation to
Expand Down Expand Up @@ -139,12 +184,12 @@ def __init__(self, root_folder):
self.resdirs.append(DEFAULT_RES)
self.builddirs.append(DEFAULT_BUILD)
self.frameworkdirs.append(DEFAULT_FRAMEWORK)
self.components = DefaultOrderedDict(_CppInfo)
# public_deps is needed to accumulate list of deps for cmake targets
self.public_deps = []
self.configs = {}

def __getattr__(self, config):

def _get_cpp_info():
result = _CppInfo()
result.rootpath = self.rootpath
Expand Down Expand Up @@ -179,6 +224,7 @@ def merge_lists(seq1, seq2):
self.frameworkdirs = merge_lists(self.frameworkdirs, dep_cpp_info.framework_paths)
self.libs = merge_lists(self.libs, dep_cpp_info.libs)
self.frameworks = merge_lists(self.frameworks, dep_cpp_info.frameworks)
self.build_modules = merge_lists(self.build_modules, dep_cpp_info.build_modules_paths)
self.rootpaths.append(dep_cpp_info.rootpath)

# Note these are in reverse order
Expand All @@ -187,7 +233,6 @@ def merge_lists(seq1, seq2):
self.cflags = merge_lists(dep_cpp_info.cflags, self.cflags)
self.sharedlinkflags = merge_lists(dep_cpp_info.sharedlinkflags, self.sharedlinkflags)
self.exelinkflags = merge_lists(dep_cpp_info.exelinkflags, self.exelinkflags)
self.build_modules = merge_lists(self.build_modules, dep_cpp_info.build_modules_paths)

if not self.sysroot:
self.sysroot = dep_cpp_info.sysroot
Expand Down Expand Up @@ -225,6 +270,136 @@ def framework_paths(self):
return self.frameworkdirs


class DepCppInfo(object):

def __init__(self, cpp_info):
self._cpp_info = cpp_info
self._libs = None
self._system_libs = None
self._frameworks = None
self._defines = None
self._cxxflags = None
self._cflags = None
self._sharedlinkflags = None
self._exelinkflags = None

self._include_paths = None
self._lib_paths = None
self._bin_paths = None
self._build_paths = None
self._res_paths = None
self._src_paths = None
self._framework_paths = None
self._build_module_paths = None

def __getattr__(self, item):
try:
attr = self._cpp_info.__getattribute__(item)
except AttributeError: # item is not defined, get config (CppInfo)
attr = self._cpp_info.__getattr__(item)
return attr

def _filter_paths(self, paths):
abs_paths = [os.path.join(self._cpp_info.rootpath, p)
if not os.path.isabs(p) else p for p in paths]
if self._cpp_info.filter_empty:
return [p for p in abs_paths if os.path.isdir(p)]
else:
return abs_paths

def _aggregated_values(self, item):
def merge_lists(seq1, seq2):
return [s for s in seq1 if s not in seq2] + seq2

if getattr(self, "_%s" % item) is None:
values = getattr(self._cpp_info, item)
for _, component in self._cpp_info.components.items():
values = merge_lists(values, getattr(component, item))
setattr(self, "_%s" % item, values)
return getattr(self, "_%s" % item)

def _aggregated_paths(self, item):
if getattr(self, "_%s_paths" % item) is None:
values = self._filter_paths(getattr(self._cpp_info, "%sdirs" % item))
for _, component in self._cpp_info.components.items():
values.extend(self._filter_paths(getattr(component, "%sdirs" % item)))
setattr(self, "_%s_paths" % item, values)
return getattr(self, "_%s_paths" % item)

@property
def build_modules_paths(self):
if self._build_modules_paths is None:
self._build_modules_paths = [os.path.join(self.rootpath, p) if not os.path.isabs(p)
else p for p in self.build_modules]
for _, component in self._cpp_info.components.items():
for build_module in component.build_modules:
if os.path.isabs(build_module):
self._build_modules_paths.append(build_module)
else:
self._build_modules_paths.append(self._build_modules_paths.append(build_module))
return self._build_modules_paths

@property
def include_paths(self):
return self._aggregated_paths("include")

@property
def lib_paths(self):
return self._aggregated_paths("lib")

@property
def src_paths(self):
return self._aggregated_paths("src")

@property
def bin_paths(self):
return self._aggregated_paths("bin")

@property
def build_paths(self):
return self._aggregated_paths("build")

@property
def res_paths(self):
return self._aggregated_paths("res")

@property
def framework_paths(self):
return self._aggregated_paths("framework")

@property
def libs(self):
return self._aggregated_values("libs")

@property
def system_libs(self):
return self._aggregated_values("system_libs")

@property
def frameworks(self):
return self._aggregated_values("frameworks")

@property
def defines(self):
return self._aggregated_values("defines")

@property
def cxxflags(self):
return self._aggregated_values("cxxflags")

@property
def cflags(self):
return self._aggregated_values("cflags")

@property
def sharedlinkflags(self):
return self._aggregated_values("sharedlinkflags")

@property
def exelinkflags(self):
return self._aggregated_values("exelinkflags")


class DepsCppInfo(_BaseDepsCppInfo):
""" Build Information necessary to build a given conans. It contains the
flags, directories and options if its dependencies. The conans CONANFILE
Expand All @@ -251,8 +426,9 @@ def deps(self):
def __getitem__(self, item):
return self._dependencies[item]

def update(self, dep_cpp_info, pkg_name):
assert isinstance(dep_cpp_info, CppInfo)
def update(self, cpp_info, pkg_name):
assert isinstance(cpp_info, CppInfo)
dep_cpp_info = DepCppInfo(cpp_info)
self._dependencies[pkg_name] = dep_cpp_info
super(DepsCppInfo, self).update(dep_cpp_info)
for config, cpp_info in dep_cpp_info.configs.items():
Expand Down
85 changes: 85 additions & 0 deletions conans/test/integration/package_info_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,88 @@ def build(self):
self.assertIn("System deps: %s" % merged_system_libs, client.out)
self.assertIn("intermediate system deps: %s" % intermediate_system_libs, client.out)
self.assertIn("dep system deps: %s" % dep_system_libs, client.out)

def package_info_components_test(self):
dep = textwrap.dedent("""
import os
from conans import ConanFile

class Dep(ConanFile):

def package_info(self):
self.cpp_info.components["dep1"].libs.append("libdep1")
self.cpp_info.components["dep1"].defines.append("definedep1")
self.cpp_info.components["dep2"].libs.append("libdep2")
os.mkdir(os.path.join(self.package_folder, "includedep2"))
self.cpp_info.components["dep2"].includedirs.append("includedep2")
""")
intermediate = textwrap.dedent("""
import os
from conans import ConanFile

class Intermediate(ConanFile):
requires = "dep/1.0@us/ch"

def package_info(self):
self.cpp_info.libs.append("libint")
self.cpp_info.defines.append("defint")
os.mkdir(os.path.join(self.package_folder, "includeint"))
self.cpp_info.includedirs.append("includeint")
self.cpp_info.libs.append("libint_bis")
self.cpp_info.defines.append("defint_bis")
os.mkdir(os.path.join(self.package_folder, "includeint_bis"))
self.cpp_info.includedirs.append("includeint_bis")
self.cpp_info.components["int1"].libs.append("libint1")
self.cpp_info.components["int1"].defines.append("defint1")
os.mkdir(os.path.join(self.package_folder, "includeint1"))
self.cpp_info.components["int1"].includedirs.append("includeint1")
self.cpp_info.components["int2"].libs.append("libint2")
self.cpp_info.components["int2"].defines.append("defint2")
os.mkdir(os.path.join(self.package_folder, "includeint2"))
self.cpp_info.components["int2"].includedirs.append("includeint2")
""")
consumer = textwrap.dedent("""
import os
from conans import ConanFile

class Consumer(ConanFile):
requires = "intermediate/1.0@us/ch"

def build(self):
self.output.info("deps_cpp_info.libs: %s" % self.deps_cpp_info.libs)
self.output.info("deps_cpp_info.defines: %s" % self.deps_cpp_info.defines)
self.output.info("deps_cpp_info.include_paths: %s" %
[os.path.basename(value) for value in self.deps_cpp_info.include_paths])
for dep_key, dep_value in self.deps_cpp_info.dependencies:
self.output.info("%s.libs: %s" % (dep_key, dep_value.libs))
self.output.info("%s.defines: %s" % (dep_key, dep_value.defines))
self.output.info("%s.include_paths: %s" % (dep_key,
[os.path.basename(value) for value in
dep_value.include_paths]))
""")

client = TestClient()
client.save({"conanfile_dep.py": dep,
"conanfile_intermediate.py": intermediate,
"conanfile_consumer.py": consumer})
client.run("export conanfile_dep.py dep/1.0@us/ch")
client.run("export conanfile_intermediate.py intermediate/1.0@us/ch")
client.run("create conanfile_consumer.py consumer/1.0@us/ch --build missing")

self.assertIn("deps_cpp_info.libs: ['libint', 'libint_bis', 'libint1', 'libint2', "
"'libdep1', 'libdep2']", client.out)
self.assertIn("deps_cpp_info.defines: ['definedep1', 'defint', 'defint_bis', 'defint1', "
"'defint2']", client.out)
self.assertIn("deps_cpp_info.include_paths: ['includeint', 'includeint_bis', 'includeint1', "
"'includeint2', 'includedep2']", client.out)

self.assertIn("intermediate.libs: ['libint', 'libint_bis', 'libint1', 'libint2']",
client.out)
self.assertIn("intermediate.defines: ['defint', 'defint_bis', 'defint1', 'defint2']",
client.out)
self.assertIn("intermediate.include_paths: ['includeint', 'includeint_bis', 'includeint1', "
"'includeint2']", client.out)

self.assertIn("dep.libs: ['libdep1', 'libdep2']", client.out)
self.assertIn("dep.defines: ['definedep1']", client.out)
self.assertIn("dep.include_paths: ['includedep2']", client.out)
Loading