Repository: mesonbuild/meson

Issue:
Using installed static library(with dependencies) with .pc file generated by pkgconfig module via plain dependency(·) call fails
An installed static library with dependencies and an .pc file generated by the pkgconfig module is not usable with a plain `dependency(·)`, but only using `dependency(·, static: true)`. I think in the case that meson only installs a static library the user should not be forced to know to modify the dependency in an meson project that uses this library. This is especially true, when the library can be build in both variants.

I think meson should detect if `import('pkgconfig').generate` gets passed a static library and automatically promote it's dependencies from `Require.private` to `Require` (and similarly for `Libs`). This way meson could generate .pc files that work both with `-Ddefault_library=static` and `-Ddefault_library=shared`. For manually created 'both' passing the shared library to generate would still produce a .pc file that allows the using program to select static or dynamic linking.

Reproducer:

lib/liba.c:
```
void liba_func() {
}
```

lib/libb.c:
```
void liba_func();

void libb_func() {
    liba_func();
}
```

lib/meson.build
```
project('lib', ['c'])

a = library('static-test-a', 'liba.c', install: true)

b = library('static-test-b', 'libb.c', link_with: a, install: true)

import('pkgconfig').generate(
    version: '0.0',
    description: 'XXX',
    filebase: 'static-test-b',
    name: 'XXX',
    libraries: ['-Wl,-rpath-link,' + join_paths(get_option('prefix'), get_option('libdir')), b],
    subdirs: ['.']
)
```

app/app.c:
```
void libb_func();

int main() {
    libb_func();
    return 0;
}
```

app/meson.build:
```
project('app', ['c'])

b = dependency('static-test-b')

executable('app', 'app.c', dependencies : [b])
```

assuming a clean prefix:
```
cd lib
meson.py _build --prefix=$PWD/../prefix
meson.py configure _build -Ddefault_library=static
ninja -C _build/ install

cd ../app
PKG_CONFIG_PATH=$PWD/../prefix/lib/x86_64-linux-gnu/pkgconfig/ meson.py _build
ninja -C _build/
```

This work when omitting the `-Ddefault_library=static` line. But when building a static library it fails with:
```
FAILED: app 
ccache cc  -o app 'app@exe/app.c.o' -Wl,--no-undefined -Wl,--as-needed -L/home/foo/lib/../prefix/lib/x86_64-linux-gnu -Wl,-rpath-link,/home/foo/lib/../prefix/lib/x86_64-linux-gnu -Wl,--start-group -lstatic-test-b -Wl,--end-group  
/home/foo/lib/../prefix/lib/x86_64-linux-gnu/libstatic-test-b.a(libb.c.o): In function `libb_func':
/home/foo/lib/_build/../libb.c:4: undefined reference to `liba_func'
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
```


Discussion:

Resolving patch:
diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py
--- a/mesonbuild/modules/pkgconfig.py
+++ b/mesonbuild/modules/pkgconfig.py
@@ -119,11 +119,15 @@ def _process_libs(self, libs, public):
                         obj.generated_pc = self.name
             elif isinstance(obj, (build.SharedLibrary, build.StaticLibrary)):
                 processed_libs.append(obj)
-                self.add_priv_libs(obj.get_dependencies())
-                self.add_priv_libs(obj.get_external_deps())
                 if public:
                     if not hasattr(obj, 'generated_pc'):
                         obj.generated_pc = self.name
+                if isinstance(obj, build.StaticLibrary) and public:
+                    self.add_pub_libs(obj.get_dependencies())
+                    self.add_pub_libs(obj.get_external_deps())
+                else:
+                    self.add_priv_libs(obj.get_dependencies())
+                    self.add_priv_libs(obj.get_external_deps())
             elif isinstance(obj, str):
                 processed_libs.append(obj)
             else:
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -2827,6 +2827,27 @@ def test_pkgconfig_usage(self):
             self.assertTrue(os.path.isfile(test_exe))
             subprocess.check_call(test_exe, env=myenv)
 
+    @unittest.skipIf(shutil.which('pkg-config') is None, 'Pkg-config not found.')
+    def test_pkgconfig_internal_libraries(self):
+        '''
+        '''
+        with tempfile.TemporaryDirectory() as tempdirname:
+            # build library
+            testdirbase = os.path.join(self.unit_test_dir, '28 pkgconfig use libraries')
+            testdirlib = os.path.join(testdirbase, 'lib')
+            self.init(testdirlib, extra_args=['--prefix=' + tempdirname,
+                                              '--libdir=lib',
+                                              '--default-library=static'], default_args=False)
+            self.build()
+            self.install(use_destdir=False)
+
+            # build user of library
+            pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig')
+            os.environ['PKG_CONFIG_PATH'] = pkg_dir
+            self.new_builddir()
+            self.init(os.path.join(testdirbase, 'app'))
+            self.build()
+
 
 class LinuxArmCrossCompileTests(BasePlatformTests):
     '''


Test patch:
diff --git a/test cases/common/51 pkgconfig-gen/dependencies/meson.build b/test cases/common/51 pkgconfig-gen/dependencies/meson.build
--- a/test cases/common/51 pkgconfig-gen/dependencies/meson.build	
+++ b/test cases/common/51 pkgconfig-gen/dependencies/meson.build	
@@ -5,7 +5,7 @@ pkgg = import('pkgconfig')
 # libmain internally use libinternal and expose libexpose in its API
 exposed_lib = shared_library('libexposed', 'exposed.c')
 internal_lib = shared_library('libinternal', 'internal.c')
-main_lib = static_library('libmain', link_with : [exposed_lib, internal_lib])
+main_lib = both_libraries('libmain', link_with : [exposed_lib, internal_lib])
 
 pkgg.generate(exposed_lib)
 
diff --git a/test cases/unit/28 pkgconfig use libraries/app/app.c b/test cases/unit/28 pkgconfig use libraries/app/app.c
new file mode 100644
--- /dev/null
+++ b/test cases/unit/28 pkgconfig use libraries/app/app.c	
@@ -0,0 +1,6 @@
+void libb_func();
+
+int main() {
+    libb_func();
+    return 0;
+}
diff --git a/test cases/unit/28 pkgconfig use libraries/app/meson.build b/test cases/unit/28 pkgconfig use libraries/app/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/unit/28 pkgconfig use libraries/app/meson.build	
@@ -0,0 +1,5 @@
+project('app', ['c'])
+
+b = dependency('test-b')
+
+executable('app', 'app.c', dependencies : [b])
diff --git a/test cases/unit/28 pkgconfig use libraries/lib/liba.c b/test cases/unit/28 pkgconfig use libraries/lib/liba.c
new file mode 100644
--- /dev/null
+++ b/test cases/unit/28 pkgconfig use libraries/lib/liba.c	
@@ -0,0 +1,2 @@
+void liba_func() {
+}
diff --git a/test cases/unit/28 pkgconfig use libraries/lib/libb.c b/test cases/unit/28 pkgconfig use libraries/lib/libb.c
new file mode 100644
--- /dev/null
+++ b/test cases/unit/28 pkgconfig use libraries/lib/libb.c	
@@ -0,0 +1,5 @@
+void liba_func();
+
+void libb_func() {
+    liba_func();
+}
diff --git a/test cases/unit/28 pkgconfig use libraries/lib/meson.build b/test cases/unit/28 pkgconfig use libraries/lib/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/unit/28 pkgconfig use libraries/lib/meson.build	
@@ -0,0 +1,16 @@
+project('lib', ['c'])
+
+a = library('test-a', 'liba.c', install: true)
+
+b = library('test-b', 'libb.c', link_with: a, install: true)
+
+import('pkgconfig').generate(
+    version: '0.0',
+    description: 'test library',
+    filebase: 'test-b',
+    name: 'test library',
+    libraries: [b],
+    subdirs: ['.']
+)
+
+