diff --git a/docs/markdown/Dependencies.md b/docs/markdown/Dependencies.md index a457342d1a9c..93f75d5a916b 100644 --- a/docs/markdown/Dependencies.md +++ b/docs/markdown/Dependencies.md @@ -80,8 +80,8 @@ var = foo_dep.get_variable(cmake : 'CMAKE_VAR', pkgconfig : 'pkg-config-var', co ``` It accepts the keywords 'cmake', 'pkgconfig', 'pkgconfig_define', -'configtool', 'internal', and 'default_value'. 'pkgconfig_define' -works just like the 'define_variable' argument to +'configtool', 'internal', 'system', and 'default_value'. +'pkgconfig_define' works just like the 'define_variable' argument to `get_pkgconfig_variable`. When this method is invoked the keyword corresponding to the underlying type of the dependency will be used to look for a variable. If that variable cannot be found or if the caller @@ -849,6 +849,34 @@ version. *New in 0.54.0* the `system` method. +## DIA SDK + +*(added 1.6.0)* + +Microsoft's Debug Interface Access SDK (DIA SDK) is available only on Windows, +when using msvc, clang-cl or clang compiler from Microsoft Visual Studio. + +The DIA SDK runtime is not statically linked to target. The default usage +method requires the runtime DLL (msdiaXXX.dll) to be manually registered in the +OS with `regsrv32.exe` command, so it can be loaded using `CoCreateInstance` +Windows function. + +Alternatively, you can use meson to copy the DIA runtime DLL to your build +directory, and load it dynamically using `NoRegCoCreate` function provided by +the DIA SDK. To facilitate this, you can read DLL path from dependency's +variable 'dll' and use fs module to copy it. Example: + +```meson +dia = dependency('diasdk', required: true) +fs = import('fs') +fs.copyfile(dia.get_variable('dll')) + +conf = configuration_data() +conf.set('msdia_dll_name', fs.name(dia_dll_name)) +``` + +Only the major version is available (eg. version is `14` for msdia140.dll). +
1: They may appear to be case-insensitive, if the underlying file system happens to be case-insensitive. diff --git a/docs/markdown/snippets/dia_sdk.md b/docs/markdown/snippets/dia_sdk.md new file mode 100644 index 000000000000..eb704230da4f --- /dev/null +++ b/docs/markdown/snippets/dia_sdk.md @@ -0,0 +1,3 @@ +## Support for DIA SDK + +Added support for Windows Debug Interface Access SDK (DIA SDK) dependency. It allows reading with MSVC debugging information (.PDB format). This dependency can only be used on Windows, with msvc, clang or clang-cl compiler. diff --git a/docs/markdown/snippets/system_variable_in_dep.md b/docs/markdown/snippets/system_variable_in_dep.md new file mode 100644 index 000000000000..0cd936322bd9 --- /dev/null +++ b/docs/markdown/snippets/system_variable_in_dep.md @@ -0,0 +1,3 @@ +## Support for variable in system dependencies + +System Dependency method `get_variable()` now supports `system` variable. diff --git a/docs/yaml/objects/dep.yaml b/docs/yaml/objects/dep.yaml index 76543d2c1ab9..103992c547a0 100644 --- a/docs/yaml/objects/dep.yaml +++ b/docs/yaml/objects/dep.yaml @@ -191,7 +191,7 @@ methods: since: 0.58.0 description: | This argument is used as a default value - for `cmake`, `pkgconfig`, `configtool` and `internal` keyword + for `cmake`, `pkgconfig`, `configtool`, `internal` and `system` keyword arguments. It is useful in the common case where `pkgconfig` and `internal` use the same variable name, in which case it's easier to write `dep.get_variable('foo')` instead of `dep.get_variable(pkgconfig: 'foo', internal: 'foo')`. @@ -214,6 +214,11 @@ methods: since: 0.54.0 description: The internal variable name + system: + type: str + since: 1.6.0 + description: The system variable name + default_value: type: str description: The default value to return when the variable does not exist diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index 89d2285ba3a6..a3eb6623f02c 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -195,6 +195,7 @@ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T. 'zlib': 'dev', 'jni': 'dev', 'jdk': 'dev', + 'diasdk': 'dev', 'boost': 'boost', 'cuda': 'cuda', diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 08e81f0d79d5..b4d2b38d96af 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -237,7 +237,7 @@ def _add_sub_dependency(self, deplist: T.Iterable[T.Callable[[], 'Dependency']]) def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, configtool: T.Optional[str] = None, internal: T.Optional[str] = None, - default_value: T.Optional[str] = None, + system: T.Optional[str] = None, default_value: T.Optional[str] = None, pkgconfig_define: PkgConfigDefineType = None) -> str: if default_value is not None: return default_value @@ -321,7 +321,7 @@ def get_include_dirs(self) -> T.List['IncludeDirs']: def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, configtool: T.Optional[str] = None, internal: T.Optional[str] = None, - default_value: T.Optional[str] = None, + system: T.Optional[str] = None, default_value: T.Optional[str] = None, pkgconfig_define: PkgConfigDefineType = None) -> str: val = self.variables.get(internal, default_value) if val is not None: diff --git a/mesonbuild/dependencies/cmake.py b/mesonbuild/dependencies/cmake.py index 5493e94ba79d..4a722157ff56 100644 --- a/mesonbuild/dependencies/cmake.py +++ b/mesonbuild/dependencies/cmake.py @@ -617,7 +617,7 @@ def log_details(self) -> str: def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, configtool: T.Optional[str] = None, internal: T.Optional[str] = None, - default_value: T.Optional[str] = None, + system: T.Optional[str] = None, default_value: T.Optional[str] = None, pkgconfig_define: PkgConfigDefineType = None) -> str: if cmake and self.traceparser is not None: try: diff --git a/mesonbuild/dependencies/configtool.py b/mesonbuild/dependencies/configtool.py index 679c69f5d9be..afe61b29114c 100644 --- a/mesonbuild/dependencies/configtool.py +++ b/mesonbuild/dependencies/configtool.py @@ -150,7 +150,7 @@ def log_tried() -> str: def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, configtool: T.Optional[str] = None, internal: T.Optional[str] = None, - default_value: T.Optional[str] = None, + system: T.Optional[str] = None, default_value: T.Optional[str] = None, pkgconfig_define: PkgConfigDefineType = None) -> str: if configtool: p, out, _ = Popen_safe(self.config + self.get_variable_args(configtool)) diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index de85516feb64..94f51ff69b12 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -28,8 +28,10 @@ if T.TYPE_CHECKING: from ..envconfig import MachineInfo from ..environment import Environment + from ..compilers import Compiler from ..mesonlib import MachineChoice from typing_extensions import TypedDict + from ..interpreter.type_checking import PkgConfigDefineType class JNISystemDependencyKW(TypedDict): modules: T.List[str] @@ -700,6 +702,106 @@ def __init__(self, environment: 'Environment', kwargs: JNISystemDependencyKW): packages['jdk'] = JDKSystemDependency +class DiaSDKSystemDependency(SystemDependency): + + def _try_path(self, diadir: str, cpu: str) -> bool: + if not os.path.isdir(diadir): + return False + + include = os.path.join(diadir, 'include') + if not os.path.isdir(include): + mlog.error('DIA SDK is missing include directory:', include) + return False + + lib = os.path.join(diadir, 'lib', cpu, 'diaguids.lib') + if not os.path.exists(lib): + mlog.error('DIA SDK is missing library:', lib) + return False + + bindir = os.path.join(diadir, 'bin', cpu) + if not os.path.exists(bindir): + mlog.error(f'Directory {bindir} not found') + return False + + found = glob.glob(os.path.join(bindir, 'msdia*.dll')) + if not found: + mlog.error("Can't find msdia*.dll in " + bindir) + return False + if len(found) > 1: + mlog.error('Multiple msdia*.dll files found in ' + bindir) + return False + self.dll = found[0] + + # Parse only major version from DLL name (eg '8' from 'msdia80.dll', '14' from 'msdia140.dll', etc.). + # Minor version is not reflected in the DLL name, instead '0' is always used. + # Aside from major version in DLL name, the SDK version is not visible to user anywhere. + # The only place where the full version is stored, seems to be the Version field in msdia*.dll resources. + dllname = os.path.basename(self.dll) + versionstr = dllname[len('msdia'):-len('.dll')] + if versionstr[-1] == '0': + self.version = versionstr[:-1] + else: + mlog.error(f"Unexpected DIA SDK version string in '{dllname}'") + self.version = 'unknown' + + self.compile_args.append('-I' + include) + self.link_args.append(lib) + self.is_found = True + return True + + # Check if compiler has a built-in macro defined + @staticmethod + def _has_define(compiler: 'Compiler', dname: str, env: 'Environment') -> bool: + defval, _ = compiler.get_define(dname, '', env, [], []) + return defval is not None + + def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + super().__init__('diasdk', environment, kwargs) + self.is_found = False + + compilers = environment.coredata.compilers.host + if 'cpp' in compilers: + compiler = compilers['cpp'] + elif 'c' in compilers: + compiler = compilers['c'] + else: + raise DependencyException('DIA SDK is only supported in C and C++ projects') + + is_msvc_clang = compiler.id == 'clang' and self._has_define(compiler, '_MSC_VER', environment) + if compiler.id not in {'msvc', 'clang-cl'} and not is_msvc_clang: + raise DependencyException('DIA SDK is only supported with Microsoft Visual Studio compilers') + + cpu_translate = {'arm': 'arm', 'aarch64': 'arm64', 'x86': '.', 'x86_64': 'amd64'} + cpu_family = environment.machines.host.cpu_family + cpu = cpu_translate.get(cpu_family) + if cpu is None: + raise DependencyException(f'DIA SDK is not supported for "{cpu_family}" architecture') + + vsdir = os.environ.get('VSInstallDir') + if vsdir is None: + raise DependencyException("Environment variable VSInstallDir required for DIA SDK is not set") + + diadir = os.path.join(vsdir, 'DIA SDK') + if self._try_path(diadir, cpu): + mlog.debug('DIA SDK was found at default path: ', diadir) + self.is_found = True + return + mlog.debug('DIA SDK was not found at default path: ', diadir) + + return + + def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, + configtool: T.Optional[str] = None, internal: T.Optional[str] = None, + system: T.Optional[str] = None, default_value: T.Optional[str] = None, + pkgconfig_define: PkgConfigDefineType = None) -> str: + if system == 'dll' and self.is_found: + return self.dll + if default_value is not None: + return default_value + raise DependencyException(f'Could not get system variable and no default was set for {self!r}') + +packages['diasdk'] = DiaSDKSystemDependency + packages['llvm'] = llvm_factory = DependencyFactory( 'LLVM', [DependencyMethods.CMAKE, DependencyMethods.CONFIG_TOOL], diff --git a/mesonbuild/dependencies/pkgconfig.py b/mesonbuild/dependencies/pkgconfig.py index 9d47155a23d4..4fea86c12a33 100644 --- a/mesonbuild/dependencies/pkgconfig.py +++ b/mesonbuild/dependencies/pkgconfig.py @@ -557,7 +557,7 @@ def log_tried() -> str: def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None, configtool: T.Optional[str] = None, internal: T.Optional[str] = None, - default_value: T.Optional[str] = None, + system: T.Optional[str] = None, default_value: T.Optional[str] = None, pkgconfig_define: PkgConfigDefineType = None) -> str: if pkgconfig: try: diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index 155cfd23e56e..1f80e206f9ff 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -539,6 +539,7 @@ def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.Dep KwargInfo('pkgconfig', (str, NoneType)), KwargInfo('configtool', (str, NoneType)), KwargInfo('internal', (str, NoneType), since='0.54.0'), + KwargInfo('system', (str, NoneType), since='1.6.0'), KwargInfo('default_value', (str, NoneType)), PKGCONFIG_DEFINE_KW, ) @@ -555,6 +556,7 @@ def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: 'kwargs.Depend pkgconfig=kwargs['pkgconfig'] or default_varname, configtool=kwargs['configtool'] or default_varname, internal=kwargs['internal'] or default_varname, + system=kwargs['system'] or default_varname, default_value=kwargs['default_value'], pkgconfig_define=kwargs['pkgconfig_define'], ) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index eee53c5ff413..c9390e9750a3 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -268,6 +268,7 @@ class DependencyGetVariable(TypedDict): pkgconfig: T.Optional[str] configtool: T.Optional[str] internal: T.Optional[str] + system: T.Optional[str] default_value: T.Optional[str] pkgconfig_define: PkgConfigDefineType diff --git a/test cases/windows/23 diasdk/dia_registered.cpp b/test cases/windows/23 diasdk/dia_registered.cpp new file mode 100644 index 000000000000..cec4fb2f678f --- /dev/null +++ b/test cases/windows/23 diasdk/dia_registered.cpp @@ -0,0 +1,30 @@ +// Loads DIA SDK from system registry using CoCreateInstance(). +// The corresponding msdiaXXX.dll must be registered in system registry +// (eg. run `regsvr32.exe msdia140.dll` as administrator) + +#include +#include +#include +#include + +int main() +{ + try { + + HRESULT hr = CoInitialize(NULL); + if (FAILED(hr)) + throw std::runtime_error("Failed to initialize COM library"); + + IDiaDataSource* datasrc; + hr = CoCreateInstance( CLSID_DiaSource, NULL, CLSCTX_INPROC_SERVER, __uuidof(IDiaDataSource), (void **)&datasrc); + if (FAILED(hr)) + throw std::runtime_error("Can't create IDiaDataSource. You must register msdia*.dll with regsvr32.exe."); + + std::cout << "DIA was successfully loaded\n"; + return 0; + + } catch (std::exception& err) { + std::cerr << err.what() << std::endl; + return 1; + } +} diff --git a/test cases/windows/23 diasdk/meson.build b/test cases/windows/23 diasdk/meson.build new file mode 100644 index 000000000000..bb8477822a66 --- /dev/null +++ b/test cases/windows/23 diasdk/meson.build @@ -0,0 +1,13 @@ +project('diatest', 'cpp') + +if host_machine.system() != 'windows' + error('MESON_SKIP_TEST: unsupported platform') +endif +cpp = meson.get_compiler('cpp', native: false) +is_msvc_clang = cpp.get_id() == 'clang' and cpp.get_define('_MSC_VER') != '' +if not ['msvc', 'clang-cl'].contains(cpp.get_id()) and not is_msvc_clang + error('MESON_SKIP_TEST: unsupported compiler') +endif + +dia = dependency('diasdk', required: true) +executable('dia_registered', ['dia_registered.cpp'], dependencies:[dia]) diff --git a/test cases/windows/24 diasdk copy dll/config.h.in b/test cases/windows/24 diasdk copy dll/config.h.in new file mode 100644 index 000000000000..80d59c1a4b09 --- /dev/null +++ b/test cases/windows/24 diasdk copy dll/config.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define MSDIA_DLL_NAME L"@msdia_dll_name@" diff --git a/test cases/windows/24 diasdk copy dll/dia_from_dll.cpp b/test cases/windows/24 diasdk copy dll/dia_from_dll.cpp new file mode 100644 index 000000000000..5474717490d3 --- /dev/null +++ b/test cases/windows/24 diasdk copy dll/dia_from_dll.cpp @@ -0,0 +1,32 @@ +// Loads msdiaXXX.dll from current directory using NoRegCoCreate. +// File name is set in config.h symbol MSDIA_DLL_NAME. + +#include +#include +#include +#include +#include + +#include "config.h" + +int main() +{ + try { + + HRESULT hr = CoInitialize(NULL); + if (FAILED(hr)) + throw std::runtime_error("Failed to initialize COM library"); + + IDiaDataSource* datasrc; + hr = NoRegCoCreate(MSDIA_DLL_NAME, CLSID_DiaSource, __uuidof(IDiaDataSource), (void**)&datasrc); + if (FAILED(hr)) + throw std::runtime_error("Can't open DIA DLL"); + + std::cout << "DIA was successfully loaded\n"; + return 0; + + } catch (std::exception& err) { + std::cerr << err.what() << std::endl; + return 1; + } +} diff --git a/test cases/windows/24 diasdk copy dll/meson.build b/test cases/windows/24 diasdk copy dll/meson.build new file mode 100644 index 000000000000..1a078b0d98de --- /dev/null +++ b/test cases/windows/24 diasdk copy dll/meson.build @@ -0,0 +1,21 @@ +project('diatest', 'cpp') + +if host_machine.system() != 'windows' + error('MESON_SKIP_TEST: unsupported platform') +endif +cpp = meson.get_compiler('cpp', native: false) +is_msvc_clang = cpp.get_id() == 'clang' and cpp.get_define('_MSC_VER') != '' +if not ['msvc', 'clang-cl'].contains(cpp.get_id()) and not is_msvc_clang + error('MESON_SKIP_TEST: unsupported compiler') +endif + +dia = dependency('diasdk', required: true) +dia_dll_name = dia.get_variable('dll') +fs = import('fs') +fs.copyfile( dia_dll_name ) + +conf = configuration_data() +conf.set('msdia_dll_name', fs.name(dia_dll_name)) +configure_file(input: 'config.h.in', output: 'config.h', configuration: conf) + +executable('dia_from_dll', ['dia_from_dll.cpp'], dependencies: [dia])