diff --git a/docs/markdown/Pkgconfig-module.md b/docs/markdown/Pkgconfig-module.md index 9a34e1478ea4..6aee0c70f5ce 100644 --- a/docs/markdown/Pkgconfig-module.md +++ b/docs/markdown/Pkgconfig-module.md @@ -5,9 +5,12 @@ This module is a simple generator for ## Usage -To use this module, just do: **`pkg = import('pkgconfig')`**. The -following function will then be available as `pkg.generate()`. You -can, of course, replace the name `pkg` with anything else. +```meson +pkg = import('pkgconfig') +bar_dep = dependency('bar') +lib = library('foo', dependencies : [bar]) +pkg.generate(lib) +``` ### pkg.generate() @@ -25,14 +28,13 @@ keyword arguments. - `libraries` a list of built libraries (usually results of shared_library) that the user needs to link against. Arbitrary strings can also be provided and they will be added into the `Libs` field. Since 0.45.0 - dependencies of built libraries will be automatically added to `Libs.private` - field. If a dependency is provided by pkg-config then it will be added in - `Requires.private` instead. Other type of dependency objects can also be passed - and will result in their `link_args` and `compile_args` to be added to `Libs` - and `Cflags` fields. + dependencies of built libraries will be automatically added, see the + [Implicit dependencies](#Implicit_dependencies) section below for the exact + rules. - `libraries_private` list of built libraries or strings to put in the - `Libs.private` field. Since 0.45.0 it can also contain dependency objects, - their `link_args` will be added to `Libs.private`. + `Libs.private` field. Since 0.45.0 dependencies of built libraries will be + automatically added, see the [Implicit dependencies](#Implicit_dependencies) + section below for the exact rules. - `name` the name of this library, used to set the `Name:` field - `subdirs` which subdirs of `include` should be added to the header search path, for example if you install headers into @@ -59,3 +61,38 @@ provided for all required fields of the pc file: - `install_dir` is set to `pkgconfig` folder in the same location than the provided library. - `description` is set to the project's name followed by the library's name. - `name` is set to the library's name. + +### Implicit dependencies + +The exact rules followed to find dependencies that are implicitly added into the +pkg-config file have evolved over time. Here are the rules as of Meson *0.49.0*, +previous versions might have slightly different behaviour. + +- Not found libraries or dependencies are ignored. +- Libraries and dependencies are private by default (i.e. added into + `Requires.private:` or `Libs.private:`) unless they are explicitly added in + `libraries` or `requires` keyword arguments, or is the main library (first + positional argument). +- Libraries and dependencies will be de-duplicated, if they are added in both + public and private (e.g `Requires:` and `Requires.private:`) it will be removed + from the private list. +- Shared libraries (i.e. `shared_library()` and **NOT** `library()`) add only + `-lfoo` into `Libs:` or `Libs.private:` but their dependencies are not pulled. + This is because dependencies are only needed for static link. +- Other libraries (i.e. `static_library()` or `library()`) add `-lfoo` into `Libs:` + or `Libs.private:` and recursively add their dependencies into `Libs.private:` or + `Requires.private:`. +- Dependencies provided by pkg-config are added into `Requires:` or + `Requires.private:`. If a version was specified when declaring that dependency + it will be written into the generated file too. +- The thread dependency (i.e. `dependency('thread')`) adds `-pthread` into + `Libs:` or `Libs.private:`. +- Internal dependencies (i.e. + `declare_dependency(compiler_args : '-DFOO', link_args : '-Wl,something', link_with : foo)`) + add `compiler_args` into `Cflags:` if public, `link_args` and `link_with` into + `Libs:` if public or `Libs.private:` if private. +- Other dependency types add their compiler arguments into `Cflags:` if public, + and linker arguments into `Libs:` if public or `Libs.private:` if private. +- Once a pkg-config file is generated for a library using `pkg.generate(mylib)`, + any subsequent call to `pkg.generate()` where mylib appears, will generate a + `Requires:` or `Requires.private` instead of a `Libs:` or `Libs.private:`. diff --git a/docs/markdown/snippets/pkgconfig_break.md b/docs/markdown/snippets/pkgconfig_break.md new file mode 100644 index 000000000000..49c908dd7aa7 --- /dev/null +++ b/docs/markdown/snippets/pkgconfig_break.md @@ -0,0 +1,34 @@ +## Deprecation warning in pkg-config generator + +All libraries passed to the `libraries` keyword argument of the `generate()` +method used to be associated with that generated pkg-config file. That means +that any subsequent call to `generate()` where those libraries appear would add +the filebase of the `generate()` that first contained them into `Requires:` or +`Requires.private:` field instead of adding an `-l` to `Libs:` or `Libs.private:`. + +This behaviour is now deprecated. The library that should be associated with +the generated pkg-config file should be passed as first positional argument +instead of in the `libraries` keyword argument. The previous behaviour is +maintained but prints a deprecation warning and support for this will be removed +in a future Meson release. If you can not create the needed pkg-config file +without this warning, please file an issue with as much details as possible +about the situation. + +For example this sample will write `Requires: liba` into `libb.pc` but print a +deprecation warning: +```meson +liba = library(...) +pkg.generate(libraries : liba) + +libb = library(...) +pkg.generate(libraries : [liba, libb]) +``` + +It can be fixed by passing `liba` as first positional argument:: +```meson +liba = library(...) +pkg.generate(liba) + +libb = library(...) +pkg.generate(libb, libraries : [liba]) +``` diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 708695af0908..eee3783cfec6 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +import os, types from pathlib import PurePath from .. import build @@ -50,11 +50,24 @@ def add_pub_reqs(self, reqs): def add_priv_reqs(self, reqs): self.priv_reqs += self._process_reqs(reqs) + def _check_generated_pc_deprecation(self, obj): + if hasattr(obj, 'generated_pc_warn'): + mlog.deprecation('Library', mlog.bold(obj.name), 'was passed to the ' + '"libraries" keyword argument of a previous call ' + 'to generate() method instead of first positional ' + 'argument.', 'Adding', mlog.bold(obj.generated_pc), + 'to "Requires" field, but this is a deprecated ' + 'behaviour that will change in a future version ' + 'of Meson. Please report the issue if this ' + 'warning cannot be avoided in your case.', + location=obj.generated_pc_warn) + def _process_reqs(self, reqs): '''Returns string names of requirements''' processed_reqs = [] for obj in mesonlib.listify(reqs, unholder=True): if hasattr(obj, 'generated_pc'): + self._check_generated_pc_deprecation(obj) processed_reqs.append(obj.generated_pc) elif hasattr(obj, 'pcdep'): pcdeps = mesonlib.listify(obj.pcdep) @@ -93,7 +106,8 @@ def _process_libs(self, libs, public): for d in pcdeps: processed_reqs.append(d.name) self.add_version_reqs(d.name, obj.version_reqs) - elif hasattr(obj, 'generated_pc') and obj.generated_pc != self.name: + elif hasattr(obj, 'generated_pc'): + self._check_generated_pc_deprecation(obj) processed_reqs.append(obj.generated_pc) elif isinstance(obj, dependencies.PkgConfigDependency): if obj.found(): @@ -102,6 +116,14 @@ def _process_libs(self, libs, public): elif isinstance(obj, dependencies.ThreadDependency): processed_libs += obj.get_compiler().thread_link_flags(obj.env) processed_cflags += obj.get_compiler().thread_flags(obj.env) + elif isinstance(obj, dependencies.InternalDependency): + if obj.found(): + processed_libs += obj.get_link_args() + processed_cflags += obj.get_compile_args() + if public: + self.add_pub_libs(obj.libraries) + else: + self.add_priv_libs(obj.libraries) elif isinstance(obj, dependencies.Dependency): if obj.found(): processed_libs += obj.get_link_args() @@ -114,14 +136,8 @@ def _process_libs(self, libs, public): # than needed build deps. # See https://bugs.freedesktop.org/show_bug.cgi?id=105572 processed_libs.append(obj) - if public: - if not hasattr(obj, 'generated_pc'): - obj.generated_pc = self.name elif isinstance(obj, (build.SharedLibrary, build.StaticLibrary)): processed_libs.append(obj) - 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(internal=False)) self.add_pub_libs(obj.get_external_deps()) @@ -407,6 +423,22 @@ def parse_variable_list(stringlist): self.generate_pkgconfig_file(state, deps, subdirs, name, description, url, version, pcfile, conflicts, variables) res = build.Data(mesonlib.File(True, state.environment.get_scratch_dir(), pcfile), pkgroot) + # Associate the main library with this generated pc file. If the library + # is used in any subsequent call to the generated, it will generate a + # 'Requires:' or 'Requires.private:'. + # Backward compatibility: We used to set 'generated_pc' on all public + # libraries instead of just the main one. Keep doing that but warn if + # anyone is relying on that deprecated behaviour. + if mainlib: + if not hasattr(mainlib, 'generated_pc'): + mainlib.generated_pc = filebase + else: + mlog.warning('Already generated a pkg-config file for', mlog.bold(mainlib.name)) + for lib in deps.pub_libs: + if not isinstance(lib, str) and not hasattr(lib, 'generated_pc'): + lib.generated_pc = filebase + lib.generated_pc_warn = types.SimpleNamespace(subdir=state.subdir, + lineno=state.current_lineno) return ModuleReturnValue(res, [res]) def initialize(*args, **kwargs): diff --git a/test cases/common/48 pkgconfig-gen/dependencies/custom.c b/test cases/common/48 pkgconfig-gen/dependencies/custom.c new file mode 100644 index 000000000000..2cc5651dd594 --- /dev/null +++ b/test cases/common/48 pkgconfig-gen/dependencies/custom.c @@ -0,0 +1,3 @@ +int custom_function() { + return 42; +} diff --git a/test cases/common/48 pkgconfig-gen/dependencies/meson.build b/test cases/common/48 pkgconfig-gen/dependencies/meson.build index 047e7e7ebf77..2dad3932a3e8 100644 --- a/test cases/common/48 pkgconfig-gen/dependencies/meson.build +++ b/test cases/common/48 pkgconfig-gen/dependencies/meson.build @@ -6,6 +6,7 @@ pkgg = import('pkgconfig') exposed_lib = shared_library('libexposed', 'exposed.c') internal_lib = shared_library('libinternal', 'internal.c') main_lib = both_libraries('libmain', link_with : [exposed_lib, internal_lib]) +custom_lib = shared_library('custom', 'custom.c') pkgg.generate(exposed_lib) @@ -14,7 +15,7 @@ pc_dep = dependency('libfoo', version : '>=1.0') pc_dep_dup = dependency('libfoo', version : '>= 1.0') notfound_dep = dependency('notfound', required : false) threads_dep = dependency('threads') -custom_dep = declare_dependency(link_args : ['-lcustom'], compile_args : ['-DCUSTOM']) +custom_dep = declare_dependency(link_with : custom_lib, compile_args : ['-DCUSTOM']) custom2_dep = declare_dependency(link_args : ['-lcustom2'], compile_args : ['-DCUSTOM2']) # Generate a PC file: