Merge pull request #1321 from centricular/compiler-args-class

Improve compiler argument handling with a new class CompilerArgs derived from list()
pull/1347/head
Jussi Pakkanen 8 years ago committed by GitHub
commit 6357ad0cd0
  1. 70
      mesonbuild/backend/backends.py
  2. 252
      mesonbuild/backend/ninjabackend.py
  3. 189
      mesonbuild/backend/vs2010backend.py
  4. 14
      mesonbuild/build.py
  5. 304
      mesonbuild/compilers.py
  6. 4
      mesonbuild/coredata.py
  7. 8
      mesonbuild/dependencies.py
  8. 92
      run_unittests.py
  9. 6
      setup.cfg
  10. 22
      test cases/common/138 include order/meson.build
  11. 1
      test cases/common/138 include order/sub1/main.h
  12. 4
      test cases/common/138 include order/sub1/meson.build
  13. 6
      test cases/common/138 include order/sub1/some.c
  14. 10
      test cases/common/138 include order/sub1/some.h
  15. 1
      test cases/common/138 include order/sub2/main.h
  16. 1
      test cases/common/138 include order/sub2/meson.build
  17. 1
      test cases/common/138 include order/sub3/main.h
  18. 1
      test cases/common/138 include order/sub3/meson.build
  19. 8
      test cases/common/138 include order/sub4/main.c
  20. 3
      test cases/common/138 include order/sub4/main.h
  21. 4
      test cases/common/138 include order/sub4/meson.build

@ -21,6 +21,7 @@ from .. import compilers
import json import json
import subprocess import subprocess
from ..mesonlib import MesonException, get_compiler_for_source, classify_unity_sources from ..mesonlib import MesonException, get_compiler_for_source, classify_unity_sources
from ..compilers import CompilerArgs
class CleanTrees: class CleanTrees:
''' '''
@ -338,32 +339,59 @@ class Backend:
return extra_args return extra_args
def generate_basic_compiler_args(self, target, compiler, no_warn_args=False): def generate_basic_compiler_args(self, target, compiler, no_warn_args=False):
commands = [] # Create an empty commands list, and start adding arguments from
# various sources in the order in which they must override each other
# starting from hard-coded defaults followed by build options and so on.
commands = CompilerArgs(compiler)
# First, the trivial ones that are impossible to override.
#
# Add -nostdinc/-nostdinc++ if needed; can't be overriden
commands += self.get_cross_stdlib_args(target, compiler) commands += self.get_cross_stdlib_args(target, compiler)
# Add things like /NOLOGO or -pipe; usually can't be overriden
commands += compiler.get_always_args() commands += compiler.get_always_args()
# Only add warning-flags by default if the buildtype enables it, and if
# we weren't explicitly asked to not emit warnings (for Vala, f.ex)
if no_warn_args: if no_warn_args:
commands += compiler.get_no_warn_args() commands += compiler.get_no_warn_args()
elif self.environment.coredata.get_builtin_option('buildtype') != 'plain': elif self.environment.coredata.get_builtin_option('buildtype') != 'plain':
commands += compiler.get_warn_args(self.environment.coredata.get_builtin_option('warning_level')) commands += compiler.get_warn_args(self.environment.coredata.get_builtin_option('warning_level'))
# Add -Werror if werror=true is set in the build options set on the
# command-line or default_options inside project(). This only sets the
# action to be done for warnings if/when they are emitted, so it's ok
# to set it after get_no_warn_args() or get_warn_args().
if self.environment.coredata.get_builtin_option('werror'):
commands += compiler.get_werror_args()
# Add compile args for c_* or cpp_* build options set on the
# command-line or default_options inside project().
commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options) commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options)
commands += self.build.get_global_args(compiler) # Add buildtype args: optimization level, debugging, etc.
commands += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype'))
# Add compile args added using add_project_arguments()
commands += self.build.get_project_args(compiler, target.subproject) commands += self.build.get_project_args(compiler, target.subproject)
# Add compile args added using add_global_arguments()
# These override per-project arguments
commands += self.build.get_global_args(compiler)
# Compile args added from the env: CFLAGS/CXXFLAGS, etc. We want these
# to override all the defaults, but not the per-target compile args.
commands += self.environment.coredata.external_args[compiler.get_language()] commands += self.environment.coredata.external_args[compiler.get_language()]
commands += self.escape_extra_args(compiler, target.get_extra_args(compiler.get_language())) # Always set -fPIC for shared libraries
commands += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype'))
if self.environment.coredata.get_builtin_option('werror'):
commands += compiler.get_werror_args()
if isinstance(target, build.SharedLibrary): if isinstance(target, build.SharedLibrary):
commands += compiler.get_pic_args() commands += compiler.get_pic_args()
# Set -fPIC for static libraries by default unless explicitly disabled
if isinstance(target, build.StaticLibrary) and target.pic: if isinstance(target, build.StaticLibrary) and target.pic:
commands += compiler.get_pic_args() commands += compiler.get_pic_args()
# Add compile args needed to find external dependencies
# Link args are added while generating the link command
for dep in target.get_external_deps(): for dep in target.get_external_deps():
# Cflags required by external deps might have UNIX-specific flags, commands += dep.get_compile_args()
# so filter them out if needed # Qt needs -fPIC for executables
commands += compiler.unix_compile_flags_to_native(dep.get_compile_args()) # XXX: We should move to -fPIC for all executables
if isinstance(target, build.Executable): if isinstance(target, build.Executable):
commands += dep.get_exe_args() commands += dep.get_exe_args(compiler)
# For 'automagic' deps: Boost and GTest. Also dependency('threads').
# pkg-config puts the thread flags itself via `Cflags:`
if dep.need_threads():
commands += compiler.thread_flags()
# Fortran requires extra include directives. # Fortran requires extra include directives.
if compiler.language == 'fortran': if compiler.language == 'fortran':
for lt in target.link_targets: for lt in target.link_targets:
@ -664,23 +692,3 @@ class Backend:
for s in self.build.postconf_scripts: for s in self.build.postconf_scripts:
cmd = s['exe'] + s['args'] cmd = s['exe'] + s['args']
subprocess.check_call(cmd, env=child_env) subprocess.check_call(cmd, env=child_env)
# Subprojects of subprojects may cause the same dep args to be used
# multiple times. Remove duplicates here. Note that we can't dedup
# libraries based on name alone, because "-lfoo -lbar -lfoo" is
# a completely valid (though pathological) sequence and removing the
# latter may fail. Usually only applies to static libs, though.
def dedup_arguments(self, commands):
includes = {}
final_commands = []
previous = '-fsuch_arguments=woof'
for c in commands:
if c.startswith(('-I', '-L', '/LIBPATH')):
if c in includes:
continue
includes[c] = True
if previous == c:
continue
previous = c
final_commands.append(c)
return final_commands

@ -19,6 +19,7 @@ from .. import build
from .. import mlog from .. import mlog
from .. import dependencies from .. import dependencies
from .. import compilers from .. import compilers
from ..compilers import CompilerArgs
from ..mesonlib import File, MesonException, get_compiler_for_source, Popen_safe from ..mesonlib import File, MesonException, get_compiler_for_source, Popen_safe
from .backends import CleanTrees, InstallData from .backends import CleanTrees, InstallData
from ..build import InvalidArguments from ..build import InvalidArguments
@ -1725,7 +1726,7 @@ rule FORTRAN_DEP_HACK
def generate_llvm_ir_compile(self, target, outfile, src): def generate_llvm_ir_compile(self, target, outfile, src):
compiler = get_compiler_for_source(target.compilers.values(), src) compiler = get_compiler_for_source(target.compilers.values(), src)
commands = [] commands = CompilerArgs(compiler)
# Compiler args for compiling this target # Compiler args for compiling this target
commands += compilers.get_base_compile_args(self.environment.coredata.base_options, commands += compilers.get_base_compile_args(self.environment.coredata.base_options,
compiler) compiler)
@ -1748,11 +1749,40 @@ rule FORTRAN_DEP_HACK
# Write the Ninja build command # Write the Ninja build command
compiler_name = 'llvm_ir{}_COMPILER'.format('_CROSS' if target.is_cross else '') compiler_name = 'llvm_ir{}_COMPILER'.format('_CROSS' if target.is_cross else '')
element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src) element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src)
commands = self.dedup_arguments(commands) # Convert from GCC-style link argument naming to the naming used by the
# current compiler.
commands = commands.to_native()
element.add_item('ARGS', commands) element.add_item('ARGS', commands)
element.write(outfile) element.write(outfile)
return rel_obj return rel_obj
def get_source_dir_include_args(self, target, compiler):
curdir = target.get_subdir()
tmppath = os.path.normpath(os.path.join(self.build_to_src, curdir))
return compiler.get_include_args(tmppath, False)
def get_build_dir_include_args(self, target, compiler):
curdir = target.get_subdir()
if curdir == '':
curdir = '.'
return compiler.get_include_args(curdir, False)
def get_custom_target_dir_include_args(self, target, compiler):
custom_target_include_dirs = []
for i in target.get_generated_sources():
# Generator output goes into the target private dir which is
# already in the include paths list. Only custom targets have their
# own target build dir.
if not isinstance(i, build.CustomTarget):
continue
idir = self.get_target_dir(i)
if idir not in custom_target_include_dirs:
custom_target_include_dirs.append(idir)
incs = []
for i in custom_target_include_dirs:
incs += compiler.get_include_args(i, False)
return incs
def generate_single_compile(self, target, outfile, src, is_generated=False, header_deps=[], order_deps=[]): def generate_single_compile(self, target, outfile, src, is_generated=False, header_deps=[], order_deps=[]):
""" """
Compiles C/C++, ObjC/ObjC++, Fortran, and D sources Compiles C/C++, ObjC/ObjC++, Fortran, and D sources
@ -1763,30 +1793,40 @@ rule FORTRAN_DEP_HACK
raise AssertionError('BUG: sources should not contain headers {!r}'.format(src.fname)) raise AssertionError('BUG: sources should not contain headers {!r}'.format(src.fname))
extra_orderdeps = [] extra_orderdeps = []
compiler = get_compiler_for_source(target.compilers.values(), src) compiler = get_compiler_for_source(target.compilers.values(), src)
commands = []
# The first thing is implicit include directories: source, build and private. # Create an empty commands list, and start adding arguments from
commands += compiler.get_include_args(self.get_target_private_dir(target), False) # various sources in the order in which they must override each other
# Compiler args for compiling this target commands = CompilerArgs(compiler)
# Add compiler args for compiling this target derived from 'base' build
# options passed on the command-line, in default_options, etc.
# These have the lowest priority.
commands += compilers.get_base_compile_args(self.environment.coredata.base_options, commands += compilers.get_base_compile_args(self.environment.coredata.base_options,
compiler) compiler)
# Add the root source and build directories as include dirs # The code generated by valac is usually crap and has tons of unused
curdir = target.get_subdir() # variables and such, so disable warnings for Vala C sources.
tmppath = os.path.normpath(os.path.join(self.build_to_src, curdir)) no_warn_args = (is_generated == 'vala')
src_inc = compiler.get_include_args(tmppath, False) # Add compiler args and include paths from several sources; defaults,
if curdir == '': # build options, external dependencies, etc.
curdir = '.' commands += self.generate_basic_compiler_args(target, compiler, no_warn_args)
build_inc = compiler.get_include_args(curdir, False) # Add include dirs from the `include_directories:` kwarg on the target
commands += build_inc + src_inc # and from `include_directories:` of internal deps of the target.
# -I args work differently than other ones. In them the first found #
# directory is used whereas for other flags (such as -ffoo -fno-foo) the # Target include dirs should override internal deps include dirs.
# latest one is used. Therefore put the internal include directories #
# here before generating the "basic compiler args" so they override args # Include dirs from internal deps should override include dirs from
# coming from e.g. pkg-config. # external deps.
for i in target.get_include_dirs(): for i in target.get_include_dirs():
basedir = i.get_curdir() basedir = i.get_curdir()
for d in i.get_incdirs(): for d in i.get_incdirs():
expdir = os.path.join(basedir, d) # Avoid superfluous '/.' at the end of paths when d is '.'
if d not in ('', '.'):
expdir = os.path.join(basedir, d)
else:
expdir = basedir
srctreedir = os.path.join(self.build_to_src, expdir) srctreedir = os.path.join(self.build_to_src, expdir)
# Add source subdir first so that the build subdir overrides it
sargs = compiler.get_include_args(srctreedir, i.is_system)
commands += sargs
# There may be include dirs where a build directory has not been # There may be include dirs where a build directory has not been
# created for some source dir. For example if someone does this: # created for some source dir. For example if someone does this:
# #
@ -1797,20 +1837,32 @@ rule FORTRAN_DEP_HACK
bargs = compiler.get_include_args(expdir, i.is_system) bargs = compiler.get_include_args(expdir, i.is_system)
else: else:
bargs = [] bargs = []
sargs = compiler.get_include_args(srctreedir, i.is_system)
commands += bargs commands += bargs
commands += sargs
for d in i.get_extra_build_dirs(): for d in i.get_extra_build_dirs():
commands += compiler.get_include_args(d, i.is_system) commands += compiler.get_include_args(d, i.is_system)
commands += self.generate_basic_compiler_args(target, compiler, # Add per-target compile args, f.ex, `c_args : ['-DFOO']`. We set these
# The code generated by valac is usually crap # near the end since these are supposed to override everything else.
# and has tons of unused variables and such, commands += self.escape_extra_args(compiler,
# so disable warnings for Vala C sources. target.get_extra_args(compiler.get_language()))
no_warn_args=(is_generated == 'vala')) # Add source dir and build dir. Project-specific and target-specific
for d in target.external_deps: # include paths must override per-target compile args, include paths
if d.need_threads(): # from external dependencies, internal dependencies, and from
commands += compiler.thread_flags() # per-target `include_directories:`
break #
# We prefer headers in the build dir and the custom target dir over the
# source dir since, for instance, the user might have an
# srcdir == builddir Autotools build in their source tree. Many
# projects that are moving to Meson have both Meson and Autotools in
# parallel as part of the transition.
commands += self.get_source_dir_include_args(target, compiler)
commands += self.get_custom_target_dir_include_args(target, compiler)
commands += self.get_build_dir_include_args(target, compiler)
# Finally add the private dir for the target to the include path. This
# must override everything else and must be the final path added.
commands += compiler.get_include_args(self.get_target_private_dir(target), False)
# FIXME: This file handling is atrocious and broken. We need to
# replace it with File objects used consistently everywhere.
if isinstance(src, RawFilename): if isinstance(src, RawFilename):
rel_src = src.fname rel_src = src.fname
if os.path.isabs(src.fname): if os.path.isabs(src.fname):
@ -1835,7 +1887,13 @@ rule FORTRAN_DEP_HACK
rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename) rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename)
rel_obj += '.' + self.environment.get_object_suffix() rel_obj += '.' + self.environment.get_object_suffix()
dep_file = compiler.depfile_for_object(rel_obj) dep_file = compiler.depfile_for_object(rel_obj)
# Add MSVC debug file generation compile flags: /Fd /FS
commands += self.get_compile_debugfile_args(compiler, target, rel_obj)
# PCH handling
if self.environment.coredata.base_options.get('b_pch', False): if self.environment.coredata.base_options.get('b_pch', False):
commands += self.get_pch_include_args(compiler, target)
pchlist = target.get_pch(compiler.language) pchlist = target.get_pch(compiler.language)
else: else:
pchlist = [] pchlist = []
@ -1848,19 +1906,7 @@ rule FORTRAN_DEP_HACK
i = os.path.join(self.get_target_private_dir(target), compiler.get_pch_name(pchlist[0])) i = os.path.join(self.get_target_private_dir(target), compiler.get_pch_name(pchlist[0]))
arr.append(i) arr.append(i)
pch_dep = arr pch_dep = arr
custom_target_include_dirs = []
for i in target.get_generated_sources():
if not isinstance(i, build.CustomTarget):
continue
idir = self.get_target_dir(i)
if idir not in custom_target_include_dirs:
custom_target_include_dirs.append(idir)
for i in custom_target_include_dirs:
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 = '' crstr = ''
if target.is_cross: if target.is_cross:
crstr = '_CROSS' crstr = '_CROSS'
@ -1895,7 +1941,9 @@ rule FORTRAN_DEP_HACK
element.add_orderdep(d) element.add_orderdep(d)
element.add_orderdep(pch_dep) element.add_orderdep(pch_dep)
element.add_orderdep(extra_orderdeps) element.add_orderdep(extra_orderdeps)
commands = self.dedup_arguments(commands) # Convert from GCC-style link argument naming to the naming used by the
# current compiler.
commands = commands.to_native()
for i in self.get_fortran_orderdeps(target, compiler): for i in self.get_fortran_orderdeps(target, compiler):
element.add_orderdep(i) element.add_orderdep(i)
element.add_item('DEPFILE', dep_file) element.add_item('DEPFILE', dep_file)
@ -1995,47 +2043,23 @@ rule FORTRAN_DEP_HACK
return [] return []
return linker.get_no_stdlib_link_args() return linker.get_no_stdlib_link_args()
def generate_link(self, target, outfile, outname, obj_list, linker, extra_args=[]): def get_target_type_link_args(self, target, linker):
if isinstance(target, build.StaticLibrary):
linker_base = 'STATIC'
else:
linker_base = linker.get_language() # Fixme.
if isinstance(target, build.SharedLibrary):
self.generate_shsym(outfile, target)
crstr = ''
if target.is_cross:
crstr = '_CROSS'
linker_rule = linker_base + crstr + '_LINKER'
abspath = os.path.join(self.environment.get_build_dir(), target.subdir) abspath = os.path.join(self.environment.get_build_dir(), target.subdir)
commands = [] commands = []
if not isinstance(target, build.StaticLibrary):
commands += self.build.get_project_link_args(linker, target.subproject)
commands += self.build.get_global_link_args(linker)
commands += self.get_cross_stdlib_link_args(target, linker)
commands += linker.get_linker_always_args()
if not isinstance(target, build.StaticLibrary):
commands += compilers.get_base_link_args(self.environment.coredata.base_options,
linker,
isinstance(target, build.SharedModule))
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): if isinstance(target, build.Executable):
# Currently only used with the Swift compiler to add '-emit-executable'
commands += linker.get_std_exe_link_args() commands += linker.get_std_exe_link_args()
elif isinstance(target, build.SharedLibrary): elif isinstance(target, build.SharedLibrary):
if isinstance(target, build.SharedModule): if isinstance(target, build.SharedModule):
commands += linker.get_std_shared_module_link_args() commands += linker.get_std_shared_module_link_args()
else: else:
commands += linker.get_std_shared_lib_link_args() commands += linker.get_std_shared_lib_link_args()
# All shared libraries are PIC
commands += linker.get_pic_args() commands += linker.get_pic_args()
if hasattr(target, 'soversion'): # Add -Wl,-soname arguments on Linux, -install_name on OS X
soversion = target.soversion
else:
soversion = None
commands += linker.get_soname_args(target.prefix, target.name, target.suffix, commands += linker.get_soname_args(target.prefix, target.name, target.suffix,
abspath, soversion, isinstance(target, build.SharedModule)) abspath, target.soversion,
isinstance(target, build.SharedModule))
# This is only visited when using the Visual Studio toolchain # This is only visited when using the Visual Studio toolchain
if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'): if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'):
commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src)) commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src))
@ -2046,17 +2070,75 @@ rule FORTRAN_DEP_HACK
commands += linker.get_std_link_args() commands += linker.get_std_link_args()
else: else:
raise RuntimeError('Unknown build target type.') raise RuntimeError('Unknown build target type.')
# Link arguments of static libraries are not put in the command line of return commands
# the library. They are instead appended to the command line where
# the static library is used. def generate_link(self, target, outfile, outname, obj_list, linker, extra_args=[]):
if isinstance(target, build.StaticLibrary):
linker_base = 'STATIC'
else:
linker_base = linker.get_language() # Fixme.
if isinstance(target, build.SharedLibrary):
self.generate_shsym(outfile, target)
crstr = ''
if target.is_cross:
crstr = '_CROSS'
linker_rule = linker_base + crstr + '_LINKER'
# Create an empty commands list, and start adding link arguments from
# various sources in the order in which they must override each other
# starting from hard-coded defaults followed by build options and so on.
#
# Once all the linker options have been passed, we will start passing
# libraries and library paths from internal and external sources.
commands = CompilerArgs(linker)
# First, the trivial ones that are impossible to override.
#
# Add linker args for linking this target derived from 'base' build
# options passed on the command-line, in default_options, etc.
# These have the lowest priority.
if not isinstance(target, build.StaticLibrary):
commands += compilers.get_base_link_args(self.environment.coredata.base_options,
linker,
isinstance(target, build.SharedModule))
# Add -nostdlib if needed; can't be overriden
commands += self.get_cross_stdlib_link_args(target, linker)
# Add things like /NOLOGO; usually can't be overriden
commands += linker.get_linker_always_args()
# Add buildtype linker args: optimization level, etc.
commands += linker.get_buildtype_linker_args(self.environment.coredata.get_builtin_option('buildtype'))
# Add /DEBUG and the pdb filename when using MSVC
commands += self.get_link_debugfile_args(linker, target, outname)
# Add link args specific to this BuildTarget type, such as soname args,
# PIC, import library generation, etc.
commands += self.get_target_type_link_args(target, linker)
if not isinstance(target, build.StaticLibrary):
# Add link args added using add_project_link_arguments()
commands += self.build.get_project_link_args(linker, target.subproject)
# Add link args added using add_global_link_arguments()
# These override per-project link arguments
commands += self.build.get_global_link_args(linker)
# Link args added from the env: LDFLAGS. We want these to
# override all the defaults but not the per-target link args.
commands += self.environment.coredata.external_link_args[linker.get_language()]
# Now we will add libraries and library paths from various sources
# Add link args to link to all internal libraries (link_with:) and
# internal dependencies needed by this target.
if linker_base == 'STATIC': if linker_base == 'STATIC':
# Link arguments of static libraries are not put in the command
# line of the library. They are instead appended to the command
# line where the static library is used.
dependencies = [] dependencies = []
else: else:
dependencies = target.get_dependencies() dependencies = target.get_dependencies()
commands += self.build_target_link_arguments(linker, dependencies) commands += self.build_target_link_arguments(linker, dependencies)
# For 'automagic' deps: Boost and GTest. Also dependency('threads').
# pkg-config puts the thread flags itself via `Cflags:`
for d in target.external_deps: for d in target.external_deps:
if d.need_threads(): if d.need_threads():
commands += linker.thread_link_flags() commands += linker.thread_link_flags()
# Only non-static built targets need link args and link dependencies
if not isinstance(target, build.StaticLibrary): if not isinstance(target, build.StaticLibrary):
commands += target.link_args commands += target.link_args
# External deps must be last because target link libraries may depend on them. # External deps must be last because target link libraries may depend on them.
@ -2066,12 +2148,24 @@ rule FORTRAN_DEP_HACK
if isinstance(d, build.StaticLibrary): if isinstance(d, build.StaticLibrary):
for dep in d.get_external_deps(): for dep in d.get_external_deps():
commands += dep.get_link_args() commands += dep.get_link_args()
# Add link args for c_* or cpp_* build options. Currently this only
# adds c_winlibs and cpp_winlibs when building for Windows. This needs
# to be after all internal and external libraries so that unresolved
# symbols from those can be found here. This is needed when the
# *_winlibs that we want to link to are static mingw64 libraries.
commands += linker.get_option_link_args(self.environment.coredata.compiler_options)
# Set runtime-paths so we can run executables without needing to set
# LD_LIBRARY_PATH, etc in the environment. Doesn't work on Windows.
commands += linker.build_rpath_args(self.environment.get_build_dir(), commands += linker.build_rpath_args(self.environment.get_build_dir(),
self.determine_rpath_dirs(target), target.install_rpath) self.determine_rpath_dirs(target),
target.install_rpath)
# Add libraries generated by custom targets
custom_target_libraries = self.get_custom_target_provided_libraries(target) custom_target_libraries = self.get_custom_target_provided_libraries(target)
commands += extra_args commands += extra_args
commands += custom_target_libraries commands += custom_target_libraries
commands = linker.unix_link_flags_to_native(self.dedup_arguments(commands)) # Convert from GCC-style link argument naming to the naming used by the
# current compiler.
commands = commands.to_native()
dep_targets = [self.get_dependency_filename(t) for t in dependencies] dep_targets = [self.get_dependency_filename(t) for t in dependencies]
dep_targets += [os.path.join(self.environment.source_dir, dep_targets += [os.path.join(self.environment.source_dir,
target.subdir, t) for t in target.link_depends] target.subdir, t) for t in target.link_depends]

@ -23,6 +23,7 @@ from .. import dependencies
from .. import mlog from .. import mlog
from .. import compilers from .. import compilers
from ..build import BuildTarget from ..build import BuildTarget
from ..compilers import CompilerArgs
from ..mesonlib import MesonException, File from ..mesonlib import MesonException, File
from ..environment import Environment from ..environment import Environment
@ -426,24 +427,25 @@ class Vs2010Backend(backends.Backend):
pch_out.text = '$(IntDir)$(TargetName)-%s.pch' % lang pch_out.text = '$(IntDir)$(TargetName)-%s.pch' % lang
def add_additional_options(self, lang, parent_node, file_args): def add_additional_options(self, lang, parent_node, file_args):
if len(file_args[lang]) == 0: args = []
# We only need per file options if they were not set per project. for arg in file_args[lang].to_native():
return if arg == '%(AdditionalOptions)':
args = file_args[lang] + ['%(AdditionalOptions)'] args.append(arg)
else:
args.append(self.escape_additional_option(arg))
ET.SubElement(parent_node, "AdditionalOptions").text = ' '.join(args) ET.SubElement(parent_node, "AdditionalOptions").text = ' '.join(args)
def add_preprocessor_defines(self, lang, parent_node, file_defines): def add_preprocessor_defines(self, lang, parent_node, file_defines):
if len(file_defines[lang]) == 0: defines = []
# We only need per file options if they were not set per project. for define in file_defines[lang]:
return if define == '%(PreprocessorDefinitions)':
defines = file_defines[lang] + ['%(PreprocessorDefinitions)'] defines.append(define)
else:
defines.append(self.escape_preprocessor_define(define))
ET.SubElement(parent_node, "PreprocessorDefinitions").text = ';'.join(defines) ET.SubElement(parent_node, "PreprocessorDefinitions").text = ';'.join(defines)
def add_include_dirs(self, lang, parent_node, file_inc_dirs): def add_include_dirs(self, lang, parent_node, file_inc_dirs):
if len(file_inc_dirs[lang]) == 0: dirs = file_inc_dirs[lang]
# We only need per file options if they were not set per project.
return
dirs = file_inc_dirs[lang] + ['%(AdditionalIncludeDirectories)']
ET.SubElement(parent_node, "AdditionalIncludeDirectories").text = ';'.join(dirs) ET.SubElement(parent_node, "AdditionalIncludeDirectories").text = ';'.join(dirs)
@staticmethod @staticmethod
@ -668,90 +670,132 @@ class Vs2010Backend(backends.Backend):
# Arguments, include dirs, defines for all files in the current target # Arguments, include dirs, defines for all files in the current target
target_args = [] target_args = []
target_defines = [] target_defines = []
target_inc_dirs = ['.', self.relpath(self.get_target_private_dir(target), target_inc_dirs = []
self.get_target_dir(target)),
proj_to_src_dir] + generated_files_include_dirs
# Arguments, include dirs, defines passed to individual files in # Arguments, include dirs, defines passed to individual files in
# a target; perhaps because the args are language-specific # a target; perhaps because the args are language-specific
file_args = dict((lang, []) for lang in target.compilers) #
# file_args is also later split out into defines and include_dirs in
# case someone passed those in there
file_args = dict((lang, CompilerArgs(comp)) for lang, comp in target.compilers.items())
file_defines = dict((lang, []) for lang in target.compilers) file_defines = dict((lang, []) for lang in target.compilers)
file_inc_dirs = dict((lang, []) for lang in target.compilers) file_inc_dirs = dict((lang, []) for lang in target.compilers)
for l, args in self.environment.coredata.external_args.items(): # The order in which these compile args are added must match
# generate_single_compile() and generate_basic_compiler_args()
for l, comp in target.compilers.items():
if l in file_args:
file_args[l] += compilers.get_base_compile_args(self.environment.coredata.base_options, comp)
file_args[l] += comp.get_option_compile_args(self.environment.coredata.compiler_options)
# Add compile args added using add_project_arguments()
for l, args in self.build.projects_args.get(target.subproject, {}).items():
if l in file_args: if l in file_args:
file_args[l] += args file_args[l] += args
# Add compile args added using add_global_arguments()
# These override per-project arguments
for l, args in self.build.global_args.items(): for l, args in self.build.global_args.items():
if l in file_args: if l in file_args:
file_args[l] += args file_args[l] += args
for l, args in self.build.projects_args.get(target.subproject, {}).items(): # Compile args added from the env: CFLAGS/CXXFLAGS, etc. We want these
# to override all the defaults, but not the per-target compile args.
for l, args in self.environment.coredata.external_args.items():
if l in file_args: if l in file_args:
file_args[l] += args file_args[l] += args
for args in file_args.values():
# This is where Visual Studio will insert target_args, target_defines,
# etc, which are added later from external deps (see below).
args += ['%(AdditionalOptions)', '%(PreprocessorDefinitions)', '%(AdditionalIncludeDirectories)']
# Add include dirs from the `include_directories:` kwarg on the target
# and from `include_directories:` of internal deps of the target.
#
# Target include dirs should override internal deps include dirs.
#
# Include dirs from internal deps should override include dirs from
# external deps.
# These are per-target, but we still add them as per-file because we
# need them to be looked in first.
for d in target.get_include_dirs():
for i in d.get_incdirs():
curdir = os.path.join(d.get_curdir(), i)
args.append('-I' + self.relpath(curdir, target.subdir)) # build dir
args.append('-I' + os.path.join(proj_to_src_root, curdir)) # src dir
for i in d.get_extra_build_dirs():
curdir = os.path.join(d.get_curdir(), i)
args.append('-I' + self.relpath(curdir, target.subdir)) # build dir
# Add per-target compile args, f.ex, `c_args : ['/DFOO']`. We set these
# near the end since these are supposed to override everything else.
for l, args in target.extra_args.items(): for l, args in target.extra_args.items():
if l in file_args: if l in file_args:
file_args[l] += compiler.unix_compile_flags_to_native(args) file_args[l] += args
for l, comp in target.compilers.items(): # The highest priority includes. In order of directory search:
if l in file_args: # target private dir, target build dir, generated sources include dirs,
file_args[l] += comp.get_option_compile_args(self.environment.coredata.compiler_options) # target source dir
for args in file_args.values():
t_inc_dirs = ['.', self.relpath(self.get_target_private_dir(target),
self.get_target_dir(target))]
t_inc_dirs += generated_files_include_dirs + [proj_to_src_dir]
args += ['-I' + arg for arg in t_inc_dirs]
# Split preprocessor defines and include directories out of the list of
# all extra arguments. The rest go into %(AdditionalOptions).
for l, args in file_args.items():
for arg in args[:]:
if arg.startswith(('-D', '/D')) or arg == '%(PreprocessorDefinitions)':
file_args[l].remove(arg)
# Don't escape the marker
if arg == '%(PreprocessorDefinitions)':
define = arg
else:
define = arg[2:]
# De-dup
if define in file_defines[l]:
file_defines[l].remove(define)
file_defines[l].append(define)
elif arg.startswith(('-I', '/I')) or arg == '%(AdditionalIncludeDirectories)':
file_args[l].remove(arg)
# Don't escape the marker
if arg == '%(AdditionalIncludeDirectories)':
inc_dir = arg
else:
inc_dir = arg[2:]
# De-dup
if inc_dir not in file_inc_dirs[l]:
file_inc_dirs[l].append(inc_dir)
# Split compile args needed to find external dependencies
# Link args are added while generating the link command
for d in target.get_external_deps(): for d in target.get_external_deps():
# Cflags required by external deps might have UNIX-specific flags, # Cflags required by external deps might have UNIX-specific flags,
# so filter them out if needed # so filter them out if needed
d_compile_args = compiler.unix_compile_flags_to_native(d.get_compile_args()) d_compile_args = compiler.unix_args_to_native(d.get_compile_args())
for arg in d_compile_args: for arg in d_compile_args:
if arg.startswith(('-D', '/D')): if arg.startswith(('-D', '/D')):
define = arg[2:] define = arg[2:]
# De-dup # De-dup
if define not in target_defines: if define in target_defines:
target_defines.append(define) target_defines.remove(define)
target_defines.append(define)
elif arg.startswith(('-I', '/I')): elif arg.startswith(('-I', '/I')):
inc_dir = arg[2:] inc_dir = arg[2:]
# De-dup # De-dup
if inc_dir not in target_inc_dirs: if inc_dir not in target_inc_dirs:
target_inc_dirs.append(inc_dir) target_inc_dirs.append(inc_dir)
else: else:
# De-dup target_args.append(arg)
if arg not in target_args:
target_args.append(arg)
# Split preprocessor defines and include directories out of the list of
# all extra arguments. The rest go into %(AdditionalOptions).
for l, args in file_args.items():
file_args[l] = []
for arg in args:
if arg.startswith(('-D', '/D')):
define = self.escape_preprocessor_define(arg[2:])
# De-dup
if define not in file_defines[l]:
file_defines[l].append(define)
elif arg.startswith(('-I', '/I')):
inc_dir = arg[2:]
# De-dup
if inc_dir not in file_inc_dirs[l]:
file_inc_dirs[l].append(inc_dir)
else:
file_args[l].append(self.escape_additional_option(arg))
languages += gen_langs languages += gen_langs
if len(target_args) > 0: if len(target_args) > 0:
target_args.append('%(AdditionalOptions)') target_args.append('%(AdditionalOptions)')
ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(target_args) ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(target_args)
for d in target.include_dirs:
for i in d.incdirs:
curdir = os.path.join(d.curdir, i)
target_inc_dirs.append(self.relpath(curdir, target.subdir)) # build dir
target_inc_dirs.append(os.path.join(proj_to_src_root, curdir)) # src dir
for i in d.get_extra_build_dirs():
curdir = os.path.join(d.curdir, i)
target_inc_dirs.append(self.relpath(curdir, target.subdir)) # build dir
target_inc_dirs.append('%(AdditionalIncludeDirectories)') target_inc_dirs.append('%(AdditionalIncludeDirectories)')
ET.SubElement(clconf, 'AdditionalIncludeDirectories').text = ';'.join(target_inc_dirs) ET.SubElement(clconf, 'AdditionalIncludeDirectories').text = ';'.join(target_inc_dirs)
target_defines.append('%(PreprocessorDefinitions)') target_defines.append('%(PreprocessorDefinitions)')
ET.SubElement(clconf, 'PreprocessorDefinitions').text = ';'.join(target_defines) ET.SubElement(clconf, 'PreprocessorDefinitions').text = ';'.join(target_defines)
rebuild = ET.SubElement(clconf, 'MinimalRebuild') ET.SubElement(clconf, 'MinimalRebuild').text = 'true'
rebuild.text = 'true' ET.SubElement(clconf, 'FunctionLevelLinking').text = 'true'
funclink = ET.SubElement(clconf, 'FunctionLevelLinking')
funclink.text = 'true'
pch_node = ET.SubElement(clconf, 'PrecompiledHeader') pch_node = ET.SubElement(clconf, 'PrecompiledHeader')
if self.environment.coredata.get_builtin_option('werror'):
ET.SubElement(clconf, 'TreatWarningAsError').text = 'true'
# Note: SuppressStartupBanner is /NOLOGO and is 'true' by default
pch_sources = {} pch_sources = {}
for lang in ['c', 'cpp']: for lang in ['c', 'cpp']:
pch = target.get_pch(lang) pch = target.get_pch(lang)
@ -773,19 +817,27 @@ class Vs2010Backend(backends.Backend):
resourcecompile = ET.SubElement(compiles, 'ResourceCompile') resourcecompile = ET.SubElement(compiles, 'ResourceCompile')
ET.SubElement(resourcecompile, 'PreprocessorDefinitions') ET.SubElement(resourcecompile, 'PreprocessorDefinitions')
# Linker options
link = ET.SubElement(compiles, 'Link') link = ET.SubElement(compiles, 'Link')
# Put all language args here, too. extra_link_args = CompilerArgs(compiler)
extra_link_args = compiler.get_option_link_args(self.environment.coredata.compiler_options)
# FIXME: Can these buildtype linker args be added as tags in the # FIXME: Can these buildtype linker args be added as tags in the
# vcxproj file (similar to buildtype compiler args) instead of in # vcxproj file (similar to buildtype compiler args) instead of in
# AdditionalOptions? # AdditionalOptions?
extra_link_args += compiler.get_buildtype_linker_args(self.buildtype) extra_link_args += compiler.get_buildtype_linker_args(self.buildtype)
for l in self.environment.coredata.external_link_args.values():
extra_link_args += l
if not isinstance(target, build.StaticLibrary): if not isinstance(target, build.StaticLibrary):
extra_link_args += target.link_args
if isinstance(target, build.SharedModule): if isinstance(target, build.SharedModule):
extra_link_args += compiler.get_std_shared_module_link_args() extra_link_args += compiler.get_std_shared_module_link_args()
# Add link args added using add_project_link_arguments()
extra_link_args += self.build.get_project_link_args(compiler, target.subproject)
# Add link args added using add_global_link_arguments()
# These override per-project link arguments
extra_link_args += self.build.get_global_link_args(compiler)
# Link args added from the env: LDFLAGS. We want these to
# override all the defaults but not the per-target link args.
extra_link_args += self.environment.coredata.external_link_args[compiler.get_language()]
# Only non-static built targets need link args and link dependencies
extra_link_args += target.link_args
# External deps must be last because target link libraries may depend on them. # External deps must be last because target link libraries may depend on them.
for dep in target.get_external_deps(): for dep in target.get_external_deps():
extra_link_args += dep.get_link_args() extra_link_args += dep.get_link_args()
@ -793,8 +845,13 @@ class Vs2010Backend(backends.Backend):
if isinstance(d, build.StaticLibrary): if isinstance(d, build.StaticLibrary):
for dep in d.get_external_deps(): for dep in d.get_external_deps():
extra_link_args += dep.get_link_args() extra_link_args += dep.get_link_args()
extra_link_args = compiler.unix_link_flags_to_native(extra_link_args) # Add link args for c_* or cpp_* build options. Currently this only
(additional_libpaths, additional_links, extra_link_args) = self.split_link_args(extra_link_args) # adds c_winlibs and cpp_winlibs when building for Windows. This needs
# to be after all internal and external libraries so that unresolved
# symbols from those can be found here. This is needed when the
# *_winlibs that we want to link to are static mingw64 libraries.
extra_link_args += compiler.get_option_link_args(self.environment.coredata.compiler_options)
(additional_libpaths, additional_links, extra_link_args) = self.split_link_args(extra_link_args.to_native())
if len(extra_link_args) > 0: if len(extra_link_args) > 0:
extra_link_args.append('%(AdditionalOptions)') extra_link_args.append('%(AdditionalOptions)')
ET.SubElement(link, "AdditionalOptions").text = ' '.join(extra_link_args) ET.SubElement(link, "AdditionalOptions").text = ' '.join(extra_link_args)

@ -170,6 +170,10 @@ class IncludeDirs:
else: else:
self.extra_build_dirs = extra_build_dirs self.extra_build_dirs = extra_build_dirs
def __repr__(self):
r = '<{} {}/{}>'
return r.format(self.__class__.__name__, self.curdir, self.incdirs)
def get_curdir(self): def get_curdir(self):
return self.curdir return self.curdir
@ -585,14 +589,16 @@ class BuildTarget(Target):
for i in self.link_depends: for i in self.link_depends:
if not isinstance(i, str): if not isinstance(i, str):
raise InvalidArguments('Link_depends arguments must be strings.') raise InvalidArguments('Link_depends arguments must be strings.')
inclist = kwargs.get('include_directories', [])
if not isinstance(inclist, list):
inclist = [inclist]
self.add_include_dirs(inclist)
deplist = kwargs.get('dependencies', []) deplist = kwargs.get('dependencies', [])
if not isinstance(deplist, list): if not isinstance(deplist, list):
deplist = [deplist] deplist = [deplist]
self.add_deps(deplist) self.add_deps(deplist)
# Target-specific include dirs must be added after include dirs from
# internal deps (added inside self.add_deps()) to override correctly.
inclist = kwargs.get('include_directories', [])
if not isinstance(inclist, list):
inclist = [inclist]
self.add_include_dirs(inclist)
self.custom_install_dir = kwargs.get('install_dir', None) self.custom_install_dir = kwargs.get('install_dir', None)
if self.custom_install_dir is not None: if self.custom_install_dir is not None:
if not isinstance(self.custom_install_dir, str): if not isinstance(self.custom_install_dir, str):

@ -323,6 +323,175 @@ class RunResult:
self.stdout = stdout self.stdout = stdout
self.stderr = stderr self.stderr = stderr
class CompilerArgs(list):
'''
Class derived from list() that manages a list of compiler arguments. Should
be used while constructing compiler arguments from various sources. Can be
operated with ordinary lists, so this does not need to be used everywhere.
All arguments must be inserted and stored in GCC-style (-lfoo, -Idir, etc)
and can converted to the native type of each compiler by using the
.to_native() method to which you must pass an instance of the compiler or
the compiler class.
New arguments added to this class (either with .append(), .extend(), or +=)
are added in a way that ensures that they override previous arguments.
For example:
>>> a = ['-Lfoo', '-lbar']
>>> a += ['-Lpho', '-lbaz']
>>> print(a)
['-Lpho', '-Lfoo', '-lbar', '-lbaz']
Arguments will also be de-duped if they can be de-duped safely.
Note that because of all this, this class is not commutative and does not
preserve the order of arguments if it is safe to not. For example:
>>> ['-Ifoo', '-Ibar'] + ['-Ifez', '-Ibaz', '-Werror']
['-Ifez', '-Ibaz', '-Ifoo', '-Ibar', '-Werror']
>>> ['-Ifez', '-Ibaz', '-Werror'] + ['-Ifoo', '-Ibar']
['-Ifoo', '-Ibar', '-Ifez', '-Ibaz', '-Werror']
'''
# NOTE: currently this class is only for C-like compilers, but it can be
# extended to other languages easily. Just move the following to the
# compiler class and initialize when self.compiler is set.
# Arg prefixes that override by prepending instead of appending
prepend_prefixes = ('-I', '-L')
# Arg prefixes and args that must be de-duped by returning 2
dedup2_prefixes = ('-I', '-L', '-D')
dedup2_args = ()
# Arg prefixes and args that must be de-duped by returning 1
dedup1_prefixes = ()
dedup1_args = ('-c', '-S', '-E', '-pipe')
compiler = None
def _check_args(self, args):
cargs = []
if len(args) > 2:
raise TypeError("CompilerArgs() only accepts at most 2 arguments: "
"The compiler, and optionally an initial list")
elif len(args) == 0:
return cargs
elif len(args) == 1:
if isinstance(args[0], (Compiler, StaticLinker)):
self.compiler = args[0]
else:
raise TypeError("you must pass a Compiler instance as one of "
"the arguments")
elif len(args) == 2:
if isinstance(args[0], (Compiler, StaticLinker)):
self.compiler = args[0]
cargs = args[1]
elif isinstance(args[1], (Compiler, StaticLinker)):
cargs = args[0]
self.compiler = args[1]
else:
raise TypeError("you must pass a Compiler instance as one of "
"the two arguments")
else:
raise AssertionError('Not reached')
return cargs
def __init__(self, *args):
super().__init__(self._check_args(args))
@classmethod
def _can_dedup(cls, arg):
'''
Returns whether the argument can be safely de-duped. This is dependent
on two things:
a) Whether an argument can be 'overriden' by a later argument. For
example, -DFOO defines FOO and -UFOO undefines FOO. In this case, we
can safely remove the previous occurance and add a new one. The same
is true for include paths and library paths with -I and -L. For
these we return `2`. See `dedup2_prefixes` and `dedup2_args`.
b) Arguments that once specifie cannot be undone, such as `-c` or
`-pipe`. New instances of these can be completely skipped. For these
we return `1`. See `dedup1_prefixes` and `dedup1_args`.
c) Whether it matters where or how many times on the command-line
a particular argument is present. This can matter for symbol
resolution in static or shared libraries, so we cannot de-dup or
reorder them. For these we return `0`. This is the default.
'''
if arg.startswith(cls.dedup2_prefixes) or arg in cls.dedup2_args:
return 2
if arg.startswith(cls.dedup1_prefixes) or arg in cls.dedup1_args:
return 1
return 0
@classmethod
def _should_prepend(cls, arg):
if arg.startswith(cls.prepend_prefixes):
return True
return False
def to_native(self):
return self.compiler.unix_args_to_native(self)
def __add__(self, args):
new = CompilerArgs(self, self.compiler)
new += args
return new
def __iadd__(self, args):
'''
Add two CompilerArgs while taking into account overriding of arguments
and while preserving the order of arguments as much as possible
'''
pre = []
post = []
if not isinstance(args, list):
raise TypeError('can only concatenate list (not "{}") to list'.format(args))
for arg in args:
# If the argument can be de-duped, do it either by removing the
# previous occurance of it and adding a new one, or not adding the
# new occurance.
dedup = self._can_dedup(arg)
if dedup == 1:
# Argument already exists and adding a new instance is useless
if arg in self or arg in pre or arg in post:
continue
if dedup == 2:
# Remove all previous occurances of the arg and add it anew
if arg in self:
self.remove(arg)
if arg in pre:
pre.remove(arg)
if arg in post:
post.remove(arg)
if self._should_prepend(arg):
pre.append(arg)
else:
post.append(arg)
# Insert at the beginning
self[:0] = pre
# Append to the end
super().__iadd__(post)
return self
def __radd__(self, args):
new = CompilerArgs(args, self.compiler)
new += self
return new
def __mul__(self, args):
raise TypeError("can't multiply compiler arguments")
def __imul__(self, args):
raise TypeError("can't multiply compiler arguments")
def __rmul__(self, args):
raise TypeError("can't multiply compiler arguments")
def append(self, arg):
self.__iadd__([arg])
def extend(self, args):
self.__iadd__(args)
class Compiler: class Compiler:
def __init__(self, exelist, version): def __init__(self, exelist, version):
if isinstance(exelist, str): if isinstance(exelist, str):
@ -412,11 +581,8 @@ class Compiler:
def has_function(self, *args, **kwargs): def has_function(self, *args, **kwargs):
raise EnvironmentException('Language %s does not support function checks.' % self.language) raise EnvironmentException('Language %s does not support function checks.' % self.language)
def unix_link_flags_to_native(self, args): @classmethod
"Always returns a copy that can be independently mutated" def unix_args_to_native(cls, args):
return args[:]
def unix_compile_flags_to_native(self, args):
"Always returns a copy that can be independently mutated" "Always returns a copy that can be independently mutated"
return args[:] return args[:]
@ -435,7 +601,7 @@ class Compiler:
self.language)) self.language))
def get_cross_extra_flags(self, environment, *, compile, link): def get_cross_extra_flags(self, environment, *, compile, link):
extra_flags = [] extra_flags = CompilerArgs(self)
if self.is_cross and environment: if self.is_cross and environment:
if 'properties' in environment.cross_info.config: if 'properties' in environment.cross_info.config:
lang_args_key = self.language + '_args' lang_args_key = self.language + '_args'
@ -474,7 +640,7 @@ class Compiler:
output = self._get_compile_output(tmpdirname, mode) output = self._get_compile_output(tmpdirname, mode)
# Construct the compiler command-line # Construct the compiler command-line
commands = self.get_exelist() commands = CompilerArgs(self)
commands.append(srcname) commands.append(srcname)
commands += extra_args commands += extra_args
commands += self.get_always_args() commands += self.get_always_args()
@ -485,6 +651,8 @@ class Compiler:
commands += self.get_preprocess_only_args() commands += self.get_preprocess_only_args()
else: else:
commands += self.get_output_args(output) commands += self.get_output_args(output)
# Generate full command-line with the exelist
commands = self.get_exelist() + commands.to_native()
mlog.debug('Running compile:') mlog.debug('Running compile:')
mlog.debug('Working directory: ', tmpdirname) mlog.debug('Working directory: ', tmpdirname)
mlog.debug('Command line: ', ' '.join(commands), '\n') mlog.debug('Command line: ', ' '.join(commands), '\n')
@ -663,7 +831,7 @@ class CCompiler(Compiler):
mlog.debug('Sanity testing ' + self.language + ' compiler:', ' '.join(self.exelist)) mlog.debug('Sanity testing ' + self.language + ' compiler:', ' '.join(self.exelist))
mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) mlog.debug('Is cross compiler: %s.' % str(self.is_cross))
extra_flags = [] extra_flags = CompilerArgs(self)
source_name = os.path.join(work_dir, sname) source_name = os.path.join(work_dir, sname)
binname = sname.rsplit('.', 1)[0] binname = sname.rsplit('.', 1)[0]
if self.is_cross: if self.is_cross:
@ -742,51 +910,29 @@ class CCompiler(Compiler):
}}''' }}'''
return self.compiles(t.format(**fargs), env, extra_args, dependencies) return self.compiles(t.format(**fargs), env, extra_args, dependencies)
@staticmethod
def _override_args(args, override):
'''
Add @override to @args in such a way that arguments are overriden
correctly.
We want the include directories to be added first (since they are
chosen left-to-right) and all other arguments later (since they
override previous arguments or add to a list that's chosen
right-to-left).
'''
before_args = []
after_args = []
for arg in override:
if arg.startswith(('-I', '/I')):
before_args.append(arg)
else:
after_args.append(arg)
return before_args + args + after_args
def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'): def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'):
if extra_args is None: if extra_args is None:
extra_args = [] extra_args = []
if isinstance(extra_args, str): elif isinstance(extra_args, str):
extra_args = [extra_args] extra_args = [extra_args]
if dependencies is None: if dependencies is None:
dependencies = [] dependencies = []
elif not isinstance(dependencies, list): elif not isinstance(dependencies, list):
dependencies = [dependencies] dependencies = [dependencies]
# Add compile flags needed by dependencies after converting to the # Add compile flags needed by dependencies
# native type of the selected compiler args = CompilerArgs(self)
cargs = [a for d in dependencies for a in d.get_compile_args()] for d in dependencies:
args = self.unix_link_flags_to_native(cargs) args += d.get_compile_args()
# Read c_args/cpp_args/etc from the cross-info file (if needed) # Read c_args/cpp_args/etc from the cross-info file (if needed)
args += self.get_cross_extra_flags(env, compile=True, link=False) args += self.get_cross_extra_flags(env, compile=True, link=False)
# Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env
# We assume that the user has ensured these are compiler-specific # We assume that the user has ensured these are compiler-specific
args += env.coredata.external_args[self.language] args += env.coredata.external_args[self.language]
# Append extra_args to the compiler check args such that it overrides args += self.get_compiler_check_args()
extra_args = self._override_args(self.get_compiler_check_args(), extra_args) # extra_args must override all other arguments, so we add them last
extra_args = self.unix_link_flags_to_native(extra_args) args += extra_args
# Append both to the compiler args such that they override them
args = self._override_args(args, extra_args)
# We only want to compile; not link # We only want to compile; not link
with self.compile(code, args, mode) as p: with self.compile(code, args.to_native(), mode) as p:
return p.returncode == 0 return p.returncode == 0
def _links_wrapper(self, code, env, extra_args, dependencies): def _links_wrapper(self, code, env, extra_args, dependencies):
@ -799,11 +945,11 @@ class CCompiler(Compiler):
dependencies = [] dependencies = []
elif not isinstance(dependencies, list): elif not isinstance(dependencies, list):
dependencies = [dependencies] dependencies = [dependencies]
# Add compile and link flags needed by dependencies after converting to # Add compile and link flags needed by dependencies
# the native type of the selected compiler args = CompilerArgs(self)
cargs = [a for d in dependencies for a in d.get_compile_args()] for d in dependencies:
link_args = [a for d in dependencies for a in d.get_link_args()] args += d.get_compile_args()
args = self.unix_link_flags_to_native(cargs + link_args) args += d.get_link_args()
# Select a CRT if needed since we're linking # Select a CRT if needed since we're linking
args += self.get_linker_debug_crt_args() args += self.get_linker_debug_crt_args()
# Read c_args/c_link_args/cpp_args/cpp_link_args/etc from the # Read c_args/c_link_args/cpp_args/cpp_link_args/etc from the
@ -812,12 +958,11 @@ class CCompiler(Compiler):
# Add LDFLAGS from the env. We assume that the user has ensured these # Add LDFLAGS from the env. We assume that the user has ensured these
# are compiler-specific # are compiler-specific
args += env.coredata.external_link_args[self.language] args += env.coredata.external_link_args[self.language]
# Append extra_args to the compiler check args such that it overrides # Add compiler check args such that they override
extra_args = self._override_args(self.get_compiler_check_args(), extra_args) args += self.get_compiler_check_args()
extra_args = self.unix_link_flags_to_native(extra_args) # extra_args must override all other arguments, so we add them last
# Append both to the compiler args such that they override them args += extra_args
args = self._override_args(args, extra_args) return self.compile(code, args.to_native())
return self.compile(code, args)
def links(self, code, env, extra_args=None, dependencies=None): def links(self, code, env, extra_args=None, dependencies=None):
with self._links_wrapper(code, env, extra_args, dependencies) as p: with self._links_wrapper(code, env, extra_args, dependencies) as p:
@ -1179,6 +1324,9 @@ class CPPCompiler(CCompiler):
self.language = 'cpp' self.language = 'cpp'
CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) CCompiler.__init__(self, exelist, version, is_cross, exe_wrap)
def get_no_stdinc_args(self):
return ['-nostdinc++']
def sanity_check(self, work_dir, environment): def sanity_check(self, work_dir, environment):
code = 'class breakCCompiler;int main(int argc, char **argv) { return 0; }\n' code = 'class breakCCompiler;int main(int argc, char **argv) { return 0; }\n'
return self.sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code) return self.sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code)
@ -1686,7 +1834,8 @@ class DCompiler(Compiler):
paths = paths + ':' + padding paths = paths + ':' + padding
return ['-L-rpath={}'.format(paths)] return ['-L-rpath={}'.format(paths)]
def translate_args_to_nongnu(self, args): @classmethod
def translate_args_to_nongnu(cls, args):
dcargs = [] dcargs = []
# Translate common arguments to flags the LDC/DMD compilers # Translate common arguments to flags the LDC/DMD compilers
# can understand. # can understand.
@ -1802,11 +1951,9 @@ class LLVMDCompiler(DCompiler):
# -L is for the compiler, telling it to pass the second -L to the linker. # -L is for the compiler, telling it to pass the second -L to the linker.
return ['-L-L' + dirname] return ['-L-L' + dirname]
def unix_link_flags_to_native(self, args): @classmethod
return self.translate_args_to_nongnu(args) def unix_args_to_native(cls, args):
return cls.translate_args_to_nongnu(args)
def unix_compile_flags_to_native(self, args):
return self.translate_args_to_nongnu(args)
class DmdDCompiler(DCompiler): class DmdDCompiler(DCompiler):
def __init__(self, exelist, version, is_cross): def __init__(self, exelist, version, is_cross):
@ -1854,11 +2001,9 @@ class DmdDCompiler(DCompiler):
def get_std_shared_lib_link_args(self): def get_std_shared_lib_link_args(self):
return ['-shared', '-defaultlib=libphobos2.so'] return ['-shared', '-defaultlib=libphobos2.so']
def unix_link_flags_to_native(self, args): @classmethod
return self.translate_args_to_nongnu(args) def unix_args_to_native(cls, args):
return cls.translate_args_to_nongnu(args)
def unix_compile_flags_to_native(self, args):
return self.translate_args_to_nongnu(args)
class VisualStudioCCompiler(CCompiler): class VisualStudioCCompiler(CCompiler):
std_warn_args = ['/W3'] std_warn_args = ['/W3']
@ -1978,9 +2123,14 @@ class VisualStudioCCompiler(CCompiler):
def get_option_link_args(self, options): def get_option_link_args(self, options):
return options['c_winlibs'].value[:] return options['c_winlibs'].value[:]
def unix_link_flags_to_native(self, args): @classmethod
def unix_args_to_native(cls, args):
result = [] result = []
for i in args: for i in args:
# -mms-bitfields is specific to MinGW-GCC
# -pthread is only valid for GCC
if i in ('-mms-bitfields', '-pthread'):
continue
if i.startswith('-L'): if i.startswith('-L'):
i = '/LIBPATH:' + i[2:] i = '/LIBPATH:' + i[2:]
# Translate GNU-style -lfoo library name to the import library # Translate GNU-style -lfoo library name to the import library
@ -1998,16 +2148,6 @@ class VisualStudioCCompiler(CCompiler):
result.append(i) result.append(i)
return result return result
def unix_compile_flags_to_native(self, args):
result = []
for i in args:
# -mms-bitfields is specific to MinGW-GCC
# -pthread is only valid for GCC
if i in ('-mms-bitfields', '-pthread'):
continue
result.append(i)
return result
def get_werror_args(self): def get_werror_args(self):
return ['/WX'] return ['/WX']
@ -2795,8 +2935,10 @@ class NAGFortranCompiler(FortranCompiler):
def get_warn_args(self, level): def get_warn_args(self, level):
return NAGFortranCompiler.std_warn_args return NAGFortranCompiler.std_warn_args
class StaticLinker:
pass
class VisualStudioLinker: class VisualStudioLinker(StaticLinker):
always_args = ['/NOLOGO'] always_args = ['/NOLOGO']
def __init__(self, exelist): def __init__(self, exelist):
@ -2832,18 +2974,16 @@ class VisualStudioLinker:
def get_option_link_args(self, options): def get_option_link_args(self, options):
return [] return []
def unix_link_flags_to_native(self, args): @classmethod
return args[:] def unix_args_to_native(cls, args):
return VisualStudioCCompiler.unix_args_to_native(args)
def unix_compile_flags_to_native(self, args):
return args[:]
def get_link_debugfile_args(self, targetfile): def get_link_debugfile_args(self, targetfile):
pdbarr = targetfile.split('.')[:-1] pdbarr = targetfile.split('.')[:-1]
pdbarr += ['pdb'] pdbarr += ['pdb']
return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)] return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)]
class ArLinker: class ArLinker(StaticLinker):
def __init__(self, exelist): def __init__(self, exelist):
self.exelist = exelist self.exelist = exelist
@ -2885,10 +3025,8 @@ class ArLinker:
def get_option_link_args(self, options): def get_option_link_args(self, options):
return [] return []
def unix_link_flags_to_native(self, args): @classmethod
return args[:] def unix_args_to_native(cls, args):
def unix_compile_flags_to_native(self, args):
return args[:] return args[:]
def get_link_debugfile_args(self, targetfile): def get_link_debugfile_args(self, targetfile):

@ -117,7 +117,9 @@ class CoreData:
self.user_options = {} self.user_options = {}
self.compiler_options = {} self.compiler_options = {}
self.base_options = {} self.base_options = {}
self.external_args = {} # These are set from "the outside" with e.g. mesonconf # These two, external_*args, are set via env vars CFLAGS, LDFLAGS, etc
# but only when not cross-compiling.
self.external_args = {}
self.external_link_args = {} self.external_link_args = {}
if options.cross_file is not None: if options.cross_file is not None:
self.cross_file = os.path.join(os.getcwd(), options.cross_file) self.cross_file = os.path.join(os.getcwd(), options.cross_file)

@ -59,7 +59,7 @@ class Dependency:
def get_name(self): def get_name(self):
return self.name return self.name
def get_exe_args(self): def get_exe_args(self, compiler):
return [] return []
def need_threads(self): def need_threads(self):
@ -1045,16 +1045,14 @@ class QtBaseDependency(Dependency):
def found(self): def found(self):
return self.is_found return self.is_found
def get_exe_args(self): def get_exe_args(self, compiler):
# Originally this was -fPIE but nowadays the default # Originally this was -fPIE but nowadays the default
# for upstream and distros seems to be -reduce-relocations # for upstream and distros seems to be -reduce-relocations
# which requires -fPIC. This may cause a performance # which requires -fPIC. This may cause a performance
# penalty when using self-built Qt or on platforms # penalty when using self-built Qt or on platforms
# where -fPIC is not required. If this is an issue # where -fPIC is not required. If this is an issue
# for you, patches are welcome. # for you, patches are welcome.
if mesonlib.is_linux(): return compiler.get_pic_args()
return ['-fPIC']
return []
class Qt5Dependency(QtBaseDependency): class Qt5Dependency(QtBaseDependency):
def __init__(self, env, kwargs): def __init__(self, env, kwargs):

@ -14,11 +14,13 @@
# limitations under the License. # limitations under the License.
import stat import stat
import shlex
import unittest, os, sys, shutil, time import unittest, os, sys, shutil, time
import subprocess import subprocess
import re, json import re, json
import tempfile import tempfile
from glob import glob from glob import glob
import mesonbuild.compilers
import mesonbuild.environment import mesonbuild.environment
import mesonbuild.mesonlib import mesonbuild.mesonlib
from mesonbuild.environment import detect_ninja, Environment from mesonbuild.environment import detect_ninja, Environment
@ -95,6 +97,62 @@ class InternalTests(unittest.TestCase):
stat.S_IRWXU | stat.S_ISUID | stat.S_IRWXU | stat.S_ISUID |
stat.S_IRGRP | stat.S_IXGRP) stat.S_IRGRP | stat.S_IXGRP)
def test_compiler_args_class(self):
cargsfunc = mesonbuild.compilers.CompilerArgs
c = mesonbuild.environment.CCompiler([], 'fake', False)
# Test that bad initialization fails
self.assertRaises(TypeError, cargsfunc, [])
self.assertRaises(TypeError, cargsfunc, [], [])
self.assertRaises(TypeError, cargsfunc, c, [], [])
# Test that empty initialization works
a = cargsfunc(c)
self.assertEqual(a, [])
# Test that list initialization works
a = cargsfunc(['-I.', '-I..'], c)
self.assertEqual(a, ['-I.', '-I..'])
# Test that there is no de-dup on initialization
self.assertEqual(cargsfunc(['-I.', '-I.'], c), ['-I.', '-I.'])
## Test that appending works
a.append('-I..')
self.assertEqual(a, ['-I..', '-I.'])
a.append('-O3')
self.assertEqual(a, ['-I..', '-I.', '-O3'])
## Test that in-place addition works
a += ['-O2', '-O2']
self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2', '-O2'])
# Test that removal works
a.remove('-O2')
self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2'])
# Test that de-dup happens on addition
a += ['-Ifoo', '-Ifoo']
self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
# .extend() is just +=, so we don't test it
## Test that addition works
# Test that adding a list with just one old arg works and yields the same array
a = a + ['-Ifoo']
self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
# Test that adding a list with one arg new and one old works
a = a + ['-Ifoo', '-Ibaz']
self.assertEqual(a, ['-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2'])
# Test that adding args that must be prepended and appended works
a = a + ['-Ibar', '-Wall']
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
## Test that reflected addition works
# Test that adding to a list with just one old arg works and DOES NOT yield the same array
a = ['-Ifoo'] + a
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
# Test that adding to a list with just one new arg that is not pre-pended works
a = ['-Werror'] + a
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Werror', '-O3', '-O2', '-Wall'])
# Test that adding to a list with two new args preserves the order
a = ['-Ldir', '-Lbah'] + a
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
class LinuxlikeTests(unittest.TestCase): class LinuxlikeTests(unittest.TestCase):
def setUp(self): def setUp(self):
@ -416,7 +474,7 @@ class LinuxlikeTests(unittest.TestCase):
cmd = cmd[1:] cmd = cmd[1:]
# Verify that -I flags from the `args` kwarg are first # Verify that -I flags from the `args` kwarg are first
# This is set in the '43 has function' test case # This is set in the '43 has function' test case
self.assertEqual(cmd[2], '-I/tmp') self.assertEqual(cmd[1], '-I/tmp')
# Verify that -O3 set via the environment is overriden by -O0 # Verify that -O3 set via the environment is overriden by -O0
Oargs = [arg for arg in cmd if arg.startswith('-O')] Oargs = [arg for arg in cmd if arg.startswith('-O')]
self.assertEqual(Oargs, [Oflag, '-O0']) self.assertEqual(Oargs, [Oflag, '-O0'])
@ -641,6 +699,38 @@ class LinuxlikeTests(unittest.TestCase):
# The chown failed nonfatally if we're not root # The chown failed nonfatally if we're not root
self.assertEqual(0, statf.st_uid) self.assertEqual(0, statf.st_uid)
def test_internal_include_order(self):
testdir = os.path.join(self.common_test_dir, '138 include order')
self.init(testdir)
for cmd in self.get_compdb():
if cmd['file'].endswith('/main.c'):
cmd = cmd['command']
break
else:
raise Exception('Could not find main.c command')
incs = [a for a in shlex.split(cmd) if a.startswith("-I")]
self.assertEqual(len(incs), 8)
# target private dir
self.assertEqual(incs[0], "-Isub4/someexe@exe")
# target build subdir
self.assertEqual(incs[1], "-Isub4")
# target source subdir
msg = "{!r} does not end with '/sub4'".format(incs[2])
self.assertTrue(incs[2].endswith("/sub4"), msg)
# include paths added via per-target c_args: ['-I'...]
msg = "{!r} does not end with '/sub3'".format(incs[3])
self.assertTrue(incs[3].endswith("/sub3"), msg)
# target include_directories: build dir
self.assertEqual(incs[4], "-Isub2")
# target include_directories: source dir
msg = "{!r} does not end with '/sub2'".format(incs[5])
self.assertTrue(incs[5].endswith("/sub2"), msg)
# target internal dependency include_directories: build dir
self.assertEqual(incs[6], "-Isub1")
# target internal dependency include_directories: source dir
msg = "{!r} does not end with '/sub1'".format(incs[7])
self.assertTrue(incs[7].endswith("/sub1"), msg)
class RewriterTests(unittest.TestCase): class RewriterTests(unittest.TestCase):

@ -13,5 +13,9 @@ ignore =
# E305: expected 2 blank lines after class or function definition, found 1 # E305: expected 2 blank lines after class or function definition, found 1
E305, E305,
# E401: multiple imports on one line # E401: multiple imports on one line
E401 E401,
# too many leading '#' for block comment
E266,
# module level import not at top of file
E402
max-line-length = 120 max-line-length = 120

@ -0,0 +1,22 @@
project('include order', 'c')
# Test that the order of priority of include paths (from first to last) is:
#
# 1. Target's current build directory
# 2. Target's current source directory
# 3. Include paths added with the `c_args:` kwarg
# 4. Include paths added with the `include_directories`: kwarg
# Within this, the build dir takes precedence over the source dir
# 5. Include paths added via `include_directories:` of internal deps
# Within this, the build dir takes precedence over the source dir
# Defines an internal dep
subdir('sub1')
# Defines a per-target include path
subdir('sub2')
# Directory for `c_args:` include path
subdir('sub3')
# The directory where the target resides
subdir('sub4')
test('eh', e)

@ -0,0 +1 @@
#error "sub1/main.h included"

@ -0,0 +1,4 @@
i = include_directories('.')
l = shared_library('somelib', 'some.c')
dep = declare_dependency(link_with : l,
include_directories : i)

@ -0,0 +1,6 @@
#if defined _WIN32 || defined __CYGWIN__
__declspec(dllexport)
#endif
int somefunc(void) {
return 1984;
}

@ -0,0 +1,10 @@
#pragma once
#if defined _WIN32 || defined __CYGWIN__
#define DLL_PUBLIC __declspec(dllimport)
#else
#define DLL_PUBLIC
#endif
DLL_PUBLIC
int somefunc(void);

@ -0,0 +1 @@
#error "sub2/main.h included"

@ -0,0 +1 @@
#error "sub3/main.h included"

@ -0,0 +1 @@
sub3 = meson.current_source_dir()

@ -0,0 +1,8 @@
/* Use the <> include notation to force searching in include directories */
#include <main.h>
int main(int argc, char *argv[]) {
if (somefunc() == 1984)
return 0;
return 1;
}

@ -0,0 +1,3 @@
#pragma once
#include "some.h"

@ -0,0 +1,4 @@
e = executable('someexe', 'main.c',
c_args : ['-I' + sub3],
include_directories : j,
dependencies : dep)
Loading…
Cancel
Save