From 5c81f5558373a48fb1541a432d0ae8adb6215d05 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 | 33 +++++++++++--- 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, 148 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 d98fc1989678..f77224207af8 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1492,6 +1492,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. @@ -1502,11 +1506,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 @@ -1541,6 +1553,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 7242d46942aa..34888132bace 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -802,6 +802,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,') + retult.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,'): @@ -810,8 +822,13 @@ def has_multi_arguments(self, args, env): 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 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): @@ -914,8 +931,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): @@ -999,6 +1016,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'] @@ -1079,10 +1099,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 13b997957a1f..8184c0166315 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.')