Repository: conan-io/conan
Pull request: New PkgConfigDeps generator #9110
Changelog: Feature: New ``PkgConfigDeps`` generator. 
Docs: https://github.com/conan-io/docs/pull/2133

Closes: #9110

- [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.
- [x] 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] New ``PkgConfigDeps`` generator
Create a new ``PkgConfigDeps`` generator in ``conan.tools.gnu``:

- Create a .pc file per dependency
- Handle components
- Uses only modern practices (can assume always build/host context)

The location of the output files will be injected by ``AutotoolsToolchain`` and ``MesonToolchain`` (in both cases the `PKG_CONFIG_PATH` env var is the way)

Hints from issue discussion:

Commit message: Deleted bad imports. Using a general class

Patch to review:
diff --git a/conan/tools/gnu/__init__.py b/conan/tools/gnu/__init__.py
index 0e60df9ed31..287a9e7e207 100644
--- a/conan/tools/gnu/__init__.py
+++ b/conan/tools/gnu/__init__.py
@@ -1,3 +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
diff --git a/conan/tools/gnu/autotoolsdeps.py b/conan/tools/gnu/autotoolsdeps.py
index 4c9e25923ee..5ca427cc776 100644
--- a/conan/tools/gnu/autotoolsdeps.py
+++ b/conan/tools/gnu/autotoolsdeps.py
@@ -1,5 +1,5 @@
 from conan.tools.env import Environment
-from conan.tools.gnu.autotoolsdeps_flags import AutoToolsDepsFlags
+from conan.tools.gnu.gnudeps_flags import GnuDepsFlags
 from conans.model.new_build_info import NewCppInfo
 
 
@@ -24,7 +24,7 @@ def cpp_info(self):
         return self._cpp_info
 
     def environment(self):
-        flags = AutoToolsDepsFlags(self._conanfile, self.cpp_info)
+        flags = GnuDepsFlags(self._conanfile, self.cpp_info)
 
         # cpp_flags
         cpp_flags = []
diff --git a/conan/tools/gnu/autotoolsdeps_flags.py b/conan/tools/gnu/gnudeps_flags.py
similarity index 96%
rename from conan/tools/gnu/autotoolsdeps_flags.py
rename to conan/tools/gnu/gnudeps_flags.py
index 8f76374fa7e..eea430868d0 100644
--- a/conan/tools/gnu/autotoolsdeps_flags.py
+++ b/conan/tools/gnu/gnudeps_flags.py
@@ -1,7 +1,10 @@
-from conans.model.new_build_info import NewCppInfo
+"""
+    This is a helper class which offers a lot of useful methods and attributes
+"""
+# FIXME: only for tools.gnu? perhaps it should be a global module
 
 
-class AutoToolsDepsFlags(object):
+class GnuDepsFlags(object):
 
     def __init__(self, conanfile, cpp_info):
         self._conanfile = conanfile
diff --git a/conan/tools/gnu/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps.py
new file mode 100644
index 00000000000..32ffeebb43f
--- /dev/null
+++ b/conan/tools/gnu/pkgconfigdeps.py
@@ -0,0 +1,191 @@
+"""
+    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
+
+from conan.tools.gnu.gnudeps_flags import GnuDepsFlags
+from conans.errors import ConanException
+from conans.util.files import save
+
+
+def _concat_if_not_empty(groups):
+    return " ".join([param for group in groups for param in group if param and param.strip()])
+
+
+def get_target_namespace(req):
+    ret = req.new_cpp_info.get_property("pkg_config_name", "PkgConfigDeps")
+    return ret or req.ref.name
+
+
+def get_component_alias(req, comp_name):
+    if comp_name not in req.new_cpp_info.components:
+        # foo::foo might be referencing the root cppinfo
+        if req.ref.name == comp_name:
+            return get_target_namespace(req)
+        raise ConanException("Component '{name}::{cname}' not found in '{name}' "
+                             "package requirement".format(name=req.ref.name, cname=comp_name))
+    ret = req.new_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
+
+    def _get_require_comp_name(self, pkg_name, req):
+        pkg, comp_name = req.split("::") if "::" in req else (pkg_name, req)
+        req = self._conanfile.dependencies.direct_host[pkg]
+        cmp_name = get_component_alias(req, comp_name)
+        return cmp_name
+
+    def get_components(self, pkg_name, dep):
+        ret = []
+        for comp_name, comp in dep.new_cpp_info.get_sorted_components().items():
+            comp_genname = get_component_alias(dep, comp_name)
+            comp_requires_gennames = []
+            for require in comp.requires:
+                comp_requires_gennames.append(self._get_require_comp_name(pkg_name, require))
+            ret.append((comp_genname, comp, comp_requires_gennames))
+        return ret
+
+    def _get_public_require_deps(self, comp):
+        public_comp_deps = []
+        for require in comp.requires:
+            if "::" in require:  # Points to a component of a different package
+                pkg, cmp_name = require.split("::")
+                req = self._conanfile.dependencies.direct_host[pkg]
+                public_comp_deps.append(
+                    (get_target_namespace(req), get_component_alias(req, cmp_name)))
+            else:  # Points to a component of same package
+                public_comp_deps.append((get_target_namespace(self._conanfile),
+                                         get_component_alias(self._conanfile, require)))
+        return public_comp_deps
+
+    @property
+    def content(self):
+        ret = {}
+        host_req = self._conanfile.dependencies.host
+        for require, dep in host_req.items():
+            pkg_genname = get_target_namespace(dep)
+
+            if dep.new_cpp_info.has_components:
+                components = self.get_components(dep.ref.name, dep)
+                for comp_genname, comp, comp_requires_gennames in components:
+                    ret["%s.pc" % comp_genname] = self._pc_file_content(
+                        "%s-%s" % (pkg_genname, comp_genname),
+                        dep,
+                        comp_requires_gennames, cpp_info=comp)
+                comp_gennames = [comp_genname for comp_genname, _, _ in components]
+                if pkg_genname not in comp_gennames:
+                    ret["%s.pc" % pkg_genname] = self.global_pc_file_contents(pkg_genname,
+                                                                              dep,
+                                                                              comp_gennames)
+            else:
+                require_public_deps = [_d for _, _d in self._get_public_require_deps(dep.new_cpp_info)]
+                ret["%s.pc" % pkg_genname] = self._pc_file_content(pkg_genname, dep,
+                                                                   require_public_deps)
+        return ret
+
+    def _pc_file_content(self, name, dep, requires_gennames, cpp_info=None):
+        prefix_path = dep.package_folder.replace("\\", "/")
+        lines = ['prefix=%s' % prefix_path]
+
+        cpp_info = cpp_info or dep.new_cpp_info
+        gnudeps_flags = GnuDepsFlags(self._conanfile, cpp_info)
+
+        libdir_vars = []
+        dir_lines, varnames = self._generate_dir_lines(prefix_path, "libdir", cpp_info.libdirs)
+        if dir_lines:
+            libdir_vars = varnames
+            lines.extend(dir_lines)
+
+        includedir_vars = []
+        dir_lines, varnames = self._generate_dir_lines(prefix_path, "includedir",
+                                                       cpp_info.includedirs)
+        if dir_lines:
+            includedir_vars = varnames
+            lines.extend(dir_lines)
+
+        pkg_config_custom_content = cpp_info.get_property("pkg_config_custom_content",
+                                                          "PkgConfigDeps")
+        if pkg_config_custom_content:
+            lines.append(pkg_config_custom_content)
+
+        lines.append("")
+        lines.append("Name: %s" % name)
+        description = self._conanfile.description or "Conan package: %s" % name
+        lines.append("Description: %s" % description)
+        lines.append("Version: %s" % dep.ref.version)
+        libdirs_flags = ['-L"${%s}"' % name for name in libdir_vars]
+        lib_paths = ["${%s}" % libdir for libdir in libdir_vars]
+        libnames_flags = ["-l%s " % name for name in (cpp_info.libs + cpp_info.system_libs)]
+        shared_flags = cpp_info.sharedlinkflags + cpp_info.exelinkflags
+
+        lines.append("Libs: %s" % _concat_if_not_empty([libdirs_flags,
+                                                        libnames_flags,
+                                                        shared_flags,
+                                                        gnudeps_flags._rpath_flags(lib_paths),
+                                                        gnudeps_flags.frameworks,
+                                                        gnudeps_flags.framework_paths]))
+        include_dirs_flags = ['-I"${%s}"' % name for name in includedir_vars]
+
+        lines.append("Cflags: %s" % _concat_if_not_empty(
+            [include_dirs_flags,
+             cpp_info.cxxflags,
+             cpp_info.cflags,
+             ["-D%s" % d for d in cpp_info.defines]]))
+
+        if requires_gennames:
+            public_deps = " ".join(requires_gennames)
+            lines.append("Requires: %s" % public_deps)
+        return "\n".join(lines) + "\n"
+
+    def global_pc_file_contents(self, name, dep, comp_gennames):
+        lines = ["Name: %s" % name]
+        description = self._conanfile.description or "Conan package: %s" % name
+        lines.append("Description: %s" % description)
+        lines.append("Version: %s" % dep.ref.version)
+
+        if comp_gennames:
+            public_deps = " ".join(comp_gennames)
+            lines.append("Requires: %s" % public_deps)
+        return "\n".join(lines) + "\n"
+
+    @staticmethod
+    def _generate_dir_lines(prefix_path, varname, dirs):
+        lines = []
+        varnames = []
+        for i, directory in enumerate(dirs):
+            directory = os.path.normpath(directory).replace("\\", "/")
+            name = varname if i == 0 else "%s%d" % (varname, (i + 1))
+            prefix = ""
+            if not os.path.isabs(directory):
+                prefix = "${prefix}/"
+            elif directory.startswith(prefix_path):
+                prefix = "${prefix}/"
+                directory = os.path.relpath(directory, prefix_path).replace("\\", "/")
+            lines.append("%s=%s%s" % (name, prefix, directory))
+            varnames.append(name)
+        return lines, varnames
+
+    def generate(self):
+        # 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/client/generators/__init__.py b/conans/client/generators/__init__.py
index dfd8000adba..1af311f38b9 100644
--- a/conans/client/generators/__init__.py
+++ b/conans/client/generators/__init__.py
@@ -68,7 +68,7 @@ def __init__(self):
         self._new_generators = ["CMakeToolchain", "CMakeDeps", "MSBuildToolchain",
                                 "MesonToolchain", "MSBuildDeps", "QbsToolchain", "msbuild",
                                 "VirtualEnv", "AutotoolsDeps", "AutotoolsToolchain",
-                                "BazelDeps", "BazelToolchain"]
+                                "BazelDeps", "BazelToolchain", "PkgConfigDeps"]
 
     def add(self, name, generator_class, custom=False):
         if name not in self._generators or custom:
@@ -100,6 +100,9 @@ def _new_generator(self, generator_name, output):
         elif generator_name == "AutotoolsToolchain":
             from conan.tools.gnu import AutotoolsToolchain
             return AutotoolsToolchain
+        elif generator_name == "PkgConfigDeps":
+            from conan.tools.gnu import PkgConfigDeps
+            return PkgConfigDeps
         elif generator_name == "MSBuildToolchain":
             from conan.tools.microsoft import MSBuildToolchain
             return MSBuildToolchain
@@ -109,9 +112,6 @@ def _new_generator(self, generator_name, output):
         elif generator_name in ("MSBuildDeps", "msbuild"):
             from conan.tools.microsoft import MSBuildDeps
             return MSBuildDeps
-        elif generator_name == "CMakeDeps":
-            from conan.tools.cmake import CMakeDeps
-            return CMakeDeps
         elif generator_name == "QbsToolchain" or generator_name == "QbsProfile":
             from conan.tools.qbs.qbsprofile import QbsProfile
             return QbsProfile
diff --git a/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py b/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
new file mode 100644
index 00000000000..1f082a3f3b4
--- /dev/null
+++ b/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
@@ -0,0 +1,256 @@
+import os
+import platform
+import textwrap
+import unittest
+
+import pytest
+
+from conans.test.utils.tools import TestClient
+from conans.util.files import load
+
+
+# Without compiler, def rpath_flags(settings, os_build, lib_paths): doesn't append the -Wl...etc
+@pytest.mark.tool_compiler
+def test_pkg_config_dirs():
+    # https://github.com/conan-io/conan/issues/2756
+    conanfile = textwrap.dedent("""
+        import os
+        from conans import ConanFile
+
+        class PkgConfigConan(ConanFile):
+            name = "MyLib"
+            version = "0.1"
+
+            def package_info(self):
+                self.cpp_info.frameworkdirs = []
+                self.cpp_info.filter_empty = False
+                libname = "mylib"
+                fake_dir = os.path.join("/", "my_absoulte_path", "fake")
+                include_dir = os.path.join(fake_dir, libname, "include")
+                lib_dir = os.path.join(fake_dir, libname, "lib")
+                lib_dir2 = os.path.join(self.package_folder, "lib2")
+                self.cpp_info.includedirs = [include_dir]
+                self.cpp_info.libdirs = [lib_dir, lib_dir2]
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create .")
+    client.run("install MyLib/0.1@ -g PkgConfigDeps")
+
+    pc_path = os.path.join(client.current_folder, "MyLib.pc")
+    assert os.path.exists(pc_path) is True
+    pc_content = load(pc_path)
+    expected_rpaths = ""
+    if platform.system() in ("Linux", "Darwin"):
+        expected_rpaths = ' -Wl,-rpath,"${libdir}" -Wl,-rpath,"${libdir2}"'
+    expected_content = textwrap.dedent("""\
+        libdir=/my_absoulte_path/fake/mylib/lib
+        libdir2=${prefix}/lib2
+        includedir=/my_absoulte_path/fake/mylib/include
+
+        Name: MyLib
+        Description: Conan package: MyLib
+        Version: 0.1
+        Libs: -L"${libdir}" -L"${libdir2}"%s
+        Cflags: -I"${includedir}\"""" % expected_rpaths)
+    assert "\n".join(pc_content.splitlines()[1:]) == expected_content
+
+    def assert_is_abs(path):
+        assert os.path.isabs(path) is True
+
+    for line in pc_content.splitlines():
+        if line.startswith("includedir="):
+            assert_is_abs(line[len("includedir="):])
+            assert line.endswith("include") is True
+        elif line.startswith("libdir="):
+            assert_is_abs(line[len("libdir="):])
+            assert line.endswith("lib") is True
+        elif line.startswith("libdir3="):
+            assert "${prefix}/lib2" in line
+
+
+def test_empty_dirs():
+    # Adding in package_info all the empty directories
+    conanfile = textwrap.dedent("""
+        import os
+        from conans import ConanFile
+
+        class PkgConfigConan(ConanFile):
+            name = "MyLib"
+            version = "0.1"
+
+            def package_info(self):
+                self.cpp_info.includedirs = []
+                self.cpp_info.libdirs = []
+                self.cpp_info.bindirs = []
+                self.cpp_info.libs = []
+                self.cpp_info.frameworkdirs = []
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create .")
+    client.run("install MyLib/0.1@ -g PkgConfigDeps")
+
+    pc_path = os.path.join(client.current_folder, "MyLib.pc")
+    assert os.path.exists(pc_path) is True
+    pc_content = load(pc_path)
+    expected = textwrap.dedent("""
+        Name: MyLib
+        Description: Conan package: MyLib
+        Version: 0.1
+        Libs:%s
+        Cflags: """ % " ")  # ugly hack for trailing whitespace removed by IDEs
+    assert "\n".join(pc_content.splitlines()[1:]) == expected
+
+
+def test_pkg_config_rpaths():
+    # rpath flags are only generated for gcc and clang
+    profile = textwrap.dedent("""\
+        [settings]
+        os=Linux
+        compiler=gcc
+        compiler.version=7
+        compiler.libcxx=libstdc++
+        """)
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+
+        class PkgConfigConan(ConanFile):
+            name = "MyLib"
+            version = "0.1"
+            settings = "os", "compiler"
+            exports = "mylib.so"
+
+            def package(self):
+                self.copy("mylib.so", dst="lib")
+
+            def package_info(self):
+                self.cpp_info.libs = ["mylib"]
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile,
+                 "linux_gcc": profile,
+                 "mylib.so": "fake lib content"})
+    client.run("create . -pr=linux_gcc")
+    client.run("install MyLib/0.1@ -g PkgConfigDeps -pr=linux_gcc")
+
+    pc_path = os.path.join(client.current_folder, "MyLib.pc")
+    assert os.path.exists(pc_path) is True
+    pc_content = load(pc_path)
+    assert "-Wl,-rpath,\"${libdir}\"" in pc_content
+
+
+def test_system_libs():
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        from conans.tools import save
+        import os
+
+        class PkgConfigConan(ConanFile):
+            name = "MyLib"
+            version = "0.1"
+
+            def package(self):
+                save(os.path.join(self.package_folder, "lib", "file"), "")
+
+            def package_info(self):
+                self.cpp_info.libs = ["mylib1", "mylib2"]
+                self.cpp_info.system_libs = ["system_lib1", "system_lib2"]
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create .")
+    client.run("install MyLib/0.1@ -g PkgConfigDeps")
+
+    pc_content = client.load("MyLib.pc")
+    assert 'Libs: -L"${libdir}" -lmylib1  -lmylib2  -lsystem_lib1  -lsystem_lib2 ' in pc_content
+
+
+def test_multiple_include():
+    # https://github.com/conan-io/conan/issues/7056
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        from conans.tools import save
+        import os
+
+        class PkgConfigConan(ConanFile):
+            def package(self):
+                for p in ["inc1", "inc2", "inc3/foo", "lib1", "lib2"]:
+                    save(os.path.join(self.package_folder, p, "file"), "")
+
+            def package_info(self):
+                self.cpp_info.includedirs = ["inc1", "inc2", "inc3/foo"]
+                self.cpp_info.libdirs = ["lib1", "lib2"]
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create . pkg/0.1@")
+    client.run("install pkg/0.1@ -g PkgConfigDeps")
+
+    pc_content = client.load("pkg.pc")
+    assert "includedir=${prefix}/inc1" in pc_content
+    assert "includedir2=${prefix}/inc2" in pc_content
+    assert "includedir3=${prefix}/inc3/foo" in pc_content
+    assert "libdir=${prefix}/lib1" in pc_content
+    assert "libdir2=${prefix}/lib2" in pc_content
+    assert 'Libs: -L"${libdir}" -L"${libdir2}"' in pc_content
+    assert 'Cflags: -I"${includedir}" -I"${includedir2}" -I"${includedir3}"' in pc_content
+
+
+def test_custom_content():
+    # https://github.com/conan-io/conan/issues/7661
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        from conans.tools import save
+        import os
+        import textwrap
+
+        class PkgConfigConan(ConanFile):
+
+            def package(self):
+                save(os.path.join(self.package_folder, "include" ,"file"), "")
+                save(os.path.join(self.package_folder, "lib" ,"file"), "")
+
+            def package_info(self):
+                custom_content = textwrap.dedent(\"""
+                        datadir=${prefix}/share
+                        schemasdir=${datadir}/mylib/schemas
+                        bindir=${prefix}/bin
+                    \""")
+                self.cpp_info.set_property("pkg_config_custom_content", custom_content)
+                self.cpp_info.includedirs = ["include"]
+                self.cpp_info.libdirs = ["lib"]
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create . pkg/0.1@")
+    client.run("install pkg/0.1@ -g PkgConfigDeps")
+
+    pc_content = client.load("pkg.pc")
+    assert "libdir=${prefix}/lib" in pc_content
+    assert "datadir=${prefix}/share" in pc_content
+    assert "schemasdir=${datadir}/mylib/schemas" in pc_content
+    assert "bindir=${prefix}/bin" in pc_content
+    assert "Name: pkg" in pc_content
+
+
+def test_custom_content_components():
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        from conans.tools import save
+        import os
+        import textwrap
+
+        class PkgConfigConan(ConanFile):
+
+            def package_info(self):
+                self.cpp_info.components["mycomponent"].set_property("pkg_config_custom_content",
+                                                                     "componentdir=${prefix}/mydir")
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create . pkg/0.1@")
+    client.run("install pkg/0.1@ -g PkgConfigDeps")
+
+    pc_content = client.load("mycomponent.pc")
+    assert "componentdir=${prefix}/mydir" in pc_content


Code review comments:
diff_hunk: @@ -0,0 +1,191 @@
+"""
+    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
+
+from conan.tools.gnu.gnudeps_flags import GnuDepsFlags
+from conans.errors import ConanException
+from conans.util.files import save
+
+
+def _concat_if_not_empty(groups):
+    return " ".join([param for group in groups for param in group if param and param.strip()])
+
+
+def get_target_namespace(req):
+    ret = req.new_cpp_info.get_property("pkg_config_name", "PkgConfigDeps")
+    return ret or req.ref.name
+
+
+def get_component_alias(req, comp_name):
+    if comp_name not in req.new_cpp_info.components:
+        # foo::foo might be referencing the root cppinfo
+        if req.ref.name == comp_name:
+            return get_target_namespace(req)
+        raise ConanException("Component '{name}::{cname}' not found in '{name}' "
+                             "package requirement".format(name=req.ref.name, cname=comp_name))
+    ret = req.new_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
+
+    def _get_require_comp_name(self, pkg_name, req):
+        pkg, comp_name = req.split("::") if "::" in req else (pkg_name, req)
+        req = self._conanfile.dependencies.direct_host[pkg]
+        cmp_name = get_component_alias(req, comp_name)
+        return cmp_name
+
+    def get_components(self, pkg_name, dep):
+        ret = []
+        for comp_name, comp in dep.new_cpp_info.get_sorted_components().items():
+            comp_genname = get_component_alias(dep, comp_name)
+            comp_requires_gennames = []
+            for require in comp.requires:
+                comp_requires_gennames.append(self._get_require_comp_name(pkg_name, require))
+            ret.append((comp_genname, comp, comp_requires_gennames))
+        return ret
+
+    def _get_public_require_deps(self, comp):
+        public_comp_deps = []
+        for require in comp.requires:
+            if "::" in require:  # Points to a component of a different package
+                pkg, cmp_name = require.split("::")
+                req = self._conanfile.dependencies.direct_host[pkg]
+                public_comp_deps.append(
+                    (get_target_namespace(req), get_component_alias(req, cmp_name)))
+            else:  # Points to a component of same package
+                public_comp_deps.append((get_target_namespace(self._conanfile),
+                                         get_component_alias(self._conanfile, require)))
+        return public_comp_deps
+
+    @property
+    def content(self):
+        ret = {}
+        host_req = self._conanfile.dependencies.host
+        for require, dep in host_req.items():
+            pkg_genname = get_target_namespace(dep)
+
+            if dep.new_cpp_info.has_components:
+                components = self.get_components(dep.ref.name, dep)
+                for comp_genname, comp, comp_requires_gennames in components:
path: conan/tools/gnu/pkgconfigdeps.py
text: @user1:
Probably try to unify in the same template, both for components and without components, 

@author:
yes @user1, that's my next step 😉

diff_hunk: @@ -0,0 +1,191 @@
+"""
+    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
+
+from conan.tools.gnu.gnudeps_flags import GnuDepsFlags
+from conans.errors import ConanException
+from conans.util.files import save
+
+
+def _concat_if_not_empty(groups):
+    return " ".join([param for group in groups for param in group if param and param.strip()])
+
+
+def get_target_namespace(req):
+    ret = req.new_cpp_info.get_property("pkg_config_name", "PkgConfigDeps")
+    return ret or req.ref.name
+
+
+def get_component_alias(req, comp_name):
+    if comp_name not in req.new_cpp_info.components:
+        # foo::foo might be referencing the root cppinfo
+        if req.ref.name == comp_name:
+            return get_target_namespace(req)
+        raise ConanException("Component '{name}::{cname}' not found in '{name}' "
+                             "package requirement".format(name=req.ref.name, cname=comp_name))
+    ret = req.new_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
+
+    def _get_require_comp_name(self, pkg_name, req):
+        pkg, comp_name = req.split("::") if "::" in req else (pkg_name, req)
+        req = self._conanfile.dependencies.direct_host[pkg]
+        cmp_name = get_component_alias(req, comp_name)
+        return cmp_name
+
+    def get_components(self, pkg_name, dep):
path: conan/tools/gnu/pkgconfigdeps.py
text: @user1:
Probably private too?