Skip to content

Commit

Permalink
Merge pull request #684 from mesonbuild/pdb
Browse files Browse the repository at this point in the history
Create pdb files with MSVC
  • Loading branch information
jpakkane committed Sep 11, 2016
2 parents 167deda + 91c5f07 commit c334eed
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 12 deletions.
6 changes: 6 additions & 0 deletions mesonbuild/backend/backends.py
Expand Up @@ -120,6 +120,12 @@ def get_target_filename_for_linking(self, target):
return os.path.join(self.get_target_dir(target), target.get_filename())
raise AssertionError('BUG: Tried to link to something that\'s not a library')

def get_target_debug_filename(self, target):
fname = target.get_debug_filename()
if not fname:
raise AssertionError("BUG: Tried to generate debug filename when it doesn't exist")
return os.path.join(self.get_target_dir(target), fname)

def get_target_dir(self, target):
if self.environment.coredata.get_builtin_option('layout') == 'mirror':
dirname = target.get_subdir()
Expand Down
71 changes: 71 additions & 0 deletions mesonbuild/backend/ninjabackend.py
Expand Up @@ -544,6 +544,13 @@ def generate_target_install(self, d):
else:
# XXX: Add BuildTarget-specific install dir cases here
outdir = self.environment.get_libdir()
if isinstance(t, build.SharedLibrary) or isinstance(t, build.Executable):
if t.get_debug_filename():
# Install the debug symbols file in the same place as
# the target itself. It has no aliases, should not be
# stripped, and doesn't have an install_rpath
i = [self.get_target_debug_filename(t), outdir, [], False, '']
d.targets.append(i)
i = [self.get_target_filename(t), outdir, t.get_aliaslist(),\
should_strip, t.install_rpath]
d.targets.append(i)
Expand Down Expand Up @@ -1482,6 +1489,66 @@ def get_cross_stdlib_args(self, target, compiler):
return []
return compiler.get_no_stdinc_args()

def get_compile_debugfile_args(self, compiler, target, objfile):
if compiler.id != 'msvc':
return []
# The way MSVC uses PDB files is documented exactly nowhere so
# the following is what we have been able to decipher via
# reverse engineering.
#
# Each object file gets the path of its PDB file written
# inside it. This can be either the final PDB (for, say,
# foo.exe) or an object pdb (for foo.obj). If the former, then
# each compilation step locks the pdb file for writing, which
# is a bottleneck and object files from one target can not be
# used in a different target. The latter seems to be the
# sensible one (and what Unix does) but there is a catch. If
# you try to use precompiled headers MSVC will error out
# because both source and pch pdbs go in the same file and
# they must be the same.
#
# This means:
#
# - pch files must be compiled anew for every object file (negating
# the entire point of having them in the first place)
# - when using pch, output must go to the target pdb
#
# Since both of these are broken in some way, use the one that
# works for each target. This unfortunately means that you
# can't combine pch and object extraction in a single target.
#
# PDB files also lead to filename collisions. A target foo.exe
# has a corresponding foo.pdb. A shared library foo.dll _also_
# has pdb file called foo.pdb. So will a static library
# foo.lib, which clobbers both foo.pdb _and_ the dll file's
# export library called foo.lib (by default, currently we name
# them libfoo.a to avoidt this issue). You can give the files
# unique names such as foo_exe.pdb but VC also generates a
# bunch of other files which take their names from the target
# basename (i.e. "foo") and stomp on each other.
#
# CMake solves this problem by doing two things. First of all
# static libraries do not generate pdb files at
# all. Presumably you don't need them and VC is smart enough
# to look up the original data when linking (speculation, not
# tested). The second solution is that you can only have
# target named "foo" as an exe, shared lib _or_ static
# lib. This makes filename collisions not happen. The downside
# is that you can't have an executable foo that uses a shared
# library libfoo.so, which is a common idiom on Unix.
#
# If you feel that the above is completely wrong and all of
# this is actually doable, please send patches.

if target.has_pch():
tfilename = self.get_target_filename_abs(target)
return compiler.get_compile_debugfile_args(tfilename)
else:
return compiler.get_compile_debugfile_args(objfile)

def get_link_debugfile_args(self, linker, target, outname):
return linker.get_link_debugfile_args(outname)

def generate_single_compile(self, target, outfile, src, is_generated=False, header_deps=[], order_deps=[]):
if(isinstance(src, str) and src.endswith('.h')):
raise RuntimeError('Fug')
Expand Down Expand Up @@ -1568,6 +1635,8 @@ def generate_single_compile(self, target, outfile, src, is_generated=False, head
commands+= compiler.get_include_args(i, False)
if self.environment.coredata.base_options.get('b_pch', False):
commands += self.get_pch_include_args(compiler, target)

commands += self.get_compile_debugfile_args(compiler, target, rel_obj)
crstr = ''
if target.is_cross:
crstr = '_CROSS'
Expand Down Expand Up @@ -1636,6 +1705,7 @@ def generate_msvc_pch_command(self, target, compiler, pch):
just_name = os.path.split(header)[1]
(objname, pch_args) = compiler.gen_pch_args(just_name, source, dst)
commands += pch_args
commands += self.get_compile_debugfile_args(compiler, target, objname)
dep = dst + '.' + compiler.get_depfile_suffix()
return (commands, dep, dst, [objname])

Expand Down Expand Up @@ -1715,6 +1785,7 @@ def generate_link(self, target, outfile, outname, obj_list, linker, extra_args=[
linker)
commands += linker.get_buildtype_linker_args(self.environment.coredata.get_builtin_option('buildtype'))
commands += linker.get_option_link_args(self.environment.coredata.compiler_options)
commands += self.get_link_debugfile_args(linker, target, outname)
if not(isinstance(target, build.StaticLibrary)):
commands += self.environment.coredata.external_link_args[linker.get_language()]
if isinstance(target, build.Executable):
Expand Down
37 changes: 37 additions & 0 deletions mesonbuild/build.py
Expand Up @@ -206,6 +206,8 @@ def __init__(self, name, subdir, subproject, is_cross, sources, objects, environ
self.link_targets = []
self.link_depends = []
self.filename = 'no_name'
# The file with debugging symbols
self.debug_filename = None
self.need_install = False
self.pch = {}
self.extra_args = {}
Expand Down Expand Up @@ -467,6 +469,15 @@ def get_subdir(self):
def get_filename(self):
return self.filename

def get_debug_filename(self):
"""
The name of the file that contains debugging symbols for this target
Returns None if there are no debugging symbols or if they are embedded
in the filename itself
"""
return self.debug_filename

def get_extra_args(self, language):
return self.extra_args.get(language, [])

Expand Down Expand Up @@ -729,6 +740,11 @@ def __init__(self, name, subdir, subproject, is_cross, sources, objects, environ
self.filename = self.name
if self.suffix:
self.filename += '.' + self.suffix
# See determine_debug_filenames() in build.SharedLibrary
buildtype = environment.coredata.get_builtin_option('buildtype')
if compiler_is_msvc(self.sources, is_cross, environment) and \
buildtype.startswith('debug'):
self.debug_filename = self.prefix + self.name + '.pdb'

def type_suffix(self):
return "@exe"
Expand All @@ -754,6 +770,11 @@ def __init__(self, name, subdir, subproject, is_cross, sources, objects, environ
else:
self.suffix = 'a'
self.filename = self.prefix + self.name + '.' + self.suffix
# See determine_debug_filenames() in build.SharedLibrary
buildtype = environment.coredata.get_builtin_option('buildtype')
if compiler_is_msvc(self.sources, is_cross, environment) and \
buildtype.startswith('debug'):
self.debug_filename = self.prefix + self.name + '.pdb'

def type_suffix(self):
return "@sta"
Expand All @@ -776,6 +797,7 @@ def __init__(self, name, subdir, subproject, is_cross, sources, objects, environ
self.suffix = None
self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
self.determine_filenames(is_cross, environment)
self.determine_debug_filenames(is_cross, environment)

def determine_filenames(self, is_cross, env):
"""
Expand Down Expand Up @@ -865,6 +887,21 @@ def determine_filenames(self, is_cross, env):
self.suffix = suffix
self.filename = self.filename_tpl.format(self)

def determine_debug_filenames(self, is_cross, env):
"""
Determine the debug filename(s) using the prefix/name/etc detected in
determine_filenames() above.
"""
buildtype = env.coredata.get_builtin_option('buildtype')
if compiler_is_msvc(self.sources, is_cross, env) and buildtype.startswith('debug'):
# Currently we only implement separate debug symbol files for MSVC
# since the toolchain does it for us. Other toolchains embed the
# debugging symbols in the file itself by default.
if self.soversion:
self.debug_filename = '{0.prefix}{0.name}-{0.soversion}.pdb'.format(self)
else:
self.debug_filename = '{0.prefix}{0.name}.pdb'.format(self)

def process_kwargs(self, kwargs, environment):
super().process_kwargs(kwargs, environment)
# Shared library version
Expand Down
33 changes: 27 additions & 6 deletions mesonbuild/compilers.py
Expand Up @@ -373,6 +373,14 @@ def get_cross_extra_flags(self, environment, *, compile, link):
def get_colorout_args(self, colortype):
return []

# Some compilers (msvc) write debug info to a separate file.
# These args specify where it should be written.
def get_compile_debugfile_args(self, rel_obj):
return []

def get_link_debugfile_args(self, rel_obj):
return []

class CCompiler(Compiler):
def __init__(self, exelist, version, is_cross, exe_wrapper=None):
super().__init__(exelist, version)
Expand Down Expand Up @@ -1708,16 +1716,11 @@ def unix_compile_flags_to_native(self, args):
class VisualStudioCCompiler(CCompiler):
std_warn_args = ['/W3']
std_opt_args= ['/O2']
vs2010_always_args = ['/nologo', '/showIncludes']
vs2013_always_args = ['/nologo', '/showIncludes', '/FS']

def __init__(self, exelist, version, is_cross, exe_wrap):
CCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
self.id = 'msvc'
if int(version.split('.')[0]) > 17:
self.always_args = VisualStudioCCompiler.vs2013_always_args
else:
self.always_args = VisualStudioCCompiler.vs2010_always_args
self.always_args = ['/nologo', '/showIncludes']
self.warn_args = {'1': ['/W2'],
'2': ['/W3'],
'3': ['/w4']}
Expand Down Expand Up @@ -1878,6 +1881,16 @@ def has_argument(self, arg, env):
raise MesonException('Compiling test app failed.')
return not(warning_text in stde or warning_text in stdo)

def get_compile_debugfile_args(self, rel_obj):
pdbarr = rel_obj.split('.')[:-1]
pdbarr += ['pdb']
return ['/Fd' + '.'.join(pdbarr)]

def get_link_debugfile_args(self, targetfile):
pdbarr = targetfile.split('.')[:-1]
pdbarr += ['pdb']
return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)]

class VisualStudioCPPCompiler(VisualStudioCCompiler):
def __init__(self, exelist, version, is_cross, exe_wrap):
VisualStudioCCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
Expand Down Expand Up @@ -2564,6 +2577,11 @@ def unix_link_flags_to_native(self, args):
def unix_compile_flags_to_native(self, args):
return args[:]

def get_link_debugfile_args(self, targetfile):
pdbarr = targetfile.split('.')[:-1]
pdbarr += ['pdb']
return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)]

class ArLinker():

def __init__(self, exelist):
Expand Down Expand Up @@ -2612,3 +2630,6 @@ def unix_link_flags_to_native(self, args):

def unix_compile_flags_to_native(self, args):
return args[:]

def get_link_debugfile_args(self, targetfile):
return []
2 changes: 1 addition & 1 deletion run_tests.py
Expand Up @@ -169,7 +169,7 @@ def validate_install(srcdir, installdir):
# Check if there are any unexpected files
found = get_relative_files_list_from_dir(installdir)
for fname in found:
if fname not in expected:
if fname not in expected and not fname.endswith('.pdb'):
ret_msg += 'Extra file {0} found.\n'.format(fname)
return ret_msg

Expand Down
8 changes: 3 additions & 5 deletions test cases/common/86 same basename/meson.build
@@ -1,13 +1,11 @@
project('same basename', 'c')

subdir('sharedsub')
subdir('staticsub')

# Use the same source file to check that each top level target
# has its own unique working directory. If they don't
# then the .o files will clobber each other.
shlib = shared_library('name', 'lib.c', c_args : '-DSHAR')

# On Windows a static lib is now libfoo.a, so it does not conflict with foo.lib
# from the shared library above
stlib = static_library('name', 'lib.c', c_args : '-DSTAT')

exe1 = executable('name', 'exe1.c', link_with : stlib)
exe2 = executable('name2', 'exe2.c', link_with : shlib)
Expand Down
1 change: 1 addition & 0 deletions test cases/common/86 same basename/sharedsub/meson.build
@@ -0,0 +1 @@
shlib = shared_library('name', '../lib.c', c_args : '-DSHAR')
3 changes: 3 additions & 0 deletions test cases/common/86 same basename/staticsub/meson.build
@@ -0,0 +1,3 @@
# On Windows a static lib is now libfoo.a, so it does not conflict with foo.lib
# from the shared library above
stlib = static_library('name', '../lib.c', c_args : '-DSTAT')

0 comments on commit c334eed

Please sign in to comment.