Repository: conan-io/conan
Pull request: [PkgConfigDeps] Creates `*.pc` files for `test_requires`
Changelog: Bugfix: `PkgConfigDeps` creates `*.pc` files for `test_requires`.
Docs: omit

Closes: https://github.com/conan-io/conan/issues/11376


Problem statement:
[question] PkgConfigDeps does not generate .pc files test_requires dependencies?
Using Conan version 1.48.1

I have a meson project which is dependent on a test dependency "PkgA"

After executing `conan install .` on my recipe

`.pc` file **not** generated when:

```
    generators = 'PkgConfigDeps'

    def build_requirements(self):
        self.test_requires('PkgA')
```

As a test, I changed it to a normal requires and the `.pc` file is generated:

```
    generators = 'PkgConfigDeps'
    requires = 'PkgA'
```

Is this expected or is there another way for me to get .pc files generated for test dependencies?

Hints from issue discussion:
Hi @phillipcaofph 

Thanks for reporting the issue! And, indeed, it's a bug. This `test_require` should appear there as a normal `*.pc` file.

Commit message: Added test and build requires

Patch to review:
diff --git a/conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py
index f13daba39b2..a29908ec82b 100644
--- a/conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py
+++ b/conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py
@@ -11,8 +11,13 @@ def __init__(self, conanfile):
     def content(self):
         """Get all the *.pc files content"""
         pc_files = {}
+        # Get all the dependencies
         host_req = self._conanfile.dependencies.host
-        for _, dep in host_req.items():
+        build_req = self._conanfile.dependencies.direct_build
+        test_req = self._conanfile.dependencies.test
+        for _, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()):
+            # Require is not used at the moment, but its information could be used,
+            # and will be used in Conan 2.0
             dep_name = str(dep)
             for pc_name, pc_content in get_pc_files_and_content(self._conanfile, dep).items():
                 if pc_name in pc_files:
diff --git a/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py b/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
index 57249221273..f830719ab34 100644
--- a/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
+++ b/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
@@ -1,6 +1,7 @@
 import glob
 import os
 import platform
+import shutil
 import textwrap
 
 import pytest
@@ -621,5 +622,38 @@ def package_info(self):
     client.save({"conanfile.txt": "[requires]\nhello/1.0\n"}, clean_first=True)
     client.run("install . --build=missing -g PkgConfigDeps")
     client.run_command("PKG_CONFIG_PATH=$(pwd) pkg-config --cflags hello")
-    assert r'flag2=\"my flag2\" flag1=\"my flag1\" -DUSER_CONFIG=\"user_config.h\" -DOTHER=\"other.h\"' in client.out
+    assert r'flag2=\"my flag2\" flag1=\"my flag1\" ' \
+           r'-DUSER_CONFIG=\"user_config.h\" -DOTHER=\"other.h\"' in client.out
 
+
+def test_pkgconfigdeps_with_build_and_test_requires():
+    """
+    PkgConfigDeps has to create any build and test requires declared on the recipe.
+
+    Related issue: https://github.com/conan-io/conan/issues/11376
+    """
+    client = TestClient()
+    app_folder = os.path.join(client.current_folder, "app")
+    test_folder = os.path.join(client.current_folder, "test")
+    os.makedirs(app_folder)
+    os.makedirs(test_folder)
+    with client.chdir(app_folder):
+        client.run("new app/1.0 -m cmake_lib")
+        # client.run("new cmake_lib -d name=app -d version=1.0")
+        client.run("create .")
+    with client.chdir(test_folder):
+        client.run("new test/1.0 -m cmake_lib")
+        # client.run("new cmake_lib -d name=test -d version=1.0")
+        client.run("create .")
+    # Create library having build and test requires
+    conanfile = textwrap.dedent(r'''
+        from conans import ConanFile
+        class HelloLib(ConanFile):
+            def build_requirements(self):
+                self.build_requires('app/1.0')
+                self.test_requires('test/1.0')
+        ''')
+    client.save({"conanfile.py": conanfile}, clean_first=True)
+    client.run("install . -g PkgConfigDeps")
+    assert "Description: Conan package: test" in client.load("test.pc")
+    assert "Description: Conan package: app" in client.load("app.pc")


Code review comments:
diff_hunk: @@ -11,8 +11,13 @@ def __init__(self, conanfile):
     def content(self):
         """Get all the *.pc files content"""
         pc_files = {}
+        # Get all the dependencies
         host_req = self._conanfile.dependencies.host
-        for _, dep in host_req.items():
+        build_req = self._conanfile.dependencies.direct_build
+        test_req = self._conanfile.dependencies.test
+        for _, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()):
path: conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py
text: @user1:
is there any risk of pkg-config files from different contexts overriding each other? like `build_requiries(protobuf)` + `requires(protobuf)`

@author:
Yes, but that's a problem that you could even have with other components. Have a look at this PR https://github.com/conan-io/conan/pull/10263 So, you should change that `pkg_config_name`/`pkg_config_aliases` property.

diff_hunk: @@ -621,5 +622,38 @@ def package_info(self):
     client.save({"conanfile.txt": "[requires]\nhello/1.0\n"}, clean_first=True)
     client.run("install . --build=missing -g PkgConfigDeps")
     client.run_command("PKG_CONFIG_PATH=$(pwd) pkg-config --cflags hello")
-    assert r'flag2=\"my flag2\" flag1=\"my flag1\" -DUSER_CONFIG=\"user_config.h\" -DOTHER=\"other.h\"' in client.out
+    assert r'flag2=\"my flag2\" flag1=\"my flag1\" ' \
+           r'-DUSER_CONFIG=\"user_config.h\" -DOTHER=\"other.h\"' in client.out
 
+
+def test_pkgconfigdeps_with_build_and_test_requires():
+    """
+    PkgConfigDeps has to create any build and test requires declared on the recipe.
+
+    Related issue: https://github.com/conan-io/conan/issues/11376
+    """
+    client = TestClient()
+    app_folder = os.path.join(client.current_folder, "app")
+    test_folder = os.path.join(client.current_folder, "test")
+    os.makedirs(app_folder)
+    os.makedirs(test_folder)
+    with client.chdir(app_folder):
+        client.run("new app/1.0 -m cmake_lib")
+        # client.run("new cmake_lib -d name=app -d version=1.0")
+        client.run("create .")
+    with client.chdir(test_folder):
+        client.run("new test/1.0 -m cmake_lib")
+        # client.run("new cmake_lib -d name=test -d version=1.0")
+        client.run("create .")
+    # Create library having build and test requires
+    conanfile = textwrap.dedent(r'''
+        from conans import ConanFile
path: conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
text: @user1:
```suggestion
        from conan import ConanFile
```

diff_hunk: @@ -621,5 +622,38 @@ def package_info(self):
     client.save({"conanfile.txt": "[requires]\nhello/1.0\n"}, clean_first=True)
     client.run("install . --build=missing -g PkgConfigDeps")
     client.run_command("PKG_CONFIG_PATH=$(pwd) pkg-config --cflags hello")
-    assert r'flag2=\"my flag2\" flag1=\"my flag1\" -DUSER_CONFIG=\"user_config.h\" -DOTHER=\"other.h\"' in client.out
+    assert r'flag2=\"my flag2\" flag1=\"my flag1\" ' \
+           r'-DUSER_CONFIG=\"user_config.h\" -DOTHER=\"other.h\"' in client.out
 
+
+def test_pkgconfigdeps_with_build_and_test_requires():
+    """
+    PkgConfigDeps has to create any build and test requires declared on the recipe.
+
+    Related issue: https://github.com/conan-io/conan/issues/11376
+    """
+    client = TestClient()
+    app_folder = os.path.join(client.current_folder, "app")
+    test_folder = os.path.join(client.current_folder, "test")
+    os.makedirs(app_folder)
+    os.makedirs(test_folder)
+    with client.chdir(app_folder):
+        client.run("new app/1.0 -m cmake_lib")
+        # client.run("new cmake_lib -d name=app -d version=1.0")
+        client.run("create .")
+    with client.chdir(test_folder):
path: conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
text: @user1:
```suggestion
    with client.chdir("test"):
```

diff_hunk: @@ -621,5 +622,38 @@ def package_info(self):
     client.save({"conanfile.txt": "[requires]\nhello/1.0\n"}, clean_first=True)
     client.run("install . --build=missing -g PkgConfigDeps")
     client.run_command("PKG_CONFIG_PATH=$(pwd) pkg-config --cflags hello")
-    assert r'flag2=\"my flag2\" flag1=\"my flag1\" -DUSER_CONFIG=\"user_config.h\" -DOTHER=\"other.h\"' in client.out
+    assert r'flag2=\"my flag2\" flag1=\"my flag1\" ' \
+           r'-DUSER_CONFIG=\"user_config.h\" -DOTHER=\"other.h\"' in client.out
 
+
+def test_pkgconfigdeps_with_build_and_test_requires():
+    """
+    PkgConfigDeps has to create any build and test requires declared on the recipe.
+
+    Related issue: https://github.com/conan-io/conan/issues/11376
+    """
+    client = TestClient()
+    app_folder = os.path.join(client.current_folder, "app")
+    test_folder = os.path.join(client.current_folder, "test")
+    os.makedirs(app_folder)
+    os.makedirs(test_folder)
+    with client.chdir(app_folder):
path: conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
text: @user1:
```suggestion
    with client.chdir("app"):
```

diff_hunk: @@ -11,8 +11,13 @@ def __init__(self, conanfile):
     def content(self):
         """Get all the *.pc files content"""
         pc_files = {}
+        # Get all the dependencies
         host_req = self._conanfile.dependencies.host
-        for _, dep in host_req.items():
+        build_req = self._conanfile.dependencies.direct_build
+        test_req = self._conanfile.dependencies.test
+        for _, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()):
path: conan/tools/gnu/pkgconfigdeps/pkgconfigdeps.py
text: @user1:
Not build_requires, only ``test_requires`` should generate .pc files

Build_requires are things like "cmake"

@author:
Ok. I saw CMakeDeps doing that so I only replicated that behavior