diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 54be8ec1d..6f4501cb2 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -120,6 +120,12 @@ class Backend(): 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() diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 0b2b304c8..db9e1b013 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -544,6 +544,13 @@ int dummy; 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) @@ -1482,6 +1489,66 @@ rule FORTRAN_DEP_HACK 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') @@ -1568,6 +1635,8 @@ rule FORTRAN_DEP_HACK 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' @@ -1636,6 +1705,7 @@ rule FORTRAN_DEP_HACK 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]) @@ -1715,6 +1785,7 @@ rule FORTRAN_DEP_HACK 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): diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 50abd52b9..f95922e03 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -206,6 +206,8 @@ class BuildTarget(): 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 = {} @@ -467,6 +469,15 @@ class BuildTarget(): 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, []) @@ -729,6 +740,11 @@ class Executable(BuildTarget): 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" @@ -754,6 +770,11 @@ class StaticLibrary(BuildTarget): 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" @@ -776,6 +797,7 @@ class SharedLibrary(BuildTarget): 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): """ @@ -865,6 +887,21 @@ class SharedLibrary(BuildTarget): 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 diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 5d3dc99cb..d1f389646 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -373,6 +373,14 @@ class Compiler(): 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) @@ -1708,16 +1716,11 @@ class DmdDCompiler(DCompiler): 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']} @@ -1878,6 +1881,16 @@ class VisualStudioCCompiler(CCompiler): 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) @@ -2564,6 +2577,11 @@ class VisualStudioLinker(): 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): @@ -2612,3 +2630,6 @@ class ArLinker(): def unix_compile_flags_to_native(self, args): return args[:] + + def get_link_debugfile_args(self, targetfile): + return [] diff --git a/run_tests.py b/run_tests.py index 48f123026..b57dd3947 100755 --- a/run_tests.py +++ b/run_tests.py @@ -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 diff --git a/test cases/common/86 same basename/meson.build b/test cases/common/86 same basename/meson.build index 3dc384ee7..856c536c0 100644 --- a/test cases/common/86 same basename/meson.build +++ b/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) diff --git a/test cases/common/86 same basename/sharedsub/meson.build b/test cases/common/86 same basename/sharedsub/meson.build new file mode 100644 index 000000000..29654a94d --- /dev/null +++ b/test cases/common/86 same basename/sharedsub/meson.build @@ -0,0 +1 @@ +shlib = shared_library('name', '../lib.c', c_args : '-DSHAR') diff --git a/test cases/common/86 same basename/staticsub/meson.build b/test cases/common/86 same basename/staticsub/meson.build new file mode 100644 index 000000000..5e5242e06 --- /dev/null +++ b/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')