Repository: mesonbuild/meson

Issue:
`dependency('vulkan', version: ['>=1.X.X'])` fails on Windows
**Describe the bug**
`dependency('vulkan',  version: ['>=1.3.0'])` fails on Windows if a version is specified.
The Vulkan SDK is installed, and the environment variable (`VULKAN_SDK=C:\VulkanSDK\1.3.250.1`) is set.

Edit: The same does work on Linux.

**To Reproduce**
Install a recent Vulkan SDK (>= 1.3.0) and use this meson.build:
```python
project('test-project', 'cpp')

# this does not work
vulkan = dependency('vulkan', version: ['>=1.3.0'])
# this does work
# vulkan = dependency('vulkan')

executable('test-project', ['main.cpp'], dependencies : [vulkan])
```

**Expected behavior**
Vulkan is detected correctly.

**system parameters**
* native build
* Windows 11
* Python 3.9.13
* Meson 1.2.0
* Ninja 1.11.0



Discussion:
https://github.com/mesonbuild/meson/blob/cdef6741698d98453142cb2a0b65c72676dbb259/mesonbuild/dependencies/ui.py#L239-L240

I suppose that that never got implemented... AFAIK there's no actual SDK version inside of a header somewhere, right?
There is, but it would have to be extracted: https://github.com/KhronosGroup/Vulkan-Headers/blob/a3b683653e6a498514ef8a1865594810e91c594c/include/vulkan/vulkan_core.h#L75

I would prefer meson would warn that the version could not be determined instead of failing.
@LDAP Meson has `compiler.get_define()`, which we could use on all platforms. Wanna submit a PR?
No, this will not work because as pointed out in the link above, it expands to: 
```c
#define VK_MAKE_API_VERSION(variant, major, minor, patch) \
    ((((uint32_t)(variant)) << 29U) | (((uint32_t)(major)) << 22U) | (((uint32_t)(minor)) << 12U) | ((uint32_t)(patch)))

#define VK_HEADER_VERSION_COMPLETE VK_MAKE_API_VERSION(0, 1, 3, VK_HEADER_VERSION)
```
So, the returned value would be

```
    ((((uint32_t)(0)) << 29U) | (((uint32_t)(1)) << 22U) | (((uint32_t)(3)) << 12U) | ((uint32_t)(260)))
```

This could be accurately done by compiling and executing a program that prints the value with printf, of course.
Is there a way to do this as part of a meson setup?
Yes if you do a cc.run()
There are a couple of other "system" dependency types that implement a similar trick, the self.clib_compiler object gives you direct access to the backend compiler object which is wrapped as `cc = meson.get_compiler('c')` in a meson.build file.

The main issue is that `cc.run()` isn't very cross compiler friendly, there are some cases where it simply works such as x86 64-bit -> 32-bit, but otherwise you need to install some emulator such as wine or qemu and set it up as an exe wrapper.

That means that when cross compiling, dependencies that rely on `cc.run()` may work or may emit an error saying that it can't run host (cross) binaries. So you'll still want to try getting the version from the SDK path, probably?
I am looking into this. 

Resolving patch:
diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py
--- a/mesonbuild/dependencies/ui.py
+++ b/mesonbuild/dependencies/ui.py
@@ -17,11 +17,13 @@
 from __future__ import annotations
 
 import os
+import re
 import subprocess
 import typing as T
 
 from .. import mlog
 from .. import mesonlib
+from ..compilers.compilers import CrossNoRunException
 from ..mesonlib import (
     Popen_safe, extract_as_list, version_compare_many
 )
@@ -235,10 +237,6 @@ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.
             self.compile_args.append('-I' + inc_path)
             self.link_args.append('-L' + lib_path)
             self.link_args.append('-l' + lib_name)
-
-            # TODO: find a way to retrieve the version from the sdk?
-            # Usually it is a part of the path to it (but does not have to be)
-            return
         else:
             # simply try to guess it, usually works on linux
             libs = self.clib_compiler.find_library('vulkan', environment, [])
@@ -246,7 +244,33 @@ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.
                 self.is_found = True
                 for lib in libs:
                     self.link_args.append(lib)
-                return
+
+        if self.is_found:
+            get_version = '''\
+#include <stdio.h>
+#include <vulkan/vulkan.h>
+
+int main() {
+    printf("%i.%i.%i", VK_VERSION_MAJOR(VK_HEADER_VERSION_COMPLETE),
+                       VK_VERSION_MINOR(VK_HEADER_VERSION_COMPLETE),
+                       VK_VERSION_PATCH(VK_HEADER_VERSION_COMPLETE));
+    return 0;
+}
+'''
+            try:
+                run = self.clib_compiler.run(get_version, environment, extra_args=self.compile_args)
+            except CrossNoRunException:
+                run = None
+            if run and run.compiled and run.returncode == 0:
+                self.version = run.stdout
+            elif self.vulkan_sdk:
+                # fall back to heuristics: detect version number in path
+                # matches the default install path on Windows
+                match = re.search(rf'VulkanSDK{re.escape(os.path.sep)}([0-9]+(?:\.[0-9]+)+)', self.vulkan_sdk)
+                if match:
+                    self.version = match.group(1)
+                else:
+                    mlog.warning(f'Environment variable VULKAN_SDK={self.vulkan_sdk} is present, but Vulkan version could not be extracted.')
 
 packages['gl'] = gl_factory = DependencyFactory(
     'gl',


Test patch: