Repository: mesonbuild/meson

Issue:
Add special-case dependency for zlib
Apple supplies libz as part of its core OS (/usr/lib/libz*.dylib, /usr/include/zlib.h) but does not supply a pkgconfig file for it. As a result, a package that has a meson.build assertion `dependency('zlib')` fails, or falls back to downloading and building libz from source if the package includes its own .wrap file for it. Apple has been doing this for the entire history of zlib in OS X and doesn't seem inclined to change. It would therefore be useful if meson supplied a special case for this library, analogous to other cases that don't supply pkgconfig files. Presumably this would go in one of the mesonbuild/dependencies, I guess misc.py (like pcap and cups)? If this would be generally useful, let me know and I can have a go at writing a patch.


Discussion:
Are there other libraries that should be treated in the same way?
I have no idea (I do not have a linux box to compare my /usr/lib/lib*.dylib and /usr/lib/pkgconfig). From googling, the zlib case appears not to be solely an OS X situation (so misc.py not platform.py?). By randomly comparing fink's and Apple's versions, i suspect also libexpat, libxml2, libxslt, liblzma, the libicu suite, and probably many more "have lib*.dylib but no .pc via Apple but probably do have .pc in the original sources"
It would also be useful to do a `cc.find_library('z')` as part of the dependency implementation since some distros ship `libz.so` but don't ship a pkg-config file.
It would be nice if we can also make a special case for OpenSSL (openssl.pc, libssl.pc, libcrypto.pc) on FreeBSD. FreeBSD has OpenSSL installed in base, but .pc files are not available.

FreeBSD has zlib.pc since a few years ago because of the request from GNOME. I remember that there was plan to make OpenSSL a private library on FreeBSD, but it seems that it is not going to happen anytime soon.
This also affects GStreamer and is also relevant on other OS for what it's worth: see e.g. https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/merge_requests/19
I'm wondering if we could have some form of fallback chain in `dependency()`. Something like:

```meson
dependency(
  [
    'zlib',
    'another-zlib-pkgconfig-name',
    {
      'language': 'c',
      'library': ['z', 'zlib', 'zlib1', 'inflate'],
      'header': 'zlib.h'
    },
    {
      'subproject' : 'zlib',
      'variable' : 'zlib_dep'
    },
  ],
  required : true,
)
```

Resolving patch:
diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py
--- a/mesonbuild/dependencies/__init__.py
+++ b/mesonbuild/dependencies/__init__.py
@@ -20,7 +20,7 @@
     ExternalDependency, NotFoundDependency, ExternalLibrary, ExtraFrameworkDependency, InternalDependency,
     PkgConfigDependency, CMakeDependency, find_external_dependency, get_dep_identifier, packages, _packages_accept_language,
     DependencyFactory)
-from .dev import ValgrindDependency, gmock_factory, gtest_factory, llvm_factory
+from .dev import ValgrindDependency, gmock_factory, gtest_factory, llvm_factory, zlib_factory
 from .coarrays import coarray_factory
 from .mpi import MPIDependency
 from .scalapack import ScalapackDependency
@@ -44,6 +44,7 @@
     'gmock': gmock_factory,
     'llvm': llvm_factory,
     'valgrind': ValgrindDependency,
+    'zlib': zlib_factory,
 
     'boost': BoostDependency,
     'cuda': CudaDependency,
diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py
--- a/mesonbuild/dependencies/dev.py
+++ b/mesonbuild/dependencies/dev.py
@@ -28,6 +28,8 @@
     strip_system_libdirs, ConfigToolDependency, CMakeDependency, DependencyFactory,
 )
 from .misc import threads_factory
+from ..compilers.c import AppleClangCCompiler
+from ..compilers.cpp import AppleClangCPPCompiler
 
 if T.TYPE_CHECKING:
     from .. environment import Environment
@@ -449,6 +451,50 @@ def get_link_args(self, **kwargs):
         return []
 
 
+class ZlibSystemDependency(ExternalDependency):
+
+    def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
+        super().__init__(name, environment, kwargs)
+
+        m = self.env.machines[self.for_machine]
+
+        # I'm not sure this is entirely correct. What if we're cross compiling
+        # from something to macOS?
+        if ((m.is_darwin() and isinstance(self.clib_compiler, (AppleClangCCompiler, AppleClangCPPCompiler))) or
+                m.is_freebsd() or m.is_dragonflybsd()):
+            self.is_found = True
+            self.link_args = ['-lz']
+
+            # No need to set includes,
+            # on macos xcode/clang will do that for us.
+            # on freebsd zlib.h is in /usr/include
+        elif m.is_windows():
+            if self.clib_compiler.get_argument_syntax() == 'msvc':
+                libs = ['zlib1' 'zlib']
+            else:
+                libs = ['z']
+            for lib in libs:
+                l = self.clib_compiler.find_library(lib, environment, [])
+                h = self.clib_compiler.has_header('zlib.h', '', environment, dependencies=[self])
+                if l and h:
+                    self.is_found = True
+                    self.link_args = l
+                    break
+            else:
+                return
+        else:
+            mlog.debug('Unsupported OS {}'.format(m.system))
+            return
+
+        v, _ = self.clib_compiler.get_define('ZLIB_VERSION', '#include <zlib.h>', self.env, [], [self])
+        self.version = v.strip('"')
+
+
+    @staticmethod
+    def get_methods():
+        return [DependencyMethods.SYSTEM]
+
+
 llvm_factory = DependencyFactory(
     'LLVM',
     [DependencyMethods.CMAKE, DependencyMethods.CONFIG_TOOL],
@@ -469,3 +515,10 @@ def get_link_args(self, **kwargs):
     pkgconfig_class=GMockDependencyPC,
     system_class=GMockDependencySystem,
 )
+
+zlib_factory = DependencyFactory(
+    'zlib',
+    [DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE, DependencyMethods.SYSTEM],
+    cmake_name='ZLIB',
+    system_class=ZlibSystemDependency,
+)


Test patch:
diff --git a/test cases/common/228 zlib/meson.build b/test cases/common/228 zlib/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/common/228 zlib/meson.build	
@@ -0,0 +1,23 @@
+project('zlib system dependency', 'c')
+
+if not ['darwin', 'freebsd', 'dragonfly', 'windows'].contains(host_machine.system())
+  error('MESON_SKIP_TEST only applicable on macOS, FreeBSD, DragonflyBSD, and Windows.')
+endif
+
+cc = meson.get_compiler('c')
+
+if host_machine.system() == 'darwin' and cc.get_id() != 'clang'
+  # this will only work on mac if using Apple's clang compiler, but there is no
+  # way in the meson source level to differentiate apple clang and llvm clang
+  # In the meson CI only apple clang is tested
+  error('MESON_SKIP_TEST on macOS only clang is supported.')
+endif
+
+if not (cc.find_library('z', required: false).found() or
+        cc.find_library('zlib', required : false).found() or
+        cc.find_library('zlib1', required : false).found())
+  error('MESON_SKIP_TEST Cannot seem to find zlib via find_library, this test will probably fail.')
+endif
+
+z = dependency('zlib', method : 'system')
+assert(z.version().version_compare('>= 1.2'), 'Version does not seem to have been detected correctly.')