Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DIA SDK dependency #13638

Merged
merged 6 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions docs/markdown/Dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -849,6 +849,34 @@ version.

*New in 0.54.0* the `system` method.

## DIA SDK
dcbaker marked this conversation as resolved.
Show resolved Hide resolved

*(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).

<hr>
<a name="footnote1">1</a>: They may appear to be case-insensitive, if the
underlying file system happens to be case-insensitive.
3 changes: 3 additions & 0 deletions docs/markdown/snippets/dia_sdk.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions docs/markdown/snippets/system_variable_in_dep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Support for variable in system dependencies

System Dependency method `get_variable()` now supports `system` variable.
7 changes: 6 additions & 1 deletion docs/yaml/objects/dep.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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')`.
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions mesonbuild/dependencies/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 2 additions & 2 deletions mesonbuild/dependencies/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion mesonbuild/dependencies/cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion mesonbuild/dependencies/configtool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
102 changes: 102 additions & 0 deletions mesonbuild/dependencies/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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)
vid512 marked this conversation as resolved.
Show resolved Hide resolved
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:
dcbaker marked this conversation as resolved.
Show resolved Hide resolved
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],
Expand Down
2 changes: 1 addition & 1 deletion mesonbuild/dependencies/pkgconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions mesonbuild/interpreter/interpreterobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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'],
)
Expand Down
1 change: 1 addition & 0 deletions mesonbuild/interpreter/kwargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
30 changes: 30 additions & 0 deletions test cases/windows/23 diasdk/dia_registered.cpp
Original file line number Diff line number Diff line change
@@ -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 <dia2.h>
#include <windows.h>
#include <stdexcept>
#include <iostream>

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;
}
}
13 changes: 13 additions & 0 deletions test cases/windows/23 diasdk/meson.build
Original file line number Diff line number Diff line change
@@ -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])
3 changes: 3 additions & 0 deletions test cases/windows/24 diasdk copy dll/config.h.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

#define MSDIA_DLL_NAME L"@msdia_dll_name@"
32 changes: 32 additions & 0 deletions test cases/windows/24 diasdk copy dll/dia_from_dll.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Loads msdiaXXX.dll from current directory using NoRegCoCreate.
// File name is set in config.h symbol MSDIA_DLL_NAME.

#include <dia2.h>
#include <diacreate.h>
#include <windows.h>
#include <stdexcept>
#include <iostream>

#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;
}
}
21 changes: 21 additions & 0 deletions test cases/windows/24 diasdk copy dll/meson.build
Original file line number Diff line number Diff line change
@@ -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])
Loading