Repository: conan-io/conan

Issue:
Invalid formatting of libdir/includedir in pkg_config generator [bug]
Version: conan-1.2.3
OS: Linux

https://github.com/conan-io/conan/blob/ac25c8994878706bac23efd009ae74bc294c1add/conans/client/generators/pkg_config.py#L28-L36

`pkg_config` generator doesn't check if libdir or includedir are absolute paths which leads to the following (invalid) `.pc` being generated for `boost_math/1.66.0@bincrafters/stable`:

```
prefix=/home/user/.conan/data/boost_math/1.66.0/bincrafters/stable/package/937af278b1c9d86290dddaf1322dc99fb7d75862
libdir=${prefix}//home/user/.conan/data/boost_level8group/1.66.0/bincrafters/stable/package/afc149253b16bde7d64e527e0dbfa27b203330c8/math/lib
includedir=${prefix}//home/user/.conan/data/boost_level8group/1.66.0/bincrafters/stable/package/afc149253b16bde7d64e527e0dbfa27b203330c8/math/include
...
```


Discussion:
Hi @coffee-lord !

Thanks you so much fot the detailed report. Seems it is a bug with absolute paths as you said, mainly affecting recipes with `short_paths=True` like boost ones.

We will try to fix it for the next release.
Thanks!

Resolving patch:
diff --git a/conans/client/generators/pkg_config.py b/conans/client/generators/pkg_config.py
--- a/conans/client/generators/pkg_config.py
+++ b/conans/client/generators/pkg_config.py
@@ -13,7 +13,7 @@
 Name: my-project
 Description: Some brief but informative description
 Version: 1.2.3
-Libs: -L${libdir} -lmy-project-1 -linkerflag
+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
@@ -42,16 +42,16 @@ def single_pc_file_contents(self, name, cpp_info):
         lines = ['prefix=%s' % prefix_path]
 
         libdir_vars = []
-        varname = "libdir"
-        dir_lines = _generate_dir_lines(prefix_path, varname, cpp_info.libdirs)
-        libdir_vars.append(varname)
-        lines.extend(dir_lines)
+        dir_lines, varnames = _generate_dir_lines(prefix_path, "libdir", cpp_info.libdirs)
+        if dir_lines:
+            libdir_vars = varnames
+            lines.extend(dir_lines)
 
         includedir_vars = []
-        varname = "includedir"
-        dir_lines = _generate_dir_lines(prefix_path, varname, cpp_info.includedirs)
-        includedir_vars.append(varname)
-        lines.extend(dir_lines)
+        dir_lines, varnames = _generate_dir_lines(prefix_path, "includedir", cpp_info.includedirs)
+        if dir_lines:
+            includedir_vars = varnames
+            lines.extend(dir_lines)
 
         lines.append("")
         lines.append("Name: %s" % name)
@@ -63,7 +63,7 @@ def single_pc_file_contents(self, name, cpp_info):
         shared_flags = cpp_info.sharedlinkflags + cpp_info.exelinkflags
         the_os = (self.conanfile.settings.get_safe("os_build") or
                   self.conanfile.settings.get_safe("os"))
-        rpaths = rpath_flags(the_os, self.compiler, self._deps_build_info.lib_paths)
+        rpaths = rpath_flags(the_os, self.compiler, ["${%s}" % libdir for libdir in libdir_vars])
         lines.append("Libs: %s" % _concat_if_not_empty([libdirs_flags,
                                                         libnames_flags,
                                                         shared_flags,
@@ -88,6 +88,7 @@ def _concat_if_not_empty(groups):
 
 def _generate_dir_lines(prefix_path, varname, dirs):
     lines = []
+    varnames = []
     for i, directory in enumerate(dirs):
         directory = os.path.normpath(directory).replace("\\", "/")
         varname = varname if i == 0 else "%s%d" % (varname, (i + 2))
@@ -98,4 +99,5 @@ def _generate_dir_lines(prefix_path, varname, dirs):
             prefix = "${prefix}/"
             directory = os.path.relpath(directory, prefix_path)
         lines.append("%s=%s%s" % (varname, prefix, directory))
-    return lines
+        varnames.append(varname)
+    return lines, varnames


Test patch:
diff --git a/conans/test/generators/pkg_config_test.py b/conans/test/generators/pkg_config_test.py
--- a/conans/test/generators/pkg_config_test.py
+++ b/conans/test/generators/pkg_config_test.py
@@ -1,11 +1,12 @@
 import os
+import platform
 import unittest
 
 from conans.client.generators.pkg_config import PkgConfigGenerator
 from conans.model.settings import Settings
 from conans.model.conan_file import ConanFile
 from conans.model.build_info import CppInfo
-from conans.model.ref import ConanFileReference, PackageReference
+from conans.model.ref import ConanFileReference
 from conans.test.utils.tools import TestClient
 from conans.util.files import load
 
@@ -91,16 +92,21 @@ def package_info(self):
         pc_path = os.path.join(client.current_folder, "MyLib.pc")
         self.assertTrue(os.path.exists(pc_path))
         pc_content = load(pc_path)
-        self.assertEquals("\n".join(pc_content.splitlines()[1:]),
-                          """libdir=/my_absoulte_path/fake/mylib/lib
+        expected_rpaths = ""
+        if platform.system() == "Linux":
+            expected_rpaths = ' -Wl,-rpath="${libdir}" -Wl,-rpath="${libdir3}"'
+        elif platform.system() == "Darwin":
+            expected_rpaths = ' -Wl,-rpath,"${libdir}" -Wl,-rpath,"${libdir3}"'
+        expected_content = """libdir=/my_absoulte_path/fake/mylib/lib
 libdir3=${prefix}/lib2
 includedir=/my_absoulte_path/fake/mylib/include
 
 Name: MyLib
 Description: Conan package: MyLib
 Version: 0.1
-Libs: -L${libdir}
-Cflags: -I${includedir}""")
+Libs: -L${libdir} -L${libdir3}%s
+Cflags: -I${includedir}""" % expected_rpaths
+        self.assertEquals("\n".join(pc_content.splitlines()[1:]), expected_content)
 
         def assert_is_abs(path):
             self.assertTrue(os.path.isabs(path))
@@ -115,6 +121,37 @@ def assert_is_abs(path):
             elif line.startswith("libdir3="):
                 self.assertIn("${prefix}/lib2", line)
 
+    def pkg_config_without_libdir_test(self):
+        conanfile = """
+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 = []
+"""
+        client = TestClient()
+        client.save({"conanfile.py": conanfile})
+        client.run("create . danimtb/testing")
+        client.run("install MyLib/0.1@danimtb/testing -g pkg_config")
+
+        pc_path = os.path.join(client.current_folder, "MyLib.pc")
+        self.assertTrue(os.path.exists(pc_path))
+        pc_content = load(pc_path)
+        self.assertEquals("\n".join(pc_content.splitlines()[1:]),
+                          """
+Name: MyLib
+Description: Conan package: MyLib
+Version: 0.1
+Libs: 
+Cflags: """)
+
     def pkg_config_rpaths_test(self):
         # rpath flags are only generated for gcc and clang
         profile = """
@@ -149,8 +186,4 @@ def package_info(self):
         pc_path = os.path.join(client.current_folder, "MyLib.pc")
         self.assertTrue(os.path.exists(pc_path))
         pc_content = load(pc_path)
-        conan_ref = ConanFileReference.loads("MyLib/0.1@danimtb/testing")
-        package_dir = client.paths.package(
-            PackageReference(conan_ref, "ea61221ce137c6bac82570628201a6e1ecb34adc"))
-        correct_path = os.path.join(package_dir, "lib").replace("\\", "/")
-        self.assertIn("-Wl,-rpath=\"%s\"" % correct_path, pc_content)
+        self.assertIn("-Wl,-rpath=\"${libdir}\"", pc_content)
diff --git a/conans/test/util/pkg_config_test.py b/conans/test/util/pkg_config_test.py
--- a/conans/test/util/pkg_config_test.py
+++ b/conans/test/util/pkg_config_test.py
@@ -79,3 +79,28 @@ def test_define_prefix(self):
 
             self.assertEquals(pkg_config.variables['prefix'], '/home/conan')
         os.unlink(filename)
+
+    def rpaths_libs_test(self):
+        if platform.system() == "Windows":
+            return
+        pc_content = """prefix=/my_prefix/path
+libdir=/my_absoulte_path/fake/mylib/lib
+libdir3=${prefix}/lib2
+includedir=/my_absoulte_path/fake/mylib/include
+
+Name: MyLib
+Description: Conan package: MyLib
+Version: 0.1
+Libs: -L${libdir} -L${libdir3} -Wl,-rpath="${libdir}" -Wl,-rpath="${libdir3}"
+Cflags: -I${includedir}"""
+        tmp_dir = temp_folder()
+        filename = os.path.join(tmp_dir, 'MyLib.pc')
+        with open(filename, 'w') as f:
+            f.write(pc_content)
+        with environment_append({'PKG_CONFIG_PATH': tmp_dir}):
+            expected = ("-L/my_absoulte_path/fake/mylib/lib "
+                        "-L/my_prefix/path/lib2 "
+                        "-Wl,-rpath=/my_absoulte_path/fake/mylib/lib "
+                        "-Wl,-rpath=/my_prefix/path/lib2")
+            pkg_config = PkgConfig("MyLib")
+            self.assertIn(expected, " ".join(lib for lib in pkg_config.libs))