diff options
Diffstat (limited to 'mesonbuild/compilers.py')
-rw-r--r-- | mesonbuild/compilers.py | 304 |
1 files changed, 221 insertions, 83 deletions
diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 0a88d6b..bb9b04a 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -323,6 +323,175 @@ class RunResult: self.stdout = stdout 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: def __init__(self, exelist, version): if isinstance(exelist, str): @@ -412,11 +581,8 @@ class Compiler: def has_function(self, *args, **kwargs): raise EnvironmentException('Language %s does not support function checks.' % self.language) - def unix_link_flags_to_native(self, args): - "Always returns a copy that can be independently mutated" - return args[:] - - def unix_compile_flags_to_native(self, args): + @classmethod + def unix_args_to_native(cls, args): "Always returns a copy that can be independently mutated" return args[:] @@ -435,7 +601,7 @@ class Compiler: self.language)) def get_cross_extra_flags(self, environment, *, compile, link): - extra_flags = [] + extra_flags = CompilerArgs(self) if self.is_cross and environment: if 'properties' in environment.cross_info.config: lang_args_key = self.language + '_args' @@ -474,7 +640,7 @@ class Compiler: output = self._get_compile_output(tmpdirname, mode) # Construct the compiler command-line - commands = self.get_exelist() + commands = CompilerArgs(self) commands.append(srcname) commands += extra_args commands += self.get_always_args() @@ -485,6 +651,8 @@ class Compiler: commands += self.get_preprocess_only_args() else: 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('Working directory: ', tmpdirname) 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('Is cross compiler: %s.' % str(self.is_cross)) - extra_flags = [] + extra_flags = CompilerArgs(self) source_name = os.path.join(work_dir, sname) binname = sname.rsplit('.', 1)[0] if self.is_cross: @@ -742,51 +910,29 @@ class CCompiler(Compiler): }}''' 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'): if extra_args is None: extra_args = [] - if isinstance(extra_args, str): + elif isinstance(extra_args, str): extra_args = [extra_args] if dependencies is None: dependencies = [] elif not isinstance(dependencies, list): dependencies = [dependencies] - # Add compile flags needed by dependencies after converting to the - # native type of the selected compiler - cargs = [a for d in dependencies for a in d.get_compile_args()] - args = self.unix_link_flags_to_native(cargs) + # Add compile flags needed by dependencies + args = CompilerArgs(self) + for d in dependencies: + args += d.get_compile_args() # Read c_args/cpp_args/etc from the cross-info file (if needed) args += self.get_cross_extra_flags(env, compile=True, link=False) # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env # We assume that the user has ensured these are compiler-specific args += env.coredata.external_args[self.language] - # Append extra_args to the compiler check args such that it overrides - extra_args = self._override_args(self.get_compiler_check_args(), extra_args) - extra_args = self.unix_link_flags_to_native(extra_args) - # Append both to the compiler args such that they override them - args = self._override_args(args, extra_args) + args += self.get_compiler_check_args() + # extra_args must override all other arguments, so we add them last + args += extra_args # 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 def _links_wrapper(self, code, env, extra_args, dependencies): @@ -799,11 +945,11 @@ class CCompiler(Compiler): dependencies = [] elif not isinstance(dependencies, list): dependencies = [dependencies] - # Add compile and link flags needed by dependencies after converting to - # the native type of the selected compiler - cargs = [a for d in dependencies for a in d.get_compile_args()] - link_args = [a for d in dependencies for a in d.get_link_args()] - args = self.unix_link_flags_to_native(cargs + link_args) + # Add compile and link flags needed by dependencies + args = CompilerArgs(self) + for d in dependencies: + args += d.get_compile_args() + args += d.get_link_args() # Select a CRT if needed since we're linking args += self.get_linker_debug_crt_args() # 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 # are compiler-specific args += env.coredata.external_link_args[self.language] - # Append extra_args to the compiler check args such that it overrides - extra_args = self._override_args(self.get_compiler_check_args(), extra_args) - extra_args = self.unix_link_flags_to_native(extra_args) - # Append both to the compiler args such that they override them - args = self._override_args(args, extra_args) - return self.compile(code, args) + # Add compiler check args such that they override + args += self.get_compiler_check_args() + # extra_args must override all other arguments, so we add them last + args += extra_args + return self.compile(code, args.to_native()) def links(self, code, env, extra_args=None, dependencies=None): with self._links_wrapper(code, env, extra_args, dependencies) as p: @@ -1179,6 +1324,9 @@ class CPPCompiler(CCompiler): self.language = 'cpp' CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + def get_no_stdinc_args(self): + return ['-nostdinc++'] + def sanity_check(self, work_dir, environment): code = 'class breakCCompiler;int main(int argc, char **argv) { return 0; }\n' return self.sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code) @@ -1686,7 +1834,8 @@ class DCompiler(Compiler): paths = paths + ':' + padding return ['-L-rpath={}'.format(paths)] - def translate_args_to_nongnu(self, args): + @classmethod + def translate_args_to_nongnu(cls, args): dcargs = [] # Translate common arguments to flags the LDC/DMD compilers # can understand. @@ -1802,11 +1951,9 @@ class LLVMDCompiler(DCompiler): # -L is for the compiler, telling it to pass the second -L to the linker. return ['-L-L' + dirname] - def unix_link_flags_to_native(self, args): - return self.translate_args_to_nongnu(args) - - def unix_compile_flags_to_native(self, args): - return self.translate_args_to_nongnu(args) + @classmethod + def unix_args_to_native(cls, args): + return cls.translate_args_to_nongnu(args) class DmdDCompiler(DCompiler): def __init__(self, exelist, version, is_cross): @@ -1854,11 +2001,9 @@ class DmdDCompiler(DCompiler): def get_std_shared_lib_link_args(self): return ['-shared', '-defaultlib=libphobos2.so'] - def unix_link_flags_to_native(self, args): - return self.translate_args_to_nongnu(args) - - def unix_compile_flags_to_native(self, args): - return self.translate_args_to_nongnu(args) + @classmethod + def unix_args_to_native(cls, args): + return cls.translate_args_to_nongnu(args) class VisualStudioCCompiler(CCompiler): std_warn_args = ['/W3'] @@ -1978,9 +2123,14 @@ class VisualStudioCCompiler(CCompiler): def get_option_link_args(self, options): return options['c_winlibs'].value[:] - def unix_link_flags_to_native(self, args): + @classmethod + def unix_args_to_native(cls, 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 if i.startswith('-L'): i = '/LIBPATH:' + i[2:] # Translate GNU-style -lfoo library name to the import library @@ -1998,16 +2148,6 @@ class VisualStudioCCompiler(CCompiler): result.append(i) 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): return ['/WX'] @@ -2795,8 +2935,10 @@ class NAGFortranCompiler(FortranCompiler): def get_warn_args(self, level): return NAGFortranCompiler.std_warn_args +class StaticLinker: + pass -class VisualStudioLinker: +class VisualStudioLinker(StaticLinker): always_args = ['/NOLOGO'] def __init__(self, exelist): @@ -2832,18 +2974,16 @@ class VisualStudioLinker: def get_option_link_args(self, options): return [] - def unix_link_flags_to_native(self, args): - return args[:] - - def unix_compile_flags_to_native(self, args): - return args[:] + @classmethod + def unix_args_to_native(cls, args): + return VisualStudioCCompiler.unix_args_to_native(args) def get_link_debugfile_args(self, targetfile): pdbarr = targetfile.split('.')[:-1] pdbarr += ['pdb'] return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)] -class ArLinker: +class ArLinker(StaticLinker): def __init__(self, exelist): self.exelist = exelist @@ -2885,10 +3025,8 @@ class ArLinker: def get_option_link_args(self, options): return [] - def unix_link_flags_to_native(self, args): - return args[:] - - def unix_compile_flags_to_native(self, args): + @classmethod + def unix_args_to_native(cls, args): return args[:] def get_link_debugfile_args(self, targetfile): |