diff --git a/conan/tools/gnu/__init__.py b/conan/tools/gnu/__init__.py index 287a9e7e207..6c9add03f60 100644 --- a/conan/tools/gnu/__init__.py +++ b/conan/tools/gnu/__init__.py @@ -1,4 +1,4 @@ from conan.tools.gnu.autotoolstoolchain import AutotoolsToolchain from conan.tools.gnu.autotoolsdeps import AutotoolsDeps from conan.tools.gnu.autotools import Autotools -from conan.tools.gnu.pkgconfigdeps import PkgConfigDeps +from conan.tools.gnu.pkgconfigdeps.pkgconfigdeps import PkgConfigDeps diff --git a/conan/tools/gnu/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps.py deleted file mode 100644 index 4d823d50081..00000000000 --- a/conan/tools/gnu/pkgconfigdeps.py +++ /dev/null @@ -1,258 +0,0 @@ -""" - PkgConfigDeps Conan generator - - - PC FILE EXAMPLE: - - prefix=/usr - exec_prefix=${prefix} - libdir=${exec_prefix}/lib - includedir=${prefix}/include - - Name: my-project - Description: Some brief but informative description - Version: 1.2.3 - Libs: -L${libdir} -lmy-project-1 -linkerflag -Wl,-rpath=${libdir} - Cflags: -I${includedir}/my-project-1 - Requires: glib-2.0 >= 2.40 gio-2.0 >= 2.42 nice >= 0.1.6 - Requires.private: gthread-2.0 >= 2.40 -""" -import os -import textwrap - -from jinja2 import Template, StrictUndefined - -from conan.tools.gnu.gnudeps_flags import GnuDepsFlags -from conans.errors import ConanException -from conans.util.files import save - - -def get_package_name(req): - ret = req.cpp_info.get_property("pkg_config_name", "PkgConfigDeps") - return ret or req.ref.name - - -def get_component_name(req, comp_name): - if comp_name not in req.cpp_info.components: - # foo::foo might be referencing the root cppinfo - if req.ref.name == comp_name: - return get_package_name(req) - raise ConanException("Component '{name}::{cname}' not found in '{name}' " - "package requirement".format(name=req.ref.name, cname=comp_name)) - ret = req.cpp_info.components[comp_name].get_property("pkg_config_name", "PkgConfigDeps") - return ret or comp_name - - -class PkgConfigDeps(object): - - def __init__(self, conanfile): - self._conanfile = conanfile - - @staticmethod - def _get_pc_name(pkg_name, comp_name): - """Build a composed name for all the components and its package root name""" - return "%s-%s" % (pkg_name, comp_name) - - def _get_component_requires_names(self, dep_name, cpp_info): - """ - Get all the pkg-config valid names from the requires ones given a dependency and - a CppInfo object. - - Note: CppInfo could be coming from one Component object instead of the dependency - """ - ret = [] - for req in cpp_info.requires: - pkg_name, comp_name = req.split("::") if "::" in req else (dep_name, req) - # FIXME: it could allow defining requires to not direct dependencies - req_conanfile = self._conanfile.dependencies.host[pkg_name] - comp_alias_name = get_component_name(req_conanfile, comp_name) - ret.append(self._get_pc_name(pkg_name, comp_alias_name)) - return ret - - def _get_requires_names(self, dep): - """ - Get all the dependency's requirements (public dependencies and components) - """ - # FIXME: this str(dep.ref.name) is only needed for python2.7 (unicode values). - # Remove it for Conan 2.0 - dep_name = str(dep.ref.name) - # At first, let's check if we have defined some component requires, e.g., "pkg::cmp1" - requires = self._get_component_requires_names(dep_name, dep.cpp_info) - # If we have found some component requires it would be enough - if not requires: - # If no requires were found, let's try to get all the direct dependencies, - # e.g., requires = "other_pkg/1.0" - requires = [get_package_name(req) for req in dep.dependencies.direct_host.values()] - return requires - - def get_components_files_and_content(self, dep): - """Get all the *.pc files content for the dependency and each of its components""" - pc_files = {} - pkg_name = get_package_name(dep) - comp_names = [] - pc_gen = _PCFilesTemplate(self._conanfile, dep) - # Loop through all the package's components - for comp_name, comp_cpp_info in dep.cpp_info.get_sorted_components().items(): - comp_name = get_component_name(dep, comp_name) - comp_names.append(comp_name) - # FIXME: this str(dep.ref.name) is only needed for python2.7 (unicode values). - # Remove it for Conan 2.0 - comp_requires_names = self._get_component_requires_names(str(dep.ref.name), comp_cpp_info) - # Get the *.pc file content for each component - pkg_comp_name = self._get_pc_name(pkg_name, comp_name) - pc_files.update(pc_gen.get_pc_filename_and_content(comp_requires_names, - name=pkg_comp_name, - cpp_info=comp_cpp_info)) - # Let's create a *.pc file for the main package - pkg_requires = [self._get_pc_name(pkg_name, i) for i in comp_names] - pc_files.update(pc_gen.get_wrapper_pc_filename_and_content(pkg_requires)) - return pc_files - - @property - def content(self): - """Get all the *.pc files content""" - pc_files = {} - host_req = self._conanfile.dependencies.host - for require, dep in host_req.items(): - if dep.cpp_info.has_components: - pc_files.update(self.get_components_files_and_content(dep)) - else: # Content for package without components - pc_gen = _PCFilesTemplate(self._conanfile, dep) - requires = self._get_requires_names(dep) - pc_files.update(pc_gen.get_pc_filename_and_content(requires)) - return pc_files - - def generate(self): - """Save all the *.pc files""" - # Current directory is the generators_folder - generator_files = self.content - for generator_file, content in generator_files.items(): - save(generator_file, content) - - -class _PCFilesTemplate(object): - - def __init__(self, conanfile, dep): - self._conanfile = conanfile - self._dep = dep - self._name = get_package_name(dep) - - pc_file_template = textwrap.dedent("""\ - - {%- macro get_libs(libdirs, cpp_info, gnudeps_flags) -%} - {%- for _ in libdirs -%} - {{ '-L"${libdir%s}"' % loop.index + " " }} - {%- endfor -%} - {%- for sys_lib in (cpp_info.libs + cpp_info.system_libs) -%} - {{ "-l%s" % sys_lib + " " }} - {%- endfor -%} - {%- for shared_flag in (cpp_info.sharedlinkflags + cpp_info.exelinkflags) -%} - {{ shared_flag + " " }} - {%- endfor -%} - {%- for _ in libdirs -%} - {%- set flag = gnudeps_flags._rpath_flags(["${libdir%s}" % loop.index]) -%} - {%- if flag|length -%} - {{ flag[0] + " " }} - {%- endif -%} - {%- endfor -%} - {%- for framework in (gnudeps_flags.frameworks + gnudeps_flags.framework_paths) -%} - {{ framework + " " }} - {%- endfor -%} - {%- endmacro -%} - - {%- macro get_cflags(includedirs, cpp_info) -%} - {%- for _ in includedirs -%} - {{ '-I"${includedir%s}"' % loop.index + " " }} - {%- endfor -%} - {%- for cxxflags in cpp_info.cxxflags -%} - {{ cxxflags + " " }} - {%- endfor -%} - {%- for cflags in cpp_info.cflags-%} - {{ cflags + " " }} - {%- endfor -%} - {%- for define in cpp_info.defines-%} - {{ "-D%s" % define + " " }} - {%- endfor -%} - {%- endmacro -%} - - prefix={{ prefix_path }} - {% for path in libdirs %} - {{ "libdir{}={}".format(loop.index, path) }} - {% endfor %} - {% for path in includedirs %} - {{ "includedir%d=%s" % (loop.index, path) }} - {% endfor %} - {% if pkg_config_custom_content %} - # Custom PC content - {{ pkg_config_custom_content }} - {% endif %} - - Name: {{ name }} - Description: {{ description }} - Version: {{ version }} - Libs: {{ get_libs(libdirs, cpp_info, gnudeps_flags) }} - Cflags: {{ get_cflags(includedirs, cpp_info) }} - {% if requires|length %} - Requires: {{ requires|join(' ') }} - {% endif %} - """) - - wrapper_pc_file_template = textwrap.dedent("""\ - Name: {{ name }} - Description: {{ description }} - Version: {{ version }} - {% if requires|length %} - Requires: {{ requires|join(' ') }} - {% endif %} - """) - - def get_pc_filename_and_content(self, requires, name=None, cpp_info=None): - - def get_formatted_dirs(folders, prefix_path_): - ret = [] - for i, directory in enumerate(folders): - directory = os.path.normpath(directory).replace("\\", "/") - prefix = "" - if not os.path.isabs(directory): - prefix = "${prefix}/" - elif directory.startswith(prefix_path_): - prefix = "${prefix}/" - directory = os.path.relpath(directory, prefix_path_).replace("\\", "/") - ret.append("%s%s" % (prefix, directory)) - return ret - - dep_name = name or self._name - package_folder = self._dep.package_folder - version = self._dep.ref.version - cpp_info = cpp_info or self._dep.cpp_info - - prefix_path = package_folder.replace("\\", "/") - libdirs = get_formatted_dirs(cpp_info.libdirs, prefix_path) - includedirs = get_formatted_dirs(cpp_info.includedirs, prefix_path) - - context = { - "prefix_path": prefix_path, - "libdirs": libdirs, - "includedirs": includedirs, - "pkg_config_custom_content": cpp_info.get_property("pkg_config_custom_content", "PkgConfigDeps"), - "name": dep_name, - "description": self._conanfile.description or "Conan package: %s" % dep_name, - "version": version, - "requires": requires, - "cpp_info": cpp_info, - "gnudeps_flags": GnuDepsFlags(self._conanfile, cpp_info) - } - template = Template(self.pc_file_template, trim_blocks=True, lstrip_blocks=True, - undefined=StrictUndefined) - return {dep_name + ".pc": template.render(context)} - - def get_wrapper_pc_filename_and_content(self, requires, name=None): - dep_name = name or self._name - context = { - "name": dep_name, - "description": self._conanfile.description or "Conan package: %s" % dep_name, - "version": self._dep.ref.version, - "requires": requires - } - template = Template(self.wrapper_pc_file_template, trim_blocks=True, lstrip_blocks=True, - undefined=StrictUndefined) - return {dep_name + ".pc": template.render(context)} diff --git a/conan/tools/gnu/pkgconfigdeps/__init__.py b/conan/tools/gnu/pkgconfigdeps/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/conan/tools/gnu/pkgconfigdeps/pc_files_creator.py b/conan/tools/gnu/pkgconfigdeps/pc_files_creator.py new file mode 100644 index 00000000000..a3d7f63bece --- /dev/null +++ b/conan/tools/gnu/pkgconfigdeps/pc_files_creator.py @@ -0,0 +1,89 @@ +from conan.tools.gnu.pkgconfigdeps.pc_files_templates import get_alias_pc_filename_and_content, \ + get_pc_filename_and_content +from conan.tools.gnu.pkgconfigdeps.pc_info_loader import get_package_with_components_info, \ + get_single_package_info, get_aliases_info + + +def _get_aliases_pc_files_and_content(dep, aliases_info): + """ + Get all the PC files and content for the aliases defined previously + for package and components names + """ + pc_files = {} + for name, aliases in aliases_info.items(): + for alias in aliases: + pc_alias_file = get_alias_pc_filename_and_content( + dep, + alias, + [name], # require is the own name which is being used the aliases for. + description="Alias %s for %s" % (alias, name), + ) + pc_files.update(pc_alias_file) + return pc_files + + +def _get_components_pc_files_and_content(conanfile, dep, components_info): + """ + Get the PC files and content for dependency's components + """ + pc_files = {} + # Loop through all the package's components + for pc_info_component in components_info: + # Get the *.pc file content for each component + description = "Conan component: %s" % pc_info_component.name + pc_file = get_pc_filename_and_content( + conanfile, + dep, + pc_info_component.name, + pc_info_component.requires, + description, + cpp_info=pc_info_component.cpp_info) + pc_files.update(pc_file) + return pc_files + + +def _get_package_with_components_pc_files_and_content(conanfile, dep, package_info, components_info): + """ + Get the PC files and content for dependencies with components + """ + pc_files = {} + pc_files.update(_get_components_pc_files_and_content(conanfile, dep, components_info)) + description = "Conan package: %s" % package_info.name + pc_alias_file_pkg = get_alias_pc_filename_and_content( + dep, + package_info.name, + package_info.requires, + description + ) + pc_files.update(pc_alias_file_pkg) + return pc_files + + +def _get_single_package_pc_file_and_content(conanfile, dep, package_info): + """ + Get the PC files for dependencies without components + """ + description = "Conan package: %s" % package_info.name + pc_file = get_pc_filename_and_content(conanfile, dep, package_info.name, + package_info.requires, description) + return pc_file + + +def get_pc_files_and_content(conanfile, dep): + """ + Get all the PC files given a dependency (package, components and alias ones) + """ + pc_files = {} + if dep.cpp_info.has_components: # Package with components + pkg_info, components_info = get_package_with_components_info(dep) + pc_files.update(_get_package_with_components_pc_files_and_content(conanfile, dep, pkg_info, + components_info)) + else: + # Package without components + pkg_info = get_single_package_info(dep) + components_info = [] + pc_files.update(_get_single_package_pc_file_and_content(conanfile, dep, pkg_info)) + # Package and components names aliases + aliases_info = get_aliases_info(dep, pkg_info, components_info) + pc_files.update(_get_aliases_pc_files_and_content(dep, aliases_info)) + return pc_files diff --git a/conan/tools/gnu/pkgconfigdeps/pc_files_templates.py b/conan/tools/gnu/pkgconfigdeps/pc_files_templates.py new file mode 100644 index 00000000000..e0dbe84babe --- /dev/null +++ b/conan/tools/gnu/pkgconfigdeps/pc_files_templates.py @@ -0,0 +1,131 @@ +import os +import textwrap + +from jinja2 import Template, StrictUndefined + +from conan.tools.gnu.gnudeps_flags import GnuDepsFlags + + +def _get_pc_file_template(): + return textwrap.dedent("""\ + {%- macro get_libs(libdirs, cpp_info, gnudeps_flags) -%} + {%- for _ in libdirs -%} + {{ '-L"${libdir%s}"' % loop.index + " " }} + {%- endfor -%} + {%- for sys_lib in (cpp_info.libs + cpp_info.system_libs) -%} + {{ "-l%s" % sys_lib + " " }} + {%- endfor -%} + {%- for shared_flag in (cpp_info.sharedlinkflags + cpp_info.exelinkflags) -%} + {{ shared_flag + " " }} + {%- endfor -%} + {%- for _ in libdirs -%} + {%- set flag = gnudeps_flags._rpath_flags(["${libdir%s}" % loop.index]) -%} + {%- if flag|length -%} + {{ flag[0] + " " }} + {%- endif -%} + {%- endfor -%} + {%- for framework in (gnudeps_flags.frameworks + gnudeps_flags.framework_paths) -%} + {{ framework + " " }} + {%- endfor -%} + {%- endmacro -%} + + {%- macro get_cflags(includedirs, cpp_info) -%} + {%- for _ in includedirs -%} + {{ '-I"${includedir%s}"' % loop.index + " " }} + {%- endfor -%} + {%- for cxxflags in cpp_info.cxxflags -%} + {{ cxxflags + " " }} + {%- endfor -%} + {%- for cflags in cpp_info.cflags-%} + {{ cflags + " " }} + {%- endfor -%} + {%- for define in cpp_info.defines-%} + {{ "-D%s" % define + " " }} + {%- endfor -%} + {%- endmacro -%} + + prefix={{ prefix_path }} + {% for path in libdirs %} + {{ "libdir{}={}".format(loop.index, path) }} + {% endfor %} + {% for path in includedirs %} + {{ "includedir%d=%s" % (loop.index, path) }} + {% endfor %} + {% if pkg_config_custom_content %} + # Custom PC content + {{ pkg_config_custom_content }} + {% endif %} + + Name: {{ name }} + Description: {{ description }} + Version: {{ version }} + Libs: {{ get_libs(libdirs, cpp_info, gnudeps_flags) }} + Cflags: {{ get_cflags(includedirs, cpp_info) }} + {% if requires|length %} + Requires: {{ requires|join(' ') }} + {% endif %} + """) + + +def _get_alias_pc_file_template(): + return textwrap.dedent("""\ + Name: {{ name }} + Description: {{ description }} + Version: {{ version }} + {% if requires|length %} + Requires: {{ requires|join(' ') }} + {% endif %} + """) + + +def _get_formatted_dirs(folders, prefix_path_): + ret = [] + for i, directory in enumerate(folders): + directory = os.path.normpath(directory).replace("\\", "/") + prefix = "" + if not os.path.isabs(directory): + prefix = "${prefix}/" + elif directory.startswith(prefix_path_): + prefix = "${prefix}/" + directory = os.path.relpath(directory, prefix_path_).replace("\\", "/") + ret.append("%s%s" % (prefix, directory)) + return ret + + +def get_pc_filename_and_content(conanfile, dep, name, requires, description, cpp_info=None): + package_folder = dep.package_folder + version = dep.ref.version + cpp_info = cpp_info or dep.cpp_info + + prefix_path = package_folder.replace("\\", "/") + libdirs = _get_formatted_dirs(cpp_info.libdirs, prefix_path) + includedirs = _get_formatted_dirs(cpp_info.includedirs, prefix_path) + custom_content = cpp_info.get_property("pkg_config_custom_content", "PkgConfigDeps") + + context = { + "prefix_path": prefix_path, + "libdirs": libdirs, + "includedirs": includedirs, + "pkg_config_custom_content": custom_content, + "name": name, + "description": description, + "version": version, + "requires": requires, + "cpp_info": cpp_info, + "gnudeps_flags": GnuDepsFlags(conanfile, cpp_info) + } + template = Template(_get_pc_file_template(), trim_blocks=True, lstrip_blocks=True, + undefined=StrictUndefined) + return {name + ".pc": template.render(context)} + + +def get_alias_pc_filename_and_content(dep, name, requires, description): + context = { + "name": name, + "description": description, + "version": dep.ref.version, + "requires": requires + } + template = Template(_get_alias_pc_file_template(), trim_blocks=True, + lstrip_blocks=True, undefined=StrictUndefined) + return {name + ".pc": template.render(context)} diff --git a/conan/tools/gnu/pkgconfigdeps/pc_info_loader.py b/conan/tools/gnu/pkgconfigdeps/pc_info_loader.py new file mode 100644 index 00000000000..1633d286c9d --- /dev/null +++ b/conan/tools/gnu/pkgconfigdeps/pc_info_loader.py @@ -0,0 +1,136 @@ +from collections import namedtuple + +from conans.errors import ConanException + + +_PCInfoComponent = namedtuple("PCInfoComponent", ['name', 'requires', 'cpp_info', 'ref_name']) +_PCInfoPackage = namedtuple('PCInfoPackage', ['name', 'requires']) + + +def _get_name_with_namespace(namespace, name): + """Build a name with a namespace, e.g., openssl-crypto""" + return "%s-%s" % (namespace, name) + + +def _get_package_reference_name(dep): + """Get the reference name for the given package""" + # FIXME: this str(dep.ref.name) is only needed for python2.7 (unicode values). + # Remove it for Conan 2.0 + return str(dep.ref.name) + + +def _get_package_aliases(dep): + pkg_aliases = dep.cpp_info.get_property("pkg_config_aliases", "PkgConfigDeps") + return pkg_aliases or [] + + +def _get_component_aliases(dep, comp_name): + if comp_name not in dep.cpp_info.components: + # foo::foo might be referencing the root cppinfo + if dep.ref.name == comp_name: + return _get_package_aliases(dep) + raise ConanException("Component '{name}::{cname}' not found in '{name}' " + "package requirement".format(name=_get_package_reference_name(dep), + cname=comp_name)) + comp_aliases = dep.cpp_info.components[comp_name].get_property("pkg_config_aliases", + "PkgConfigDeps") + return comp_aliases or [] + + +def _get_package_name(dep): + pkg_name = dep.cpp_info.get_property("pkg_config_name", "PkgConfigDeps") + return pkg_name or _get_package_reference_name(dep) + + +def _get_component_name(dep, comp_name): + if comp_name not in dep.cpp_info.components: + # foo::foo might be referencing the root cppinfo + if dep.ref.name == comp_name: + return _get_package_name(dep) + raise ConanException("Component '{name}::{cname}' not found in '{name}' " + "package requirement".format(name=_get_package_reference_name(dep), + cname=comp_name)) + comp_name = dep.cpp_info.components[comp_name].get_property("pkg_config_name", "PkgConfigDeps") + return comp_name + + +def _get_component_requires_names(dep, cpp_info): + """ + Get all the pkg-config valid names from the requires ones given a dependency and + a CppInfo object. + + Note: CppInfo could be coming from one Component object instead of the dependency + """ + dep_ref_name = _get_package_reference_name(dep) + ret = [] + for req in cpp_info.requires: + pkg_ref_name, comp_ref_name = req.split("::") if "::" in req \ + else (dep_ref_name, req) + if dep_ref_name != pkg_ref_name: + req_conanfile = dep.dependencies.host[pkg_ref_name] + else: + req_conanfile = dep + comp_name = _get_component_name(req_conanfile, comp_ref_name) + if not comp_name: + pkg_name = _get_package_name(req_conanfile) + comp_name = _get_name_with_namespace(pkg_name, comp_ref_name) + ret.append(comp_name) + return ret + + +def get_single_package_info(dep): + """ + Get the whole package information when it does not have components declared + """ + pkg_name = _get_package_name(dep) + # At first, let's check if we have defined some component requires, e.g., "pkg::cmp1" + requires = _get_component_requires_names(dep, dep.cpp_info) + # If we have found some component requires it would be enough + if not requires: + # If no requires were found, let's try to get all the direct dependencies, + # e.g., requires = "other_pkg/1.0" + requires = [_get_package_name(req) for req in dep.dependencies.direct_host.values()] + # Save the package information + pkg_info = _PCInfoPackage(pkg_name, requires) + return pkg_info + + +def get_package_with_components_info(dep): + """ + Get the whole package and its components information like their own requires, names and even + the cpp_info for each component. + """ + pkg_name = _get_package_name(dep) + pkg_requires = [] + components_info = [] + # Loop through all the package's components + for comp_name, comp_cpp_info in dep.cpp_info.get_sorted_components().items(): + comp_requires_names = _get_component_requires_names(dep, comp_cpp_info) + pkg_comp_name = _get_component_name(dep, comp_name) + if not pkg_comp_name: + pkg_comp_name = _get_name_with_namespace(pkg_name, comp_name) + pkg_requires.append(pkg_comp_name) + # Save each component information + comp_info = _PCInfoComponent(pkg_comp_name, comp_requires_names, comp_cpp_info, comp_name) + components_info.append(comp_info) + # Save the package information + pkg_info = _PCInfoPackage(pkg_name, pkg_requires) + return pkg_info, components_info + + +def get_aliases_info(dep, package_info, components_info): + """ + Get the whole aliases information for the given package and components names already calculated + """ + aliases_info = {} + # Package aliases + if package_info: + pkg_aliases = _get_package_aliases(dep) + if pkg_aliases: + aliases_info[package_info.name] = pkg_aliases + # Component aliases + for comp_info in components_info: + comp_aliases = _get_component_aliases(dep, comp_info.ref_name) + if comp_aliases: + aliases_info[comp_info.name] = comp_aliases + return aliases_info diff --git a/conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py new file mode 100644 index 00000000000..3439d1c1323 --- /dev/null +++ b/conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py @@ -0,0 +1,24 @@ +from conan.tools.gnu.pkgconfigdeps.pc_files_creator import get_pc_files_and_content +from conans.util.files import save + + +class PkgConfigDeps(object): + + def __init__(self, conanfile): + self._conanfile = conanfile + + @property + def content(self): + """Get all the *.pc files content""" + pc_files = {} + host_req = self._conanfile.dependencies.host + for _, dep in host_req.items(): + pc_files.update(get_pc_files_and_content(self._conanfile, dep)) + return pc_files + + def generate(self): + """Save all the *.pc files""" + # Current directory is the generators_folder + generator_files = self.content + for generator_file, content in generator_files.items(): + save(generator_file, content) diff --git a/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py b/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py index a71efc771ba..10f48420339 100644 --- a/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py +++ b/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py @@ -262,10 +262,43 @@ def package_info(self): assert "componentdir=${prefix}/mydir" in pc_content -def test_pkg_with_component_requires(): +def test_pkg_with_public_deps_and_component_requires(): + """ + Testing a complex structure like: + + * first/0.1 + - Global pkg_config_name == "myfirstlib" + - Components: "cmp1" + * other/0.1 + * second/0.1 + - Requires: "first/0.1" + - Components: "mycomponent", "myfirstcomp" + + "mycomponent" requires "first::cmp1" + + "myfirstcomp" requires "mycomponent" + * third/0.1 + - Requires: "second/0.1", "other/0.1" + + Expected file structure after running PkgConfigDeps as generator: + - other.pc + - myfirstlib-cmp1.pc + - myfirstlib.pc + - second-mycomponent.pc + - second-myfirstcomp.pc + - second.pc + - third.pc + """ client = TestClient() - client.save({"conanfile.py": GenConanfile("first", "0.1").with_package_file("file.h", "0.1")}) - client.run("create . user/channel") + conanfile = textwrap.dedent(""" + from conans import ConanFile + + class Recipe(ConanFile): + + def package_info(self): + self.cpp_info.set_property("pkg_config_name", "myfirstlib") + self.cpp_info.components["cmp1"].libs = ["libcmp1"] + """) + client.save({"conanfile.py": conanfile}) + client.run("create . first/0.1@") client.save({"conanfile.py": GenConanfile("other", "0.1").with_package_file("file.h", "0.1")}) client.run("create .") @@ -273,10 +306,10 @@ def test_pkg_with_component_requires(): from conans import ConanFile class PkgConfigConan(ConanFile): - requires = "first/0.1@user/channel" + requires = "first/0.1" def package_info(self): - self.cpp_info.components["mycomponent"].requires.append("first::first") + self.cpp_info.components["mycomponent"].requires.append("first::cmp1") self.cpp_info.components["myfirstcomp"].requires.append("mycomponent") """) @@ -298,24 +331,42 @@ def package_info(self): """) client2.save({"conanfile.txt": conanfile}) client2.run("install .") + pc_content = client2.load("third.pc") # Originally posted: https://github.com/conan-io/conan/issues/9939 assert "Requires: second other" == get_requires_from_content(pc_content) pc_content = client2.load("second.pc") assert "Requires: second-mycomponent second-myfirstcomp" == get_requires_from_content(pc_content) pc_content = client2.load("second-mycomponent.pc") - # Note: the first-first.pc does not exist because first/0.1 is not defining any component but - # we're testing the "Requires" field is well defined and not the "first" recipe. - assert "Requires: first-first" == get_requires_from_content(pc_content) + assert "Requires: myfirstlib-cmp1" == get_requires_from_content(pc_content) pc_content = client2.load("second-myfirstcomp.pc") assert "Requires: second-mycomponent" == get_requires_from_content(pc_content) - pc_content = client2.load("first.pc") - assert "" == get_requires_from_content(pc_content) + pc_content = client2.load("myfirstlib.pc") + assert "Requires: myfirstlib-cmp1" == get_requires_from_content(pc_content) pc_content = client2.load("other.pc") assert "" == get_requires_from_content(pc_content) -def test_pkg_getting_public_requires(): +def test_pkg_with_public_deps_and_component_requires_2(): + """ + Testing another complex structure like: + + * other/0.1 + - Global pkg_config_name == "fancy_name" + - Components: "cmp1", "cmp2", "cmp3" + + "cmp1" pkg_config_name == "component1" (it shouldn't be affected by "fancy_name") + + "cmp3" pkg_config_name == "component3" (it shouldn't be affected by "fancy_name") + + "cmp3" requires "cmp1" + * pkg/0.1 + - Requires: "other/0.1" -> "other::cmp1" + + Expected file structure after running PkgConfigDeps as generator: + - component1.pc + - component3.pc + - other-cmp2.pc + - other.pc + - pkg.pc + """ client = TestClient() conanfile = textwrap.dedent(""" from conans import ConanFile @@ -323,10 +374,12 @@ def test_pkg_getting_public_requires(): class Recipe(ConanFile): def package_info(self): + self.cpp_info.set_property("pkg_config_name", "fancy_name") self.cpp_info.components["cmp1"].libs = ["other_cmp1"] + self.cpp_info.components["cmp1"].set_property("pkg_config_name", "component1") self.cpp_info.components["cmp2"].libs = ["other_cmp2"] self.cpp_info.components["cmp3"].requires.append("cmp1") - + self.cpp_info.components["cmp3"].set_property("pkg_config_name", "component3") """) client.save({"conanfile.py": conanfile}) client.run("create . other/1.0@") @@ -354,10 +407,100 @@ def package_info(self): client2.save({"conanfile.txt": conanfile}) client2.run("install .") pc_content = client2.load("pkg.pc") - assert "Requires: other-cmp1" == get_requires_from_content(pc_content) - pc_content = client2.load("other.pc") - assert "Requires: other-cmp1 other-cmp2 other-cmp3" == get_requires_from_content(pc_content) - assert client2.load("other-cmp1.pc") - assert client2.load("other-cmp2.pc") - pc_content = client2.load("other-cmp3.pc") - assert "Requires: other-cmp1" == get_requires_from_content(pc_content) + assert "Requires: component1" == get_requires_from_content(pc_content) + pc_content = client2.load("fancy_name.pc") + assert "Requires: component1 fancy_name-cmp2 component3" == get_requires_from_content(pc_content) + assert client2.load("component1.pc") + assert client2.load("fancy_name-cmp2.pc") + pc_content = client2.load("component3.pc") + assert "Requires: component1" == get_requires_from_content(pc_content) + + +def test_pkg_config_name_full_aliases(): + """ + Testing a simpler structure but paying more attention into several aliases. + Expected file structure after running PkgConfigDeps as generator: + - compo1.pc + - compo1_alias.pc + - pkg_alias1.pc + - pkg_alias2.pc + - pkg_other_name.pc + - second-mycomponent.pc + - second.pc + """ + client = TestClient() + conanfile = textwrap.dedent(""" + from conans import ConanFile + + class Recipe(ConanFile): + + def package_info(self): + self.cpp_info.set_property("pkg_config_name", "pkg_other_name") + self.cpp_info.set_property("pkg_config_aliases", ["pkg_alias1", "pkg_alias2"]) + self.cpp_info.components["cmp1"].libs = ["libcmp1"] + self.cpp_info.components["cmp1"].set_property("pkg_config_name", "compo1") + self.cpp_info.components["cmp1"].set_property("pkg_config_aliases", ["compo1_alias"]) + """) + client.save({"conanfile.py": conanfile}) + client.run("create . first/0.3@") + + conanfile = textwrap.dedent(""" + from conans import ConanFile + + class PkgConfigConan(ConanFile): + requires = "first/0.3" + + def package_info(self): + self.cpp_info.components["mycomponent"].requires.append("first::cmp1") + + """) + client.save({"conanfile.py": conanfile}, clean_first=True) + client.run("create . second/0.2@") + + conanfile = textwrap.dedent(""" + [requires] + second/0.2 + + [generators] + PkgConfigDeps + """) + client.save({"conanfile.txt": conanfile}, clean_first=True) + client.run("install .") + + pc_content = client.load("compo1.pc") + assert "Description: Conan component: compo1" in pc_content + assert "Requires" not in pc_content + + pc_content = client.load("compo1_alias.pc") + content = textwrap.dedent("""\ + Name: compo1_alias + Description: Alias compo1_alias for compo1 + Version: 0.3 + Requires: compo1 + """) + assert content == pc_content + + pc_content = client.load("pkg_other_name.pc") + assert "Description: Conan package: pkg_other_name" in pc_content + assert "Requires: compo1" in pc_content + + pc_content = client.load("pkg_alias1.pc") + content = textwrap.dedent("""\ + Name: pkg_alias1 + Description: Alias pkg_alias1 for pkg_other_name + Version: 0.3 + Requires: pkg_other_name + """) + assert content == pc_content + + pc_content = client.load("pkg_alias2.pc") + content = textwrap.dedent("""\ + Name: pkg_alias2 + Description: Alias pkg_alias2 for pkg_other_name + Version: 0.3 + Requires: pkg_other_name + """) + assert content == pc_content + + pc_content = client.load("second-mycomponent.pc") + assert "Requires: compo1" == get_requires_from_content(pc_content)