Repository: conan-io/conan

Issue:
[feature] Add bindirs to pkgconfig generator
### What is your suggestion?

pkg-config generator is useful to use conan dependencies in meson build system, especially on Windows. However, meson has no way of knowing where the DLL files are located, since the linkage is done on the import libraries.

Providing a `bindir` variable to the generated .pc file would allow meson (an possibly other systems) to query pkg-config for the location of the shared library binaries.

### Have you read the CONTRIBUTING guide?

- [X] I've read the CONTRIBUTING guide

Discussion:

Patch:
diff --git a/conan/tools/gnu/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps.py
index 00c9bf0e4..2c2723885 100644
--- a/conan/tools/gnu/pkgconfigdeps.py
+++ b/conan/tools/gnu/pkgconfigdeps.py
@@ -97,7 +97,7 @@ class _PCContentGenerator:
     template = textwrap.dedent("""\
         {%- macro get_libs(libdirs, cpp_info, gnudeps_flags) -%}
         {%- for _ in libdirs -%}
-        {{ '-L"${libdir%s}"' % loop.index + " " }}
+        {{ '-L"${libdir%s}"' % (loop.index0 or "") + " " }}
         {%- endfor -%}
         {%- for sys_lib in (cpp_info.libs + cpp_info.system_libs) -%}
         {{ "-l%s" % sys_lib + " " }}
@@ -112,7 +112,7 @@ class _PCContentGenerator:
 
         {%- macro get_cflags(includedirs, cxxflags, cflags, defines) -%}
         {%- for _ in includedirs -%}
-        {{ '-I"${includedir%s}"' % loop.index + " " }}
+        {{ '-I"${includedir%s}"' % (loop.index0 or "") + " " }}
         {%- endfor -%}
         {%- for cxxflag in cxxflags -%}
         {{ cxxflag + " " }}
@@ -127,10 +127,13 @@ class _PCContentGenerator:
 
         prefix={{ prefix_path }}
         {% for path in libdirs %}
-        {{ "libdir{}={}".format(loop.index, path) }}
+        {{ "libdir{}={}".format((loop.index0 or ""), path) }}
         {% endfor %}
         {% for path in includedirs %}
-        {{ "includedir%d=%s" % (loop.index, path) }}
+        {{ "includedir%s=%s" % ((loop.index0 or ""), path) }}
+        {% endfor %}
+        {% for path in bindirs %}
+        {{ "bindir{}={}".format((loop.index0 or ""), path) }}
         {% endfor %}
         {% if pkg_config_custom_content %}
         # Custom PC content
@@ -171,12 +174,14 @@ class _PCContentGenerator:
         prefix_path = root_folder.replace("\\", "/")
         libdirs = _get_formatted_dirs(info.cpp_info.libdirs, prefix_path)
         includedirs = _get_formatted_dirs(info.cpp_info.includedirs, prefix_path)
+        bindirs = _get_formatted_dirs(info.cpp_info.bindirs, prefix_path)
         custom_content = info.cpp_info.get_property("pkg_config_custom_content")
 
         context = {
             "prefix_path": prefix_path,
             "libdirs": libdirs,
             "includedirs": includedirs,
+            "bindirs": bindirs,
             "pkg_config_custom_content": custom_content,
             "name": info.name,
             "description": info.description,
@@ -308,7 +313,7 @@ class _PCGenerator:
         Get all the PC files and contents for any dependency:
 
         * If the given dependency does not have components:
-            The PC file will be the depency one.
+            The PC file will be the dependency one.
 
         * If the given dependency has components:
             The PC files will be saved in this order:
diff --git a/conans/search/search.py b/conans/search/search.py
index 01e09bbe1..6af6217dc 100644
--- a/conans/search/search.py
+++ b/conans/search/search.py
@@ -61,7 +61,6 @@ def _evaluate(prop_name, prop_value, binary_info):
     if not prop_name.startswith("options."):
         return compatible_prop(info_settings.get(prop_name, None), prop_value)
     else:
-        prop_name = prop_name[len("options."):]
         return compatible_prop(info_options.get(prop_name, None), prop_value)
 
 


Test patch:
diff --git a/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py b/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
index f58c5e95b..55f3db759 100644
--- a/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
+++ b/conans/test/functional/toolchains/gnu/test_pkgconfigdeps.py
@@ -1,4 +1,5 @@
 import platform
+import sys
 import textwrap
 
 import pytest
@@ -8,7 +9,7 @@ from conans.test.utils.tools import TestClient
 
 @pytest.mark.skipif(platform.system() == "Windows", reason="Needs pkg-config")
 @pytest.mark.tool("pkg_config")
-def test_pkg_configdeps_definitions_escape():
+def test_pkgconfigdeps_definitions_escape():
     client = TestClient(path_with_spaces=False)
     conanfile = textwrap.dedent(r'''
         from conan import ConanFile
@@ -54,3 +55,61 @@ def test_pkgconfigdeps_with_test_requires():
     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")
+
+
+@pytest.mark.skipif(sys.version_info.minor < 7, reason="Meson 1.1.x version needs Python >= 3.7")
+@pytest.mark.skipif(platform.system() != "Windows", reason="It makes sense only for Windows")
+@pytest.mark.tool("meson")  # https://github.com/mesonbuild/meson/pull/11649 is part of Meson 1.1.0
+@pytest.mark.tool("pkg_config")
+def test_pkgconfigdeps_bindir_and_meson():
+    """
+    This test checks that the field bindir introduced by PkgConfigDeps is useful for Windows
+    OS and shared=True where all the DLL's files are located by default there.
+
+    Basically, Meson (version >= 1.1.0) reads from the *.pc files the bindir variable if exists, and
+    uses that variable to link with if SHARED libraries.
+
+    Issue: https://github.com/conan-io/conan/issues/13532
+    """
+    client = TestClient()
+    client.run("new meson_lib -d name=hello -d version=1.0")
+    client.run("create . -tf \"\" -o *:shared=True")
+    test_meson_build = textwrap.dedent("""
+    project('Testhello', 'cpp')
+    hello = dependency('hello', version : '>=1.0')
+    example = executable('example', 'src/example.cpp', dependencies: hello)
+    test('./src/example', example)
+    """)
+    test_conanfile = textwrap.dedent("""
+    import os
+    from conan import ConanFile
+    from conan.tools.build import can_run
+    from conan.tools.meson import Meson
+    from conan.tools.layout import basic_layout
+
+    class helloTestConan(ConanFile):
+        settings = "os", "compiler", "build_type", "arch"
+        generators = "PkgConfigDeps", "MesonToolchain"
+        options = {"shared": [True, False], "fPIC": [True, False]}
+        default_options = {"shared": True, "fPIC": True}
+
+        def requirements(self):
+            self.requires("hello/1.0")
+
+        def build(self):
+            meson = Meson(self)
+            meson.configure()
+            meson.build()
+
+        def layout(self):
+            basic_layout(self)
+    """)
+    client.save({
+        "test_package/conanfile.py": test_conanfile,
+        "test_package/meson.build": test_meson_build
+    })
+    client.run("build test_package/conanfile.py -o *:shared=True")
+    # Important: Only Meson >= 1.1.0 brings this capability
+    # Executing directly "meson test" fails if the bindir field does not exist
+    client.run_command("meson test -C test_package/build-release")
+    assert "1/1 ./src/example OK"
diff --git a/conans/test/integration/command_v2/list_test.py b/conans/test/integration/command_v2/list_test.py
index 87f5581a6..a9367e62b 100644
--- a/conans/test/integration/command_v2/list_test.py
+++ b/conans/test/integration/command_v2/list_test.py
@@ -584,37 +584,6 @@ def test_list_prefs_query_custom_settings():
     assert "newsetting.subsetting: 2" not in c.out
 
 
-def test_list_query_options():
-    """
-    Make sure query works for custom settings
-    https://github.com/conan-io/conan/issues/13617
-    """
-    c = TestClient(default_server_user=True)
-    c.save({"conanfile.py": GenConanfile("pkg", "1.0").with_option("myoption", [1, 2, 3])})
-    c.run("create . -o myoption=1")
-    c.run("create . -o myoption=2")
-    c.run("create . -o myoption=3")
-
-    c.run("list pkg/1.0:* -p options.myoption=1")
-    assert "myoption: 1" in c.out
-    assert "myoption: 2" not in c.out
-    assert "myoption: 3" not in c.out
-    c.run("list pkg/1.0:* -p options.myoption=2")
-    assert "myoption: 1" not in c.out
-    assert "myoption: 2" in c.out
-    assert "myoption: 3" not in c.out
-
-    c.run("upload * -r=default -c")
-    c.run("list pkg/1.0:* -p options.myoption=1 -r=default")
-    assert "myoption: 1" in c.out
-    assert "myoption: 2" not in c.out
-    assert "myoption: 3" not in c.out
-    c.run("list pkg/1.0:* -p options.myoption=2 -r=default")
-    assert "myoption: 1" not in c.out
-    assert "myoption: 2" in c.out
-    assert "myoption: 3" not in c.out
-
-
 class TestListNoUserChannel:
     def test_no_user_channel(self):
         c = TestClient(default_server_user=True)
diff --git a/conans/test/integration/layout/test_layout_paths.py b/conans/test/integration/layout/test_layout_paths.py
index 9dc72783b..d5668af1e 100644
--- a/conans/test/integration/layout/test_layout_paths.py
+++ b/conans/test/integration/layout/test_layout_paths.py
@@ -34,7 +34,7 @@ def test_editable_layout_paths():
 
     assert 'set(dep_INCLUDE_DIRS_RELEASE "${dep_PACKAGE_FOLDER_RELEASE}/include")' in data
     pc = c.load("pkg/dep.pc")
-    assert "includedir1=${prefix}/include" in pc
+    assert "includedir=${prefix}/include" in pc
     xcode = c.load("pkg/conan_dep_dep_release_x86_64.xcconfig")
     dep_path = os.path.join(c.current_folder, "dep")
     assert f"PACKAGE_ROOT_dep[config=Release][arch=x86_64][sdk=*] = {dep_path}" in xcode
diff --git a/conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py b/conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py
index 91fc8f647..69fac1f52 100644
--- a/conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py
+++ b/conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py
@@ -31,9 +31,10 @@ def test_pkg_config_dirs():
                 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")
+                lib_dir1 = os.path.join(self.package_folder, "lib2")
                 self.cpp_info.includedirs = [include_dir]
-                self.cpp_info.libdirs = [lib_dir, lib_dir2]
+                self.cpp_info.libdirs = [lib_dir, lib_dir1]
+                self.cpp_info.bindirs = ["mybin"]
         """)
     client = TestClient()
     client.save({"conanfile.py": conanfile})
@@ -46,20 +47,22 @@ def test_pkg_config_dirs():
     assert 'Name: mylib' in pc_content
     assert 'Description: Conan package: mylib' in pc_content
     assert 'Version: 0.1' in pc_content
-    assert 'Libs: -L"${libdir1}" -L"${libdir2}"' in pc_content
-    assert 'Cflags: -I"${includedir1}"' in pc_content
+    assert 'Libs: -L"${libdir}" -L"${libdir1}"' in pc_content
+    assert 'Cflags: -I"${includedir}"' in pc_content
+    # https://github.com/conan-io/conan/pull/13623
+    assert 'bindir=${prefix}/mybin' in pc_content
 
     def assert_is_abs(path):
         assert os.path.isabs(path) is True
 
     for line in pc_content.splitlines():
-        if line.startswith("includedir1="):
-            assert_is_abs(line[len("includedir1="):])
+        if line.startswith("includedir="):
+            assert_is_abs(line[len("includedir="):])
             assert line.endswith("include")
-        elif line.startswith("libdir1="):
-            assert_is_abs(line[len("libdir1="):])
+        elif line.startswith("libdir="):
+            assert_is_abs(line[len("libdir="):])
             assert line.endswith("lib")
-        elif line.startswith("libdir2="):
+        elif line.startswith("libdir1="):
             assert "${prefix}/lib2" in line
 
 
@@ -120,7 +123,7 @@ def test_system_libs():
     client.run("install --requires=mylib/0.1@ -g PkgConfigDeps")
 
     pc_content = client.load("mylib.pc")
-    assert 'Libs: -L"${libdir1}" -lmylib1 -lmylib2 -lsystem_lib1 -lsystem_lib2' in pc_content
+    assert 'Libs: -L"${libdir}" -lmylib1 -lmylib2 -lsystem_lib1 -lsystem_lib2' in pc_content
 
 
 def test_multiple_include():
@@ -145,13 +148,13 @@ def test_multiple_include():
     client.run("install --requires=pkg/0.1@ -g PkgConfigDeps")
 
     pc_content = client.load("pkg.pc")
-    assert "includedir1=${prefix}/inc1" in pc_content
-    assert "includedir2=${prefix}/inc2" in pc_content
-    assert "includedir3=${prefix}/inc3/foo" in pc_content
-    assert "libdir1=${prefix}/lib1" in pc_content
-    assert "libdir2=${prefix}/lib2" in pc_content
-    assert 'Libs: -L"${libdir1}" -L"${libdir2}"' in pc_content
-    assert 'Cflags: -I"${includedir1}" -I"${includedir2}" -I"${includedir3}"' in pc_content
+    assert "includedir=${prefix}/inc1" in pc_content
+    assert "includedir1=${prefix}/inc2" in pc_content
+    assert "includedir2=${prefix}/inc3/foo" in pc_content
+    assert "libdir=${prefix}/lib1" in pc_content
+    assert "libdir1=${prefix}/lib2" in pc_content
+    assert 'Libs: -L"${libdir}" -L"${libdir1}"' in pc_content
+    assert 'Cflags: -I"${includedir}" -I"${includedir1}" -I"${includedir2}"' in pc_content
 
 
 def test_custom_content():
@@ -184,7 +187,7 @@ def test_custom_content():
     client.run("install --requires=pkg/0.1@ -g PkgConfigDeps")
 
     pc_content = client.load("pkg.pc")
-    assert "libdir1=${prefix}/lib" in pc_content
+    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
@@ -568,9 +571,9 @@ def test_with_editable_layout():
     with client.chdir("pkg"):
         client.run("install . -g PkgConfigDeps")
         pc = client.load("dep.pc")
-        assert 'Libs: -L"${libdir1}" -lmylib' in pc
-        assert 'includedir1=' in pc
-        assert 'Cflags: -I"${includedir1}"' in pc
+        assert 'Libs: -L"${libdir}" -lmylib' in pc
+        assert 'includedir=' in pc
+        assert 'Cflags: -I"${includedir}"' in pc
 
 
 def test_tool_requires():