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 20 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
16 changes: 9 additions & 7 deletions conans/client/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from conans.client.tools.env import pythonpath
from conans.errors import (ConanException, ConanExceptionInUserConanfileMethod,
conanfile_exception_formatter)
from conans.model.build_info import CppInfo
from conans.model.build_info import CppInfo, DepCppInfo
from conans.model.editable_layout import EditableLayout
from conans.model.env_info import EnvInfo
from conans.model.graph_info import GraphInfo
Expand Down Expand Up @@ -511,19 +511,19 @@ def _propagate_info(node, using_build_profile):

if not using_build_profile: # Do not touch anything
conan_file.deps_user_info[n.ref.name] = n.conanfile.user_info
conan_file.deps_cpp_info.update(n.conanfile.cpp_info, n.ref.name)
conan_file.deps_cpp_info.update(n.conanfile._conan_dep_cpp_info, n.ref.name)
conan_file.deps_env_info.update(n.conanfile.env_info, n.ref.name)
else:
if n in transitive or n in br_host:
conan_file.deps_cpp_info.update(n.conanfile.cpp_info, n.ref.name)
conan_file.deps_cpp_info.update(n.conanfile._conan_dep_cpp_info, n.ref.name)
else:
env_info = EnvInfo()
env_info._values_ = n.conanfile.env_info._values_.copy()
# Add cpp_info.bin_paths/lib_paths to env_info (it is needed for runtime)
env_info.DYLD_LIBRARY_PATH.extend(n.conanfile.cpp_info.lib_paths)
env_info.DYLD_LIBRARY_PATH.extend(n.conanfile.cpp_info.framework_paths)
env_info.LD_LIBRARY_PATH.extend(n.conanfile.cpp_info.lib_paths)
env_info.PATH.extend(n.conanfile.cpp_info.bin_paths)
env_info.DYLD_LIBRARY_PATH.extend(n.conanfile._conan_dep_cpp_info.lib_paths)
env_info.DYLD_LIBRARY_PATH.extend(n.conanfile._conan_dep_cpp_info.framework_paths)
env_info.LD_LIBRARY_PATH.extend(n.conanfile._conan_dep_cpp_info.lib_paths)
env_info.PATH.extend(n.conanfile._conan_dep_cpp_info.bin_paths)
conan_file.deps_env_info.update(env_info, n.ref.name)

# Update the info but filtering the package values that not apply to the subtree
Expand Down Expand Up @@ -557,3 +557,5 @@ def _call_package_info(self, conanfile, package_folder, ref):
conanfile.package_info()
self._hook_manager.execute("post_package_info", conanfile=conanfile,
reference=ref)
if conanfile._conan_dep_cpp_info is None:
conanfile._conan_dep_cpp_info = DepCppInfo(conanfile.cpp_info)
179 changes: 172 additions & 7 deletions conans/model/build_info.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from collections import OrderedDict

from conans.errors import ConanException
from conans.util.conan_v2_mode import conan_v2_behavior

DEFAULT_INCLUDE = "include"
Expand All @@ -12,6 +13,18 @@
DEFAULT_FRAMEWORK = "Frameworks"


class DefaultOrderedDict(OrderedDict):

def __init__(self, factory):
self.factory = factory
super(DefaultOrderedDict, self).__init__()

def __getitem__(self, key):
if key not in self.keys():
super(DefaultOrderedDict, self).__setitem__(key, self.factory())
return super(DefaultOrderedDict, self).__getitem__(key)


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 @@ -124,6 +137,19 @@ def set_cppflags(self, value):
cppflags = property(get_cppflags, set_cppflags)


class Component(_CppInfo):

def __init__(self, rootpath):
super(Component, self).__init__()
self.rootpath = rootpath
self.includedirs.append(DEFAULT_INCLUDE)
self.libdirs.append(DEFAULT_LIB)
self.bindirs.append(DEFAULT_BIN)
self.resdirs.append(DEFAULT_RES)
self.builddirs.append(DEFAULT_BUILD)
self.frameworkdirs.append(DEFAULT_FRAMEWORK)


class CppInfo(_CppInfo):
""" Build Information declared to be used by the CONSUMERS of a
conans. That means that consumers must use this flags and configs i order
Expand All @@ -139,12 +165,12 @@ def __init__(self, root_folder):
self.resdirs.append(DEFAULT_RES)
self.builddirs.append(DEFAULT_BUILD)
self.frameworkdirs.append(DEFAULT_FRAMEWORK)
self.components = DefaultOrderedDict(lambda: Component(self.rootpath))
# 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 All @@ -159,6 +185,24 @@ def _get_cpp_info():

return self.configs.setdefault(config, _get_cpp_info())

def raise_if_components_and_non_default_values(self):
if (self.includedirs != [DEFAULT_INCLUDE] or
self.libdirs != [DEFAULT_LIB] or
self.bindirs != [DEFAULT_BIN] or
self.resdirs != [DEFAULT_RES] or
self.frameworkdirs != [DEFAULT_FRAMEWORK] or
self.libs or
self.system_libs or
self.frameworks or
self.defines or
self.cflags or
self.cxxflags or
self.sharedlinkflags or
self.exelinkflags or
self.build_modules) and self.components:
raise ConanException("self.cpp_info.components cannot be used with self.cpp_info global "
"values at the same time")


class _BaseDepsCppInfo(_CppInfo):
def __init__(self):
Expand All @@ -179,6 +223,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 +232,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 +269,127 @@ def framework_paths(self):
return self.frameworkdirs


class DepCppInfo(object):

def __init__(self, cpp_info):
cpp_info.raise_if_components_and_non_default_values()
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

@staticmethod
def _merge_lists(seq1, seq2):
return seq1 + [s for s in seq2 if s not in seq1]

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

def _aggregated_paths(self, item):
paths = getattr(self, "_%s_paths" % item)
if paths is not None:
return paths
paths = getattr(self._cpp_info, "%s_paths" % item)
if self._cpp_info.components:
for _, component in self._cpp_info.components.items():
paths = self._merge_lists(paths, getattr(component, "%s_paths" % item))
setattr(self, "_%s_paths" % item, paths)
return paths

@property
def build_modules_paths(self):
return self._aggregated_paths("build_modules")

@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,9 +416,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)
self._dependencies[pkg_name] = dep_cpp_info
super(DepsCppInfo, self).update(dep_cpp_info)
for config, cpp_info in dep_cpp_info.configs.items():
def update(self, cpp_info, pkg_name):
assert isinstance(cpp_info, (CppInfo, DepCppInfo))
self._dependencies[pkg_name] = cpp_info
super(DepsCppInfo, self).update(cpp_info)
for config, cpp_info in cpp_info.configs.items():
self.configs.setdefault(config, _BaseDepsCppInfo()).update(cpp_info)
1 change: 1 addition & 0 deletions conans/model/conan_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ def initialize(self, settings, env):

# needed variables to pack the project
self.cpp_info = None # Will be initialized at processing time
self._conan_dep_cpp_info = None # Will be initialized at processing time
self.deps_cpp_info = DepsCppInfo()

# environment variables declared in the package_info
Expand Down
95 changes: 95 additions & 0 deletions conans/test/integration/package_info_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import textwrap
import unittest

from conans.errors import ConanException
from conans.paths import CONANFILE, CONANFILE_TXT
from conans.test.utils.tools import TestClient

Expand Down Expand Up @@ -132,3 +133,97 @@ 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, "include"))
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.components["int1"].libs.append("libint1")
self.cpp_info.components["int1"].defines.append("defint1")
os.mkdir(os.path.join(self.package_folder, "include"))
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: ['libint1', 'libint2', 'libdep1', 'libdep2']", client.out)
self.assertIn("deps_cpp_info.defines: ['definedep1', 'defint1', 'defint2']", client.out)
self.assertIn("deps_cpp_info.include_paths: ['include', 'includeint1', 'includeint2', "
"'include', 'includedep2']", client.out)

self.assertIn("intermediate.libs: ['libint1', 'libint2']",
client.out)
self.assertIn("intermediate.defines: ['defint1', 'defint2']",
client.out)
self.assertIn("intermediate.include_paths: ['include', 'includeint1', 'includeint2']",
client.out)

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

def package_info_raise_components_test(self):
conanfile = textwrap.dedent("""
from conans import ConanFile

class Intermediate(ConanFile):

def package_info(self):
self.cpp_info.defines.append("defint")
self.cpp_info.components["int1"].libs.append("libint1")
""")
client = TestClient()
client.save({"conanfile.py": conanfile})
client.run("export conanfile.py dep/1.0@us/ch")
client.run("create conanfile.py dep/1.0@us/ch", assert_error=True)
self.assertIn("ERROR: self.cpp_info.components cannot be used with self.cpp_info global "
"values at the same time", client.out)
Loading