From afdcd8744310a328d77da6b24bea2f3fa53c6036 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Sat, 31 Mar 2018 22:44:09 -0400 Subject: [PATCH] Add has_link_argument() and friends Closes: #3335. --- docs/markdown/Reference-manual.md | 22 ++++++++-- docs/markdown/snippets/has-link-argument.md | 9 ++++ mesonbuild/compilers/c.py | 34 ++++++++++++--- mesonbuild/compilers/compilers.py | 10 ++++- mesonbuild/compilers/cpp.py | 10 ----- mesonbuild/interpreter.py | 43 +++++++++++++++++++ .../common/189 has link arg/meson.build | 42 ++++++++++++++++++ 7 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 docs/markdown/snippets/has-link-argument.md create mode 100644 test cases/common/189 has link arg/meson.build diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 0d3d1aa90ba6..8ff5d24d858b 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1497,6 +1497,10 @@ the following methods: strings, returns the first argument that passes the `has_argument` test above or an empty array if none pass. +- `first_supported_link_argument(list_of_strings)` *(added 0.46.0)*, given a + list of strings, returns the first argument that passes the + `has_link_argument` test above or an empty array if none pass. + - `get_define(definename)` returns the given preprocessor symbol's value as a string or empty string if it is not defined. @@ -1507,11 +1511,19 @@ the following methods: an array containing only the arguments supported by the compiler, as if `has_argument` were called on them individually. +- `get_supported_link_arguments(list_of_string)` *(added 0.46.0)* returns + an array containing only the arguments supported by the linker, + as if `has_link_argument` were called on them individually. + - `has_argument(argument_name)` returns true if the compiler accepts the specified command line argument, that is, can compile code - without erroring out or printing a warning about an unknown flag, - you can specify external dependencies to use with `dependencies` - keyword argument. + without erroring out or printing a warning about an unknown flag. + +- `has_link_argument(argument_name)` *(added 0.46.0)* returns true if the linker + accepts the specified command line argument, that is, can compile and link + code without erroring out or printing a warning about an unknown flag. Note + that linker arguments will be converted to compiler arguments by prepending + '-Wl,' if needed, or by adding '/link' argument for VisualStudio. - `has_function(funcname)` returns true if the given function is provided by the standard library or a library passed in with the @@ -1546,6 +1558,10 @@ the following methods: `has_argument` but takes multiple arguments and uses them all in a single compiler invocation, available since 0.37.0. +- `has_multi_link_arguments(arg1, arg2, arg3, ...)` *(added 0.46.0)* is the same + as `has_link_argument` but takes multiple arguments and uses them all in a + single compiler invocation. + - `has_type(typename)` returns true if the specified token is a type, you can specify external dependencies to use with `dependencies` keyword argument. diff --git a/docs/markdown/snippets/has-link-argument.md b/docs/markdown/snippets/has-link-argument.md new file mode 100644 index 000000000000..7beda63608a9 --- /dev/null +++ b/docs/markdown/snippets/has-link-argument.md @@ -0,0 +1,9 @@ +## has_link_argument() and friends + +A new set of methods has been added on compiler objects to test if the linker +supports given arguments. + +- `has_link_argument()` +- `has_multi_link_arguments()` +- `get_supported_link_arguments()` +- `first_supported_link_argument()` diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 200f815f9942..1c3b361dc1d1 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -830,6 +830,18 @@ def thread_link_flags(self, env): return [] return ['-pthread'] + def linker_to_compiler_args(self, args): + result = [] + for arg in args: + if arg.startswith('-Wl,'): + result.append(arg) + else: + result.append('-Wl,' + arg) + return result + + def has_arguments(self, args, env, code, mode): + return self.compiles(code, env, extra_args=args, mode=mode) + def has_multi_arguments(self, args, env): for arg in args: if arg.startswith('-Wl,'): @@ -837,9 +849,15 @@ def has_multi_arguments(self, args, env): and other similar methods only support checking compiler arguments. Using them to check linker arguments are never supported, and results are likely to be wrong regardless of the compiler you are using. +has_link_argument or other similar method can be used instead. '''.format(arg)) - return self.compiles('int i;\n', env, extra_args=args) + code = 'int i;\n' + return self.has_arguments(args, env, code, mode='compile') + def has_multi_link_arguments(self, args, env): + args = self.linker_to_compiler_args(args) + code = 'int main(int argc, char **argv) { return 0; }' + return self.has_arguments(args, env, code, mode='link') class ClangCCompiler(ClangCompiler, CCompiler): def __init__(self, exelist, version, clang_type, is_cross, exe_wrapper=None, **kwargs): @@ -942,8 +960,8 @@ def get_option_compile_args(self, options): def get_std_shared_lib_link_args(self): return ['-shared'] - def has_multi_arguments(self, args, env): - return super().has_multi_arguments(args + ['-diag-error', '10006'], env) + def has_arguments(self, args, env, code, mode): + return super().has_arguments(args + ['-diag-error', '10006'], env, code, mode) class VisualStudioCCompiler(CCompiler): @@ -1027,6 +1045,9 @@ def get_linker_output_args(self, outputname): def get_linker_search_args(self, dirname): return ['/LIBPATH:' + dirname] + def linker_to_compiler_args(self, args): + return ['/link'] + args + def get_gui_app_args(self): return ['/SUBSYSTEM:WINDOWS'] @@ -1107,10 +1128,9 @@ def get_include_args(self, path, is_system): # Visual Studio is special. It ignores some arguments it does not # understand and you can't tell it to error out on those. # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t - def has_multi_arguments(self, args, env): - warning_text = '9002' - code = 'int i;\n' - with self._build_wrapper(code, env, extra_args=args, mode='compile') as p: + def has_arguments(self, args, env, code, mode): + warning_text = '4044' if mode == 'link' else '9002' + with self._build_wrapper(code, env, extra_args=args, mode=mode) as p: if p.returncode != 0: return False return not(warning_text in p.stde or warning_text in p.stdo) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 1cfdf0450954..20c173ef9db9 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -718,6 +718,11 @@ def has_multi_arguments(self, args, env): 'Language {} does not support has_multi_arguments.'.format( self.get_display_language())) + def has_multi_link_arguments(self, args, env): + raise EnvironmentException( + 'Language {} does not support has_multi_link_arguments.'.format( + self.get_display_language())) + def get_cross_extra_flags(self, environment, link): extra_flags = [] if self.is_cross and environment: @@ -760,7 +765,6 @@ def compile(self, code, extra_args=None, mode='link'): # Construct the compiler command-line commands = CompilerArgs(self) commands.append(srcname) - commands += extra_args commands += self.get_always_args() if mode == 'compile': commands += self.get_compile_only_args() @@ -769,6 +773,10 @@ def compile(self, code, extra_args=None, mode='link'): commands += self.get_preprocess_only_args() else: commands += self.get_output_args(output) + # extra_args must be last because it could contain '/link' to + # pass args to VisualStudio's linker. In that case everything + # in the command line after '/link' is given to the linker. + commands += extra_args # Generate full command-line with the exelist commands = self.get_exelist() + commands.to_native() mlog.debug('Running compile:') diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index b2f22f9bb6ed..6ba9cfb3fccc 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -174,16 +174,6 @@ def get_option_compile_args(self, options): def get_option_link_args(self, options): return [] - def has_multi_arguments(self, args, env): - for arg in args: - if arg.startswith('-Wl,'): - mlog.warning('''{} looks like a linker argument, but has_argument -and other similar methods only support checking compiler arguments. -Using them to check linker arguments are never supported, and results -are likely to be wrong regardless of the compiler you are using. -'''.format(arg)) - return super().has_multi_arguments(args + ['-diag-error', '10006'], env) - class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): def __init__(self, exelist, version, is_cross, exe_wrap, is_64): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index dec0c249bb10..b171589c7dfc 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -741,6 +741,10 @@ def __init__(self, compiler, env): 'has_multi_arguments': self.has_multi_arguments_method, 'get_supported_arguments': self.get_supported_arguments_method, 'first_supported_argument': self.first_supported_argument_method, + 'has_link_argument': self.has_link_argument_method, + 'has_multi_link_arguments': self.has_multi_link_arguments_method, + 'get_supported_link_arguments': self.get_supported_link_arguments_method, + 'first_supported_link_argument': self.first_supported_link_argument_method, 'unittest_args': self.unittest_args_method, 'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method, }) @@ -1218,6 +1222,45 @@ def first_supported_argument_method(self, args, kwargs): mlog.log('First supported argument:', mlog.red('None')) return [] + @permittedMethodKwargs({}) + def has_link_argument_method(self, args, kwargs): + args = mesonlib.stringlistify(args) + if len(args) != 1: + raise InterpreterException('has_link_argument takes exactly one argument.') + return self.has_multi_link_arguments_method(args, kwargs) + + @permittedMethodKwargs({}) + def has_multi_link_arguments_method(self, args, kwargs): + args = mesonlib.stringlistify(args) + result = self.compiler.has_multi_link_arguments(args, self.environment) + if result: + h = mlog.green('YES') + else: + h = mlog.red('NO') + mlog.log( + 'Compiler for {} supports link arguments {}:'.format( + self.compiler.get_display_language(), ' '.join(args)), + h) + return result + + @permittedMethodKwargs({}) + def get_supported_link_arguments_method(self, args, kwargs): + args = mesonlib.stringlistify(args) + supported_args = [] + for arg in args: + if self.has_link_argument_method(arg, kwargs): + supported_args.append(arg) + return supported_args + + @permittedMethodKwargs({}) + def first_supported_link_argument_method(self, args, kwargs): + for i in mesonlib.stringlistify(args): + if self.has_link_argument_method(i, kwargs): + mlog.log('First supported link argument:', mlog.bold(i)) + return [i] + mlog.log('First supported link argument:', mlog.red('None')) + return [] + ModuleState = namedtuple('ModuleState', [ 'build_to_src', 'subproject', 'subdir', 'current_lineno', 'environment', 'project_name', 'project_version', 'backend', 'compilers', 'targets', diff --git a/test cases/common/189 has link arg/meson.build b/test cases/common/189 has link arg/meson.build new file mode 100644 index 000000000000..255ff459ab4b --- /dev/null +++ b/test cases/common/189 has link arg/meson.build @@ -0,0 +1,42 @@ +project('has link arg', 'c', 'cpp') + +cc = meson.get_compiler('c') +cpp = meson.get_compiler('cpp') + +if cc.get_id() == 'msvc' + is_arg = '/OPT:REF' + useless = '/DEBUG' + isnt_arg = '/iambroken' +else + is_arg = '-Wl,-Lfoo' + useless = '-Wl,-Lbar' + isnt_arg = '-Wl,-iambroken' +endif + +assert(cc.has_link_argument(is_arg), 'Arg that should have worked does not work.') +assert(not cc.has_link_argument(isnt_arg), 'Arg that should be broken is not.') + +assert(cpp.has_link_argument(is_arg), 'Arg that should have worked does not work.') +assert(not cpp.has_link_argument(isnt_arg), 'Arg that should be broken is not.') + +assert(cc.get_supported_link_arguments([is_arg, isnt_arg, useless]) == [is_arg, useless], 'Arg filtering returned different result.') +assert(cpp.get_supported_link_arguments([is_arg, isnt_arg, useless]) == [is_arg, useless], 'Arg filtering returned different result.') + +# Have useless at the end to ensure that the search goes from front to back. +l1 = cc.first_supported_link_argument([isnt_arg, is_arg, isnt_arg, useless]) +l2 = cc.first_supported_link_argument(isnt_arg, isnt_arg, isnt_arg) + +assert(l1.length() == 1, 'First supported returned wrong result.') +assert(l1.get(0) == is_arg, 'First supported returned wrong argument.') +assert(l2.length() == 0, 'First supported did not return empty array.') + +l1 = cpp.first_supported_link_argument([isnt_arg, is_arg, isnt_arg, useless]) +l2 = cpp.first_supported_link_argument(isnt_arg, isnt_arg, isnt_arg) + +assert(l1.length() == 1, 'First supported returned wrong result.') +assert(l1.get(0) == is_arg, 'First supported returned wrong argument.') +assert(l2.length() == 0, 'First supported did not return empty array.') + +assert(not cc.has_multi_link_arguments([isnt_arg, is_arg]), 'Arg that should be broken is not.') +assert(cc.has_multi_link_arguments(is_arg), 'Arg that should have worked does not work.') +assert(cc.has_multi_link_arguments([useless, is_arg]), 'Arg that should have worked does not work.')