Skip to content

Commit

Permalink
Aggregate transitive components information in the same package for X…
Browse files Browse the repository at this point in the history
…codeDeps (#11507)

* wip

* wip

* fix tests

* update test

* add test

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* minor changes

* fix tests

* review
  • Loading branch information
czoido committed Jun 29, 2022
1 parent ae33d68 commit ef7f228
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 28 deletions.
88 changes: 62 additions & 26 deletions conan/tools/apple/xcodedeps.py
@@ -1,5 +1,6 @@
import os
import textwrap
from collections import OrderedDict

from jinja2 import Template

Expand Down Expand Up @@ -60,14 +61,14 @@ class XcodeDeps(object):
general_name = "conandeps.xcconfig"

_conf_xconfig = textwrap.dedent("""\
// Compiler options for {{pkg_name}}::{{pkg_name}}
// Compiler options for {{pkg_name}}::{{comp_name}}
HEADER_SEARCH_PATHS_{{pkg_name}}_{{comp_name}}{{condition}} = {{include_dirs}}
GCC_PREPROCESSOR_DEFINITIONS_{{pkg_name}}_{{comp_name}}{{condition}} = {{definitions}}
OTHER_CFLAGS_{{pkg_name}}_{{comp_name}}{{condition}} = {{c_compiler_flags}}
OTHER_CPLUSPLUSFLAGS_{{pkg_name}}_{{comp_name}}{{condition}} = {{cxx_compiler_flags}}
FRAMEWORK_SEARCH_PATHS_{{pkg_name}}_{{comp_name}}{{condition}} = {{frameworkdirs}}
// Link options for {{name}}
// Link options for {{pkg_name}}::{{comp_name}}
LIBRARY_SEARCH_PATHS_{{pkg_name}}_{{comp_name}}{{condition}} = {{lib_dirs}}
OTHER_LDFLAGS_{{pkg_name}}_{{comp_name}}{{condition}} = {{linker_flags}} {{libs}} {{system_libs}} {{frameworks}}
""")
Expand Down Expand Up @@ -121,25 +122,28 @@ def generate(self):
for generator_file, content in generator_files.items():
save(generator_file, content)

def _conf_xconfig_file(self, pkg_name, comp_name, cpp_info):
def _conf_xconfig_file(self, pkg_name, comp_name, transitive_cpp_infos):
"""
content for conan_poco_x86_release.xcconfig, containing the activation
"""
def _merged_vars(name):
merged = [var for cpp_info in transitive_cpp_infos for var in getattr(cpp_info, name)]
return list(OrderedDict.fromkeys(merged).keys())

fields = {
'pkg_name': pkg_name,
'comp_name': comp_name,
'include_dirs': " ".join('"{}"'.format(p) for p in cpp_info.includedirs),
'lib_dirs': " ".join('"{}"'.format(p) for p in cpp_info.libdirs),
'libs': " ".join("-l{}".format(lib) for lib in cpp_info.libs),
'system_libs': " ".join("-l{}".format(sys_lib) for sys_lib in cpp_info.system_libs),
'frameworksdirs': " ".join('"{}"'.format(p) for p in cpp_info.frameworkdirs),
'frameworks': " ".join("-framework {}".format(framework) for framework in cpp_info.frameworks),
'definitions': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in cpp_info.defines),
'c_compiler_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in cpp_info.cflags),
'cxx_compiler_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in cpp_info.cxxflags),
'linker_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in cpp_info.sharedlinkflags),
'exe_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in cpp_info.exelinkflags),
'include_dirs': " ".join('"{}"'.format(p) for p in _merged_vars("includedirs")),
'lib_dirs': " ".join('"{}"'.format(p) for p in _merged_vars("libdirs")),
'libs': " ".join("-l{}".format(lib) for lib in _merged_vars("libs")),
'system_libs': " ".join("-l{}".format(sys_lib) for sys_lib in _merged_vars("system_libs")),
'frameworksdirs': " ".join('"{}"'.format(p) for p in _merged_vars("frameworkdirs")),
'frameworks': " ".join("-framework {}".format(framework) for framework in _merged_vars("frameworks")),
'definitions': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in _merged_vars("defines")),
'c_compiler_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in _merged_vars("cflags")),
'cxx_compiler_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in _merged_vars("cxxflags")),
'linker_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in _merged_vars("sharedlinkflags")),
'exe_flags': " ".join('"{}"'.format(p.replace('"', '\\"')) for p in _merged_vars("exelinkflags")),
'condition': _xcconfig_conditional(self._conanfile.settings)
}

Expand Down Expand Up @@ -193,17 +197,17 @@ def _global_xconfig_content(self):
GLOBAL_XCCONFIG_TEMPLATE,
[self.general_name])

def get_content_for_component(self, pkg_name, component_name, cpp_info, reqs):
def get_content_for_component(self, pkg_name, component_name, transitive_internal, transitive_external):
result = {}

conf_name = _xcconfig_settings_filename(self._conanfile.settings)

props_name = "conan_{}_{}{}.xcconfig".format(pkg_name, component_name, conf_name)
result[props_name] = self._conf_xconfig_file(pkg_name, component_name, cpp_info)
result[props_name] = self._conf_xconfig_file(pkg_name, component_name, transitive_internal)

# The entry point for each package
file_dep_name = "conan_{}_{}.xcconfig".format(pkg_name, component_name)
dep_content = self._dep_xconfig_file(pkg_name, component_name, file_dep_name, props_name, reqs)
dep_content = self._dep_xconfig_file(pkg_name, component_name, file_dep_name, props_name, transitive_external)

result[file_dep_name] = dep_content
return result
Expand All @@ -213,28 +217,60 @@ def _content(self):

# Generate the config files for each component with name conan_pkgname_compname.xcconfig
# If a package has no components the name is conan_pkgname_pkgname.xcconfig
# Then all components are included in the conan_pkgname.xcconfig file
# All components are included in the conan_pkgname.xcconfig file
host_req = self._conanfile.dependencies.host
test_req = self._conanfile.dependencies.test
for dep in list(host_req.values()) + list(test_req.values()):
all_deps = list(host_req.values()) + list(test_req.values())
for dep in all_deps:

dep_name = _format_name(dep.ref.name)

include_components_names = []
if dep.cpp_info.has_components:
for comp_name, comp_cpp_info in dep.cpp_info.get_sorted_components().items():
component_deps = []
for req in comp_cpp_info.requires:
req_pkg, req_cmp = req.split("::") if "::" in req else (dep_name, req)
component_deps.append((req_pkg, req_cmp))

component_content = self.get_content_for_component(dep_name, comp_name, comp_cpp_info, component_deps)
sorted_components = dep.cpp_info.get_sorted_components().items()
for comp_name, comp_cpp_info in sorted_components:

# returns: ("list of cpp infos from required components in same package",
# "list of names from required components from other packages")
def _get_component_requires(component):
requires_external = [(req.split("::")[0], req.split("::")[1]) for req in
component.requires if "::" in req]
requires_internal = [dep.cpp_info.components.get(req) for req in
component.requires if "::" not in req]
return requires_internal, requires_external

# these are the transitive dependencies between components of the same package
transitive_internal = []
# these are the transitive dependencies to components from other packages
transitive_external = []

# return the internal cpp_infos and external components names
def _transitive_components(component):
requires_internal, requires_external = _get_component_requires(component)
transitive_internal.append(component)
transitive_internal.extend(requires_internal)
transitive_external.extend(requires_external)
for require in requires_internal:
_transitive_components(require)

_transitive_components(comp_cpp_info)

# remove duplicates
transitive_internal = list(OrderedDict.fromkeys(transitive_internal).keys())
transitive_external = list(OrderedDict.fromkeys(transitive_external).keys())

component_content = self.get_content_for_component(dep_name, comp_name,
transitive_internal,
transitive_external)
include_components_names.append((dep_name, comp_name))
result.update(component_content)
else:
public_deps = [(_format_name(d.ref.name),) * 2 for r, d in
dep.dependencies.direct_host.items() if r.visible]
required_components = dep.cpp_info.required_components if dep.cpp_info.required_components else public_deps
root_content = self.get_content_for_component(dep_name, dep_name, dep.cpp_info, required_components)
root_content = self.get_content_for_component(dep_name, dep_name, [dep.cpp_info],
required_components)
include_components_names.append((dep_name, dep_name))
result.update(root_content)

Expand Down
Expand Up @@ -9,8 +9,6 @@

@pytest.mark.skipif(platform.system() != "Darwin", reason="Only for MacOS")
@pytest.mark.tool_cmake
@pytest.mark.tool_xcodebuild
@pytest.mark.tool_xcodegen
def test_xcodedeps_components():
"""
tcp/1.0 is a lib without components
Expand Down
89 changes: 89 additions & 0 deletions conans/test/integration/toolchains/apple/test_xcodedeps.py
@@ -1,5 +1,6 @@
import os
import platform
import textwrap

import pytest

Expand Down Expand Up @@ -85,3 +86,91 @@ def test_generator_files():
assert '#include "conandeps.xcconfig"' in conan_config

check_contents(client, ["hello", "goodbye"], build_type, "x86_64", "macosx", "12.1")


@pytest.mark.skipif(platform.system() != "Darwin", reason="Only for MacOS")
def test_xcodedeps_aggregate_components():
client = TestClient()

conanfile_py = textwrap.dedent("""
from conan import ConanFile
class LibConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
def package_info(self):
self.cpp_info.includedirs = ["liba_include"]
""")

client.save({"conanfile.py": conanfile_py})

client.run("create . liba/1.0@")

r""""
1 a
/ \ /
2 3
\ /
4 5 6
| | /
\ / /
7
"""

conanfile_py = textwrap.dedent("""
from conan import ConanFile
class LibConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = "liba/1.0"
def package_info(self):
self.cpp_info.components["libb_comp1"].includedirs = ["libb_comp1"]
self.cpp_info.components["libb_comp1"].libdirs = ["mylibdir"]
self.cpp_info.components["libb_comp2"].includedirs = ["libb_comp2"]
self.cpp_info.components["libb_comp2"].libdirs = ["mylibdir"]
self.cpp_info.components["libb_comp2"].requires = ["libb_comp1"]
self.cpp_info.components["libb_comp3"].includedirs = ["libb_comp3"]
self.cpp_info.components["libb_comp3"].libdirs = ["mylibdir"]
self.cpp_info.components["libb_comp3"].requires = ["libb_comp1", "liba::liba"]
self.cpp_info.components["libb_comp4"].includedirs = ["libb_comp4"]
self.cpp_info.components["libb_comp4"].libdirs = ["mylibdir"]
self.cpp_info.components["libb_comp4"].requires = ["libb_comp2", "libb_comp3"]
self.cpp_info.components["libb_comp5"].includedirs = ["libb_comp5"]
self.cpp_info.components["libb_comp5"].libdirs = ["mylibdir"]
self.cpp_info.components["libb_comp6"].includedirs = ["libb_comp6"]
self.cpp_info.components["libb_comp6"].libdirs = ["mylibdir"]
self.cpp_info.components["libb_comp7"].includedirs = ["libb_comp7"]
self.cpp_info.components["libb_comp7"].libdirs = ["mylibdir"]
self.cpp_info.components["libb_comp7"].requires = ["libb_comp4", "libb_comp5", "libb_comp6"]
""")

client.save({"conanfile.py": conanfile_py})

client.run("create . libb/1.0@")

client.run("install libb/1.0@ -g XcodeDeps")

lib_entry = client.load("conan_libb.xcconfig")

for index in range(1, 8):
assert f"conan_libb_libb_comp{index}.xcconfig" in lib_entry

component7_entry = client.load("conan_libb_libb_comp7.xcconfig")
assert '#include "conan_liba_liba.xcconfig"' in component7_entry

component7_vars = client.load("conan_libb_libb_comp7_release_x86_64.xcconfig")

# all of the transitive required components and the component itself are added
for index in range(1, 8):
assert f"libb_comp{index}" in component7_vars

assert "mylibdir" in component7_vars

component4_vars = client.load("conan_libb_libb_comp4_release_x86_64.xcconfig")

# all of the transitive required components and the component itself are added
for index in range(1, 5):
assert f"libb_comp{index}" in component4_vars

for index in range(5, 8):
assert f"libb_comp{index}" not in component4_vars

# folders are aggregated
assert "mylibdir" in component4_vars

0 comments on commit ef7f228

Please sign in to comment.