Repository: conan-io/conan
Pull request: Refactored `conan.tools.gnu.pkgconfigdeps` module and added new `pkg_config_aliases` property
Changelog: Feature: `pkg_config_name` is used as the main name for a package/component and it will be used as the name for the `*.pc` file.
Changelog: Feature: Added new property`pkg_config_aliases` which admits a  list of strings to define different aliases for any package/component.
Docs: https://github.com/conan-io/docs/pull/2315

Closes: #9848

- [x] Refer to the issue that supports this Pull Request.
- [x] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [x] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one. 

<sup>**Note:** By default this PR will skip the slower tests and will use a limited set of python versions. Check [here](https://github.com/conan-io/conan/blob/develop/.github/PR_INCREASE_TESTING.md) how to increase the testing level by writing some tags in the current PR body text.</sup>


Problem statement:
[feature] introduce alias for PkgConfigDeps
**Issue**: The current PkgConfigDeps scopes the `pc` files with the name of the package, but people, specially from open source and conan-center, want to consume "crypto.pc" not "openssl-crypto.pc", otherwise every consumer would need to modify the build system script.

**Proposal**

1. Keep generating files scoped, always, and a global one that include all the components:

With components:
```
self.cpp_info.components["crypto"] => "openssl-crypto.pc"
self.cpp_info.components["ssl"] => "openssl-ssl.pc"
self.cpp_info.components["tls"] => "openssl-tls.pc"
self.cpp_info.components["openssl"] => "openssl-openssl.pc"
"openssl.pc" => include all components
```
 
Without components:

```
"openssl.pc" => Declares the information using the same template of the components
```


 2. Enable a new property `pkg_config_aliases` similar to `cmake_target_aliases` (@SSE4) only when the user **really want** to declare a `pc` file without scope. This will be done in conan center in very special cases where the `pc` file is kind of a "standard". Any alias to `core` or things like that should be avoided.
 
```
self.cpp_info.components["crypto"].set_property("pkg_config_aliases", ["crypto"]) => "crypto.pc" includes "openssl-crypto.pc"
self.cpp_info.set_property("pkg_config_aliases", ["bar"]) => "bar.pc" includes "openssl.pc"
```

3. There is another special case where the recipe wants to declare a `foo.pc` but including only specific component/s, not to all of them, for example, the "core" component but not some optionals. In that case:

```
self.cpp_info.components["core"] => "foo-core.pc"
self.cpp_info.components["other"] => "foo-other.pc"
self.cpp_info.components["core"].set_property("pkg_config_aliases", ["foo"]) => "foo.pc" include to "foo-core.pc"  

In this case the `foo.pc` overwrite the global `foo.pc`.

Hints from issue discussion:
For now, it's blocked by https://github.com/conan-io/conan/pull/9851
Actually, it was not blocked by that MR. It can be developed without having it merged

Commit message: Changed 'wrapper' word to 'alias' instead. It's clearer than wrapper

Patch to review:
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..55e68632073
--- /dev/null
+++ b/conan/tools/gnu/pkgconfigdeps/pc_files_creator.py
@@ -0,0 +1,76 @@
+from conan.tools.gnu.pkgconfigdeps.pc_files_templates import PCFilesTemplate
+from conan.tools.gnu.pkgconfigdeps.pc_info_loader import PCInfoLoader
+
+
+class PCFilesCreator(object):
+
+    def __init__(self, conanfile, dep):
+        self._conanfile = conanfile
+        self._dep = dep
+        self._pc_info_loader = PCInfoLoader(conanfile, dep)
+        self._pc_files_templates = PCFilesTemplate(conanfile, dep)
+
+    @property
+    def _aliases_pc_files_and_content(self):
+        """Get all the *.pc files content for the aliases defined previously"""
+        self._pc_info_loader.load_aliases_info()
+        pc_files = {}
+        for name, aliases in self._pc_info_loader.aliases_info.items():
+            for alias in aliases:
+                pc_alias_file = self._pc_files_templates.get_alias_pc_filename_and_content(
+                    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(self):
+        pc_files = {}
+        # Loop through all the package's components
+        for pc_info_component in self._pc_info_loader.components_info:
+            # Get the *.pc file content for each component
+            description = "Conan component: %s" % pc_info_component.name
+            pc_file = self._pc_files_templates.get_pc_filename_and_content(
+                pc_info_component.name,
+                pc_info_component.requires,
+                description,
+                cpp_info=pc_info_component.cpp_info)
+            pc_files.update(pc_file)
+        return pc_files
+
+    @property
+    def _package_with_components_pc_files_and_content(self):
+        self._pc_info_loader.load_package_with_components_info()
+        pc_files = {}
+        pc_files.update(self._get_components_pc_files_and_content())
+        pkg_info = self._pc_info_loader.package_info
+        description = self._conanfile.description or "Conan package: %s" % pkg_info.name
+        pc_alias_file_pkg = self._pc_files_templates.get_alias_pc_filename_and_content(
+            pkg_info.name,
+            pkg_info.requires,
+            description
+        )
+        pc_files.update(pc_alias_file_pkg)
+        return pc_files
+
+    @property
+    def _single_package_pc_file_and_content(self):
+        self._pc_info_loader.load_single_package_info()
+        pkg_info = self._pc_info_loader.package_info
+        description = self._conanfile.description or "Conan package: %s" % pkg_info.name
+        pc_file = self._pc_files_templates.get_pc_filename_and_content(pkg_info.name,
+                                                                       pkg_info.requires,
+                                                                       description)
+        return pc_file
+
+    @property
+    def pc_files_and_content(self):
+        pc_files = {}
+        if self._dep.cpp_info.has_components:
+            pc_files.update(self._package_with_components_pc_files_and_content)
+        else:
+            pc_files.update(self._single_package_pc_file_and_content)
+        # Package and components aliases
+        pc_files.update(self._aliases_pc_files_and_content)
+        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..3c61e426fa3
--- /dev/null
+++ b/conan/tools/gnu/pkgconfigdeps/pc_files_templates.py
@@ -0,0 +1,135 @@
+import os
+import textwrap
+
+from jinja2 import Template, StrictUndefined
+
+from conan.tools.gnu.gnudeps_flags import GnuDepsFlags
+
+
+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
+
+
+class PCFilesTemplate(object):
+
+    def __init__(self, conanfile, dep):
+        self._conanfile = conanfile
+        self._package_folder = dep.package_folder
+        self._version = dep.ref.version
+        self._cpp_info = dep.cpp_info
+
+    @property
+    def pc_file_template(self):
+        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 %}
+        """)
+
+    @property
+    def alias_pc_file_template(self):
+        return textwrap.dedent("""\
+            Name: {{ name }}
+            Description: {{ description }}
+            Version: {{ version }}
+            {% if requires|length %}
+            Requires: {{ requires|join(' ') }}
+            {% endif %}
+        """)
+
+    def get_pc_filename_and_content(self, name, requires, description, cpp_info=None):
+        cpp_info = cpp_info or self._cpp_info
+        prefix_path = self._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": self._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 {name + ".pc": template.render(context)}
+
+    def get_alias_pc_filename_and_content(self, name, requires, description):
+        context = {
+            "name": name,
+            "description": description,
+            "version": self._version,
+            "requires": requires
+        }
+        template = Template(self.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..9c9d1332919
--- /dev/null
+++ b/conan/tools/gnu/pkgconfigdeps/pc_info_loader.py
@@ -0,0 +1,139 @@
+from collections import namedtuple
+
+from conans.errors import ConanException
+
+
+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
+
+
+_PCInfoComponent = namedtuple("PCInfoComponent", ['name', 'requires', 'cpp_info', 'ref_name'])
+_PCInfoPackage = namedtuple('PCInfoPackage', ['name', 'requires'])
+
+
+class PCInfoLoader(object):
+
+    def __init__(self, conanfile, dep):
+        self._conanfile = conanfile
+        self._dep = dep
+        self._pkg_name = _get_package_name(dep)
+        self._pkg_ref_name = _get_package_reference_name(dep)
+        # Initialized variables
+        self._pkg_info = None
+        self._components_info = []
+        self._aliases_info = {}
+
+    @property
+    def package_info(self):
+        return self._pkg_info
+
+    @property
+    def components_info(self):
+        return self._components_info
+
+    @property
+    def aliases_info(self):
+        return self._aliases_info
+
+    def _get_component_requires_names(self, 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_ref_name, comp_ref_name = req.split("::") if "::" in req \
+                                                          else (self._pkg_ref_name, req)
+            # FIXME: it could allow defining requires to not direct dependencies
+            req_conanfile = self._conanfile.dependencies.host[pkg_ref_name]
+            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 load_single_package_info(self):
+        """Get all the dependency's requirements (public dependencies and components)"""
+        # At first, let's check if we have defined some component requires, e.g., "pkg::cmp1"
+        requires = self._get_component_requires_names(self._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 self._dep.dependencies.direct_host.values()]
+        # Save the package information
+        self._pkg_info = _PCInfoPackage(self._pkg_name, requires)
+
+    def load_package_with_components_info(self):
+        pkg_requires = []
+        # Loop through all the package's components
+        for comp_name, comp_cpp_info in self._dep.cpp_info.get_sorted_components().items():
+            comp_requires_names = self._get_component_requires_names(comp_cpp_info)
+            pkg_comp_name = _get_component_name(self._dep, comp_name)
+            if not pkg_comp_name:
+                pkg_comp_name = _get_name_with_namespace(self._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)
+            self._components_info.append(comp_info)
+        # Save the package information
+        self._pkg_info = _PCInfoPackage(self._pkg_name, pkg_requires)
+
+    def load_aliases_info(self):
+        # Component aliases
+        for comp_info in self._components_info:
+            comp_aliases = _get_component_aliases(self._dep, comp_info.ref_name)
+            if comp_aliases:
+                self._aliases_info[comp_info.name] = comp_aliases
+        # Package aliases
+        if self._pkg_info:
+            pkg_aliases = _get_package_aliases(self._dep)
+            if pkg_aliases:
+                self._aliases_info[self._pkg_info.name] = pkg_aliases
diff --git a/conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py
new file mode 100644
index 00000000000..3dbf2056a3a
--- /dev/null
+++ b/conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py
@@ -0,0 +1,25 @@
+from conan.tools.gnu.pkgconfigdeps.pc_files_creator import PCFilesCreator
+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_creator = PCFilesCreator(self._conanfile, dep)
+            pc_files.update(pc_files_creator.pc_files_and_content)
+        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)


Code review comments:
diff_hunk: @@ -0,0 +1,139 @@
+from collections import namedtuple
+
+from conans.errors import ConanException
+
+
+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
+
+
+_PCInfoComponent = namedtuple("PCInfoComponent", ['name', 'requires', 'cpp_info', 'ref_name'])
+_PCInfoPackage = namedtuple('PCInfoPackage', ['name', 'requires'])
+
+
+class PCInfoLoader(object):
+
+    def __init__(self, conanfile, dep):
+        self._conanfile = conanfile
+        self._dep = dep
+        self._pkg_name = _get_package_name(dep)
+        self._pkg_ref_name = _get_package_reference_name(dep)
+        # Initialized variables
+        self._pkg_info = None
+        self._components_info = []
+        self._aliases_info = {}
+
+    @property
+    def package_info(self):
+        return self._pkg_info
+
+    @property
+    def components_info(self):
+        return self._components_info
+
+    @property
+    def aliases_info(self):
+        return self._aliases_info
+
+    def _get_component_requires_names(self, 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_ref_name, comp_ref_name = req.split("::") if "::" in req \
+                                                          else (self._pkg_ref_name, req)
+            # FIXME: it could allow defining requires to not direct dependencies
+            req_conanfile = self._conanfile.dependencies.host[pkg_ref_name]
path: conan/tools/gnu/pkgconfigdeps/pc_info_loader.py
text: @user1:
Are you sure these are the right dependencies? Shouldn't it be the ``dep`` dependencies, instead of the root consumer ``conanfile.py``?

diff_hunk: @@ -0,0 +1,76 @@
+from conan.tools.gnu.pkgconfigdeps.pc_files_templates import PCFilesTemplate
+from conan.tools.gnu.pkgconfigdeps.pc_info_loader import PCInfoLoader
+
+
+class PCFilesCreator(object):
+
+    def __init__(self, conanfile, dep):
+        self._conanfile = conanfile
path: conan/tools/gnu/pkgconfigdeps/pc_files_creator.py
text: @user1:
This conanfile seems to be the "consumer" conanfile. And it is only used here for the ``description`` field. Shouldn't the description field be from the actual ``dep``? Maybe it is even possible to remove it completely, and have the ``description`` almost empty or automated to something like "Conan package: xxxx"