Repository: mesonbuild/meson

Issue:
pkgconfig module should allow nullification of some default variables.
Not all projects require the prefix, libdir, and includedir variables in their pkgconfig files.
In addition, it should be possbile to remove the `Cflags: -I${includedir}` line either entirely
or in order to have your own variables there, minus the `-I${includedir}` addition.
For example's sake, this is the entireity of the xorg-libinput.pc file:
```
sdkdir=/usr/include/xorg

Name: xorg-libinput
Description: X.Org libinput input driver.
Version: 0.28.0
Cflags: -I${sdkdir}
```


Discussion:
Something like this might be added but for this particular case it is not necessary. Using the builtin pkg-config generator is easy and it even works better than writing the file manually. That sample file is not relocatable, that is, if you install it somewhere else (say, to '/usr/local` for whatever reason) then you need to change `sdkdir` manually.

The pkg-config file creator takes care of all that automatically. Basically you only need to add a keyword argument `subdirs: 'xorg'` and the rest will take care of itself.
@jpakkane that may be so, but I'm trying to 1:1 port this and for whatever reason,
sdkdir is what they use.
One reason to have this is for architecture-independent pkg-config files in projects which also have architecture-dependent outputs.

For example, Weston installs libweston-N.pc which contains compiled DSOs and architecture-dependent includes. It also installs libweston-protocols-N.pc, which only contains architecture-independent XML files. Debian would like to package libweston-protocols as an architecture-independent package, but it's complaining because it includes architecture-dependent paths, particularly `libdir`.

Resolving patch:
diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py
--- a/mesonbuild/modules/pkgconfig.py
+++ b/mesonbuild/modules/pkgconfig.py
@@ -266,7 +266,7 @@ def _make_relative(self, prefix, subdir):
 
     def generate_pkgconfig_file(self, state, deps, subdirs, name, description,
                                 url, version, pcfile, conflicts, variables,
-                                uninstalled=False):
+                                uninstalled=False, dataonly=False):
         deps.remove_dups()
         coredata = state.environment.get_coredata()
         if uninstalled:
@@ -283,12 +283,13 @@ def generate_pkgconfig_file(self, state, deps, subdirs, name, description,
             incdir = PurePath(coredata.get_builtin_option('includedir'))
         fname = os.path.join(outdir, pcfile)
         with open(fname, 'w', encoding='utf-8') as ofile:
-            ofile.write('prefix={}\n'.format(self._escape(prefix)))
-            if uninstalled:
-                ofile.write('srcdir={}\n'.format(self._escape(srcdir)))
-            else:
-                ofile.write('libdir={}\n'.format(self._escape('${prefix}' / libdir)))
-                ofile.write('includedir={}\n'.format(self._escape('${prefix}' / incdir)))
+            if not dataonly:
+                ofile.write('prefix={}\n'.format(self._escape(prefix)))
+                if uninstalled:
+                    ofile.write('srcdir={}\n'.format(self._escape(srcdir)))
+                else:
+                    ofile.write('libdir={}\n'.format(self._escape('${prefix}' / libdir)))
+                    ofile.write('includedir={}\n'.format(self._escape('${prefix}' / incdir)))
             if variables:
                 ofile.write('\n')
             for k, v in variables:
@@ -370,27 +371,28 @@ def generate_uninstalled_cflags(libs):
                 ofile.write('Libs: {}\n'.format(' '.join(generate_libs_flags(deps.pub_libs))))
             if len(deps.priv_libs) > 0:
                 ofile.write('Libs.private: {}\n'.format(' '.join(generate_libs_flags(deps.priv_libs))))
+
+            def generate_compiler_flags():
+                cflags_buf = []
+                for f in deps.cflags:
+                    cflags_buf.append(self._escape(f))
+                return cflags_buf
+
+            cflags = generate_compiler_flags()
             ofile.write('Cflags:')
             if uninstalled:
                 ofile.write(' '.join(generate_uninstalled_cflags(deps.pub_libs + deps.priv_libs)))
-            else:
-                for h in subdirs:
-                    ofile.write(' ')
-                    if h == '.':
-                        ofile.write('-I${includedir}')
-                    else:
-                        ofile.write(self._escape(PurePath('-I${includedir}') / h))
-            for f in deps.cflags:
-                ofile.write(' ')
-                ofile.write(self._escape(f))
-            ofile.write('\n')
+            elif not dataonly and cflags:
+                ofile.write('{}\n'.format(' '.join(cflags)))
 
     @FeatureNewKwargs('pkgconfig.generate', '0.54.0', ['uninstalled_variables'])
     @FeatureNewKwargs('pkgconfig.generate', '0.42.0', ['extra_cflags'])
     @FeatureNewKwargs('pkgconfig.generate', '0.41.0', ['variables'])
+    @FeatureNewKwargs('pkgconfig.generate', '0.54.0', ['dataonly'])
     @permittedKwargs({'libraries', 'version', 'name', 'description', 'filebase',
                       'subdirs', 'requires', 'requires_private', 'libraries_private',
-                      'install_dir', 'extra_cflags', 'variables', 'url', 'd_module_versions'})
+                      'install_dir', 'extra_cflags', 'variables', 'url', 'd_module_versions',
+                      'dataonly'})
     def generate(self, state, args, kwargs):
         if 'variables' in kwargs:
             FeatureNew('custom pkgconfig variables', '0.41.0').use(state.subproject)
@@ -399,6 +401,7 @@ def generate(self, state, args, kwargs):
         default_description = None
         default_name = None
         mainlib = None
+        default_subdirs = ['.']
         if not args and 'version' not in kwargs:
             FeatureNew('pkgconfig.generate implicit version keyword', '0.46.0').use(state.subproject)
         elif len(args) == 1:
@@ -414,7 +417,14 @@ def generate(self, state, args, kwargs):
         elif len(args) > 1:
             raise mesonlib.MesonException('Too many positional arguments passed to Pkgconfig_gen.')
 
-        subdirs = mesonlib.stringlistify(kwargs.get('subdirs', ['.']))
+        dataonly = kwargs.get('dataonly', False)
+        if dataonly:
+            default_subdirs = []
+            blocked_vars = ['libraries', 'libraries_private', 'require_private', 'extra_cflags', 'subdirs']
+            if len(set(kwargs) & set(blocked_vars)) > 0:
+                raise mesonlib.MesonException('Cannot combine dataonly with any of {}'.format(blocked_vars))
+
+        subdirs = mesonlib.stringlistify(kwargs.get('subdirs', default_subdirs))
         version = kwargs.get('version', default_version)
         if not isinstance(version, str):
             raise mesonlib.MesonException('Version must be specified.')
@@ -440,6 +450,11 @@ def generate(self, state, args, kwargs):
             libraries = [mainlib] + libraries
 
         deps = DependenciesHelper(state, filebase)
+        for d in subdirs:
+            if d == '.':
+                deps.add_cflags(['-I${includedir}'])
+            else:
+                deps.add_cflags(self._escape(PurePath('-I${includedir}') / d))
         deps.add_pub_libs(libraries)
         deps.add_priv_libs(kwargs.get('libraries_private', []))
         deps.add_pub_reqs(kwargs.get('requires', []))
@@ -488,13 +503,14 @@ def parse_variable_list(stringlist):
         if not isinstance(pkgroot, str):
             raise mesonlib.MesonException('Install_dir must be a string.')
         self.generate_pkgconfig_file(state, deps, subdirs, name, description, url,
-                                     version, pcfile, conflicts, variables)
+                                     version, pcfile, conflicts, variables,
+                                     False, dataonly)
         res = build.Data(mesonlib.File(True, state.environment.get_scratch_dir(), pcfile), pkgroot)
         variables = parse_variable_list(mesonlib.stringlistify(kwargs.get('uninstalled_variables', [])))
         pcfile = filebase + '-uninstalled.pc'
         self.generate_pkgconfig_file(state, deps, subdirs, name, description, url,
                                      version, pcfile, conflicts, variables,
-                                     uninstalled=True)
+                                     uninstalled=True, dataonly=dataonly)
         # 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:'.
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -4968,6 +4968,11 @@ def test_pkgconfig_gen(self):
         self.assertEqual(foo_dep.get_pkgconfig_variable('foo', {}), 'bar')
         self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir', {}), '/usr/data')
 
+        libhello_nolib = PkgConfigDependency('libhello_nolib', env, kwargs)
+        self.assertTrue(libhello_nolib.found())
+        self.assertEqual(libhello_nolib.get_link_args(), [])
+        self.assertEqual(libhello_nolib.get_compile_args(), [])
+
     def test_pkgconfig_gen_deps(self):
         '''
         Test that generated pkg-config files correctly handle dependencies


Test patch:
diff --git a/test cases/common/47 pkgconfig-gen/installed_files.txt b/test cases/common/47 pkgconfig-gen/installed_files.txt
--- a/test cases/common/47 pkgconfig-gen/installed_files.txt	
+++ b/test cases/common/47 pkgconfig-gen/installed_files.txt	
@@ -2,3 +2,4 @@ usr/include/simple.h
 usr/lib/pkgconfig/simple.pc
 usr/lib/pkgconfig/libfoo.pc
 usr/lib/pkgconfig/libhello.pc
+usr/lib/pkgconfig/libhello_nolib.pc
\ No newline at end of file
diff --git a/test cases/common/47 pkgconfig-gen/meson.build b/test cases/common/47 pkgconfig-gen/meson.build
--- a/test cases/common/47 pkgconfig-gen/meson.build	
+++ b/test cases/common/47 pkgconfig-gen/meson.build	
@@ -51,3 +51,10 @@ pkgg.generate(
   description : 'A minimalistic pkgconfig file.',
   version : libver,
 )
+
+pkgg.generate(
+  name : 'libhello_nolib',
+  description : 'A minimalistic pkgconfig file.',
+  version : libver,
+  dataonly: true
+)