compilers: Rework the CompilerArgs to be less awful

There are two awful things about CompilerArgs, one is that it directly
inherits from list, and there are a lot of subtle gotcahs with
inheriting from builtin types. The second is that the class allows
arguments to be passed in whatever order. That's bad. This also fully
annotates the CompilerArgs class, so mypy can type check it for us.
pull/6301/head
Dylan Baker 5 years ago committed by Jussi Pakkanen
parent 5b97767ea8
commit 875ef354d0
  1. 144
      mesonbuild/compilers/compilers.py
  2. 8
      run_unittests.py

@ -13,6 +13,7 @@
# limitations under the License. # limitations under the License.
import contextlib, os.path, re, tempfile import contextlib, os.path, re, tempfile
import collections.abc
import typing import typing
from ..linkers import StaticLinker, GnuLikeDynamicLinkerMixin, SolarisDynamicLinker from ..linkers import StaticLinker, GnuLikeDynamicLinkerMixin, SolarisDynamicLinker
@ -385,11 +386,12 @@ class RunResult:
self.stdout = stdout self.stdout = stdout
self.stderr = stderr self.stderr = stderr
class CompilerArgs(list): class CompilerArgs(typing.MutableSequence[str]):
''' '''
Class derived from list() that manages a list of compiler arguments. Should List-like class that manages a list of compiler arguments. Should be used
be used while constructing compiler arguments from various sources. Can be while constructing compiler arguments from various sources. Can be
operated with ordinary lists, so this does not need to be used everywhere. 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) 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 and can converted to the native type of each compiler by using the
@ -438,37 +440,45 @@ class CompilerArgs(list):
# In generate_link() we add external libs without de-dup, but we must # In generate_link() we add external libs without de-dup, but we must
# *always* de-dup these because they're special arguments to the linker # *always* de-dup these because they're special arguments to the linker
always_dedup_args = tuple('-l' + lib for lib in unixy_compiler_internal_libs) always_dedup_args = tuple('-l' + lib for lib in unixy_compiler_internal_libs)
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 not args:
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): def __init__(self, compiler: typing.Union['Compiler', StaticLinker],
super().__init__(self._check_args(args)) iterable: typing.Optional[typing.Iterable[str]] = None):
self.compiler = compiler
self.__container = list(iterable) if iterable is not None else [] # type: typing.List[str]
@typing.overload
def __getitem__(self, index: int) -> str:
pass
@typing.overload
def __getitem__(self, index: slice) -> typing.List[str]:
pass
def __getitem__(self, index):
return self.__container[index]
@typing.overload
def __setitem__(self, index: int, value: str) -> None:
pass
@typing.overload
def __setitem__(self, index: slice, value: typing.List[str]) -> None:
pass
def __setitem__(self, index, value) -> None:
self.__container[index] = value
def __delitem__(self, index: typing.Union[int, slice]) -> None:
del self.__container[index]
def __len__(self) -> int:
return len(self.__container)
def insert(self, index: int, value: str) -> None:
self.__container.insert(index, value)
def copy(self) -> 'CompilerArgs':
return CompilerArgs(self.compiler, self.__container.copy())
@classmethod @classmethod
def _can_dedup(cls, arg): def _can_dedup(cls, arg):
@ -533,7 +543,7 @@ class CompilerArgs(list):
# This covers all ld.bfd, ld.gold, ld.gold, and xild on Linux, which # This covers all ld.bfd, ld.gold, ld.gold, and xild on Linux, which
# all act like (or are) gnu ld # all act like (or are) gnu ld
# TODO: this could probably be added to the DynamicLinker instead # TODO: this could probably be added to the DynamicLinker instead
if (hasattr(self.compiler, 'linker') and if (isinstance(self.compiler, Compiler) and
self.compiler.linker is not None and self.compiler.linker is not None and
isinstance(self.compiler.linker, (GnuLikeDynamicLinkerMixin, SolarisDynamicLinker))): isinstance(self.compiler.linker, (GnuLikeDynamicLinkerMixin, SolarisDynamicLinker))):
group_start = -1 group_start = -1
@ -553,7 +563,7 @@ class CompilerArgs(list):
# Remove system/default include paths added with -isystem # Remove system/default include paths added with -isystem
if hasattr(self.compiler, 'get_default_include_dirs'): if hasattr(self.compiler, 'get_default_include_dirs'):
default_dirs = self.compiler.get_default_include_dirs() default_dirs = self.compiler.get_default_include_dirs()
bad_idx_list = [] bad_idx_list = [] # type: typing.List[int]
for i, each in enumerate(new): for i, each in enumerate(new):
# Remove the -isystem and the path if the path is a default path # Remove the -isystem and the path if the path is a default path
if (each == '-isystem' and if (each == '-isystem' and
@ -566,9 +576,9 @@ class CompilerArgs(list):
bad_idx_list += [i] bad_idx_list += [i]
for i in reversed(bad_idx_list): for i in reversed(bad_idx_list):
new.pop(i) new.pop(i)
return self.compiler.unix_args_to_native(new) return self.compiler.unix_args_to_native(new.__container)
def append_direct(self, arg): def append_direct(self, arg: str) -> None:
''' '''
Append the specified argument without any reordering or de-dup except Append the specified argument without any reordering or de-dup except
for absolute paths to libraries, etc, which can always be de-duped for absolute paths to libraries, etc, which can always be de-duped
@ -577,9 +587,9 @@ class CompilerArgs(list):
if os.path.isabs(arg): if os.path.isabs(arg):
self.append(arg) self.append(arg)
else: else:
super().append(arg) self.__container.append(arg)
def extend_direct(self, iterable): def extend_direct(self, iterable: typing.Iterable[str]) -> None:
''' '''
Extend using the elements in the specified iterable without any Extend using the elements in the specified iterable without any
reordering or de-dup except for absolute paths where the order of reordering or de-dup except for absolute paths where the order of
@ -588,7 +598,7 @@ class CompilerArgs(list):
for elem in iterable: for elem in iterable:
self.append_direct(elem) self.append_direct(elem)
def extend_preserving_lflags(self, iterable): def extend_preserving_lflags(self, iterable: typing.Iterable[str]) -> None:
normal_flags = [] normal_flags = []
lflags = [] lflags = []
for i in iterable: for i in iterable:
@ -599,20 +609,20 @@ class CompilerArgs(list):
self.extend(normal_flags) self.extend(normal_flags)
self.extend_direct(lflags) self.extend_direct(lflags)
def __add__(self, args): def __add__(self, args: typing.Iterable[str]) -> 'CompilerArgs':
new = CompilerArgs(self, self.compiler) new = self.copy()
new += args new += args
return new return new
def __iadd__(self, args): def __iadd__(self, args: typing.Iterable[str]) -> 'CompilerArgs':
''' '''
Add two CompilerArgs while taking into account overriding of arguments Add two CompilerArgs while taking into account overriding of arguments
and while preserving the order of arguments as much as possible and while preserving the order of arguments as much as possible
''' '''
pre = [] pre = [] # type: typing.List[str]
post = [] post = [] # type: typing.List[str]
if not isinstance(args, list): if not isinstance(args, collections.abc.Iterable):
raise TypeError('can only concatenate list (not "{}") to list'.format(args)) raise TypeError('can only concatenate Iterable[str] (not "{}") to CompilerArgs'.format(args))
for arg in args: for arg in args:
# If the argument can be de-duped, do it either by removing the # If the argument can be de-duped, do it either by removing the
# previous occurrence of it and adding a new one, or not adding the # previous occurrence of it and adding a new one, or not adding the
@ -637,39 +647,31 @@ class CompilerArgs(list):
# Insert at the beginning # Insert at the beginning
self[:0] = pre self[:0] = pre
# Append to the end # Append to the end
super().__iadd__(post) self.__container += post
return self return self
def __radd__(self, args): def __radd__(self, args: typing.Iterable[str]):
new = CompilerArgs(args, self.compiler) new = CompilerArgs(self.compiler, args)
new += self new += self
return new return new
def __eq__(self, other: object) -> bool: def __eq__(self, other: typing.Any) -> typing.Union[bool, 'NotImplemented']:
''' # Only allow equality checks against other CompilerArgs and lists instances
Only checks for equalety of self.compilers if the attribute is present. if isinstance(other, CompilerArgs):
Use the default list equalety for the compiler args. return self.compiler == other.compiler and self.__container == other.__container
''' elif isinstance(other, list):
comp_eq = True return self.__container == other
if hasattr(other, 'compiler'): return NotImplemented
comp_eq = self.compiler == other.compiler
return comp_eq and super().__eq__(other)
def __mul__(self, args): # lgtm[py/unexpected-raise-in-special-method]
raise TypeError("can't multiply compiler arguments")
def __imul__(self, args): # lgtm[py/unexpected-raise-in-special-method]
raise TypeError("can't multiply compiler arguments")
def __rmul__(self, args): # lgtm[py/unexpected-raise-in-special-method] def append(self, arg: str) -> None:
raise TypeError("can't multiply compiler arguments")
def append(self, arg):
self.__iadd__([arg]) self.__iadd__([arg])
def extend(self, args): def extend(self, args: typing.Iterable[str]) -> None:
self.__iadd__(args) self.__iadd__(args)
def __repr__(self) -> str:
return 'CompilerArgs({!r}, {!r})'.format(self.compiler, self.__container)
class Compiler: class Compiler:
# Libraries to ignore in find_library() since they are provided by the # Libraries to ignore in find_library() since they are provided by the
# compiler or the C library. Currently only used for MSVC. # compiler or the C library. Currently only used for MSVC.

@ -360,18 +360,14 @@ class InternalTests(unittest.TestCase):
def test_compiler_args_class(self): def test_compiler_args_class(self):
cargsfunc = mesonbuild.compilers.CompilerArgs cargsfunc = mesonbuild.compilers.CompilerArgs
cc = mesonbuild.compilers.CCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock()) cc = mesonbuild.compilers.CCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock())
# Test that bad initialization fails
self.assertRaises(TypeError, cargsfunc, [])
self.assertRaises(TypeError, cargsfunc, [], [])
self.assertRaises(TypeError, cargsfunc, cc, [], [])
# Test that empty initialization works # Test that empty initialization works
a = cargsfunc(cc) a = cargsfunc(cc)
self.assertEqual(a, []) self.assertEqual(a, [])
# Test that list initialization works # Test that list initialization works
a = cargsfunc(['-I.', '-I..'], cc) a = cargsfunc(cc, ['-I.', '-I..'])
self.assertEqual(a, ['-I.', '-I..']) self.assertEqual(a, ['-I.', '-I..'])
# Test that there is no de-dup on initialization # Test that there is no de-dup on initialization
self.assertEqual(cargsfunc(['-I.', '-I.'], cc), ['-I.', '-I.']) self.assertEqual(cargsfunc(cc, ['-I.', '-I.']), ['-I.', '-I.'])
## Test that appending works ## Test that appending works
a.append('-I..') a.append('-I..')

Loading…
Cancel
Save