diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 5e7db2435..6f08d98bd 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -550,11 +550,11 @@ class Compiler: def get_exelist(self): return self.exelist[:] - def get_define(self, *args, **kwargs): - raise EnvironmentException('%s does not support get_define.' % self.id) + def get_builtin_define(self, *args, **kwargs): + raise EnvironmentException('%s does not support get_builtin_define.' % self.id) - def has_define(self, *args, **kwargs): - raise EnvironmentException('%s does not support has_define.' % self.id) + def has_builtin_define(self, *args, **kwargs): + raise EnvironmentException('%s does not support has_builtin_define.' % self.id) def get_always_args(self): return [] @@ -906,8 +906,6 @@ class CCompiler(Compiler): return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) def has_header(self, hname, prefix, env, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] fargs = {'prefix': prefix, 'header': hname} code = '''{prefix} #ifdef __has_include @@ -921,8 +919,6 @@ class CCompiler(Compiler): dependencies, 'preprocess') def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} t = '''{prefix} #include <{header}> @@ -934,7 +930,7 @@ class CCompiler(Compiler): }}''' return self.compiles(t.format(**fargs), env, extra_args, dependencies) - def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'): + def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'): if extra_args is None: extra_args = [] elif isinstance(extra_args, str): @@ -943,49 +939,43 @@ class CCompiler(Compiler): dependencies = [] elif not isinstance(dependencies, list): dependencies = [dependencies] - # Add compile flags needed by dependencies + # Collect compiler arguments args = CompilerArgs(self) for d in dependencies: + # Add compile flags needed by dependencies args += d.get_compile_args() + if mode == 'link': + # Add link flags needed to find dependencies + args += d.get_link_args() + # Select a CRT if needed since we're linking + if mode == 'link': + args += self.get_linker_debug_crt_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] + args += self.get_cross_extra_flags(env, compile=(mode != 'preprocess'), + link=(mode == 'link')) + if mode == 'preprocess': + # Add CPPFLAGS from the env. + args += env.coredata.external_preprocess_args[self.language] + elif mode == 'compile': + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env + args += env.coredata.external_args[self.language] + elif mode == 'link': + # Add LDFLAGS from the env + args += env.coredata.external_link_args[self.language] args += self.get_compiler_check_args() # extra_args must override all other arguments, so we add them last args += extra_args + return args + + def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'): + args = self._get_compiler_check_args(env, extra_args, dependencies, mode) # We only want to compile; not link with self.compile(code, args.to_native(), mode) as p: return p.returncode == 0 def _links_wrapper(self, code, env, extra_args, dependencies): "Shares common code between self.links and self.run" - if extra_args is None: - extra_args = [] - elif isinstance(extra_args, str): - extra_args = [extra_args] - if dependencies is None: - dependencies = [] - elif not isinstance(dependencies, list): - dependencies = [dependencies] - # 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 - # cross-info file (if needed) - args += self.get_cross_extra_flags(env, compile=True, link=True) - # Add LDFLAGS from the env. We assume that the user has ensured these - # are compiler-specific - args += env.coredata.external_link_args[self.language] - # 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 + args = self._get_compiler_check_args(env, extra_args, dependencies, mode='link') return self.compile(code, args.to_native()) def links(self, code, env, extra_args=None, dependencies=None): @@ -1141,6 +1131,24 @@ class CCompiler(Compiler): raise EnvironmentException('Could not determine alignment of %s. Sorry. You might want to file a bug.' % typename) return align + def get_define(self, dname, prefix, env, extra_args, dependencies): + delim = '"MESON_GET_DEFINE_DELIMITER"' + fargs = {'prefix': prefix, 'define': dname, 'delim': delim} + code = ''' + #ifndef {define} + # define {define} + #endif + {prefix} + {delim}\n{define}''' + args = self._get_compiler_check_args(env, extra_args, dependencies, + mode='preprocess').to_native() + with self.compile(code.format(**fargs), args, 'preprocess') as p: + if p.returncode != 0: + raise EnvironmentException('Could not get define {!r}'.format(dname)) + # Get the preprocessed value after the delimiter, + # minus the extra newline at the end + return p.stdo.split(delim + '\n')[-1][:-1] + @staticmethod def _no_prototype_templ(): """ @@ -2382,10 +2390,10 @@ class GnuCompiler: args[args.index('-Wpedantic')] = '-pedantic' return args - def has_define(self, define): + def has_builtin_define(self, define): return define in self.defines - def get_define(self, define): + def get_builtin_define(self, define): if define in self.defines: return self.defines[define] @@ -2896,10 +2904,10 @@ class GnuFortranCompiler(FortranCompiler): self.defines = defines or {} self.id = 'gcc' - def has_define(self, define): + def has_builtin_define(self, define): return define in self.defines - def get_define(self, define): + def get_builtin_define(self, define): if define in self.defines: return self.defines[define] diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 67516e78f..27f1dd7a4 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -148,10 +148,11 @@ class CoreData: self.user_options = {} self.compiler_options = {} self.base_options = {} - # These two, external_*args, are set via env vars CFLAGS, LDFLAGS, etc + # These 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_preprocess_args = {} # CPPFLAGS only + self.external_args = {} # CPPFLAGS + CFLAGS + self.external_link_args = {} # CFLAGS + LDFLAGS (with MSVC: only LDFLAGS) if options.cross_file is not None: self.cross_file = os.path.join(os.getcwd(), options.cross_file) else: diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 92040c45d..cb6250671 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -102,7 +102,7 @@ def detect_windows_arch(compilers): platform = os.environ.get('Platform', 'x86').lower() if platform == 'x86': return platform - if compiler.id == 'gcc' and compiler.has_define('__i386__'): + if compiler.id == 'gcc' and compiler.has_builtin_define('__i386__'): return 'x86' return os_arch @@ -129,10 +129,10 @@ def detect_cpu_family(compilers): # to know is to check the compiler defines. for c in compilers.values(): try: - if c.has_define('__i386__'): + if c.has_builtin_define('__i386__'): return 'x86' except mesonlib.MesonException: - # Ignore compilers that do not support has_define. + # Ignore compilers that do not support has_builtin_define. pass return 'x86_64' # Add fixes here as bugs are reported. @@ -149,7 +149,7 @@ def detect_cpu(compilers): # Same check as above for cpu_family for c in compilers.values(): try: - if c.has_define('__i386__'): + if c.has_builtin_define('__i386__'): return 'i686' # All 64 bit cpus have at least this level of x86 support. except mesonlib.MesonException: pass @@ -770,7 +770,7 @@ def get_args_from_envvars(compiler): compiler_is_linker = (compiler.get_exelist() == compiler.get_linker_exelist()) if lang not in ('c', 'cpp', 'objc', 'objcpp', 'fortran', 'd'): - return [], [] + return [], [], [] # Compile flags cflags_mapping = {'c': 'CFLAGS', @@ -781,12 +781,12 @@ def get_args_from_envvars(compiler): 'd': 'DFLAGS'} compile_flags = os.environ.get(cflags_mapping[lang], '') log_var(cflags_mapping[lang], compile_flags) - compile_flags = compile_flags.split() + compile_flags = shlex.split(compile_flags) # Link flags (same for all languages) link_flags = os.environ.get('LDFLAGS', '') log_var('LDFLAGS', link_flags) - link_flags = link_flags.split() + link_flags = shlex.split(link_flags) if compiler_is_linker: # When the compiler is used as a wrapper around the linker (such as # with GCC and Clang), the compile flags can be needed while linking @@ -794,14 +794,15 @@ def get_args_from_envvars(compiler): # this when the linker is stand-alone such as with MSVC C/C++, etc. link_flags = compile_flags + link_flags - # Pre-processof rlags (not for fortran) + # Pre-processor flags (not for fortran or D) preproc_flags = '' if lang in ('c', 'cpp', 'objc', 'objcpp'): preproc_flags = os.environ.get('CPPFLAGS', '') log_var('CPPFLAGS', preproc_flags) - compile_flags += preproc_flags.split() + preproc_flags = shlex.split(preproc_flags) + compile_flags += preproc_flags - return compile_flags, link_flags + return preproc_flags, compile_flags, link_flags class CrossBuildInfo: def __init__(self, filename): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index af2c17d60..bf3dffe73 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -634,6 +634,7 @@ class CompilerHolder(InterpreterObject): 'get_id': self.get_id_method, 'compute_int': self.compute_int_method, 'sizeof': self.sizeof_method, + 'get_define': self.get_define_method, 'has_header': self.has_header_method, 'has_header_symbol': self.has_header_symbol_method, 'run': self.run_method, @@ -865,6 +866,20 @@ class CompilerHolder(InterpreterObject): mlog.log('Checking for size of "%s": %d' % (element, esize)) return esize + def get_define_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('get_define() takes exactly one argument.') + check_stringlist(args) + element = args[0] + prefix = kwargs.get('prefix', '') + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of get_define() must be a string.') + extra_args = self.determine_args(kwargs) + deps = self.determine_dependencies(kwargs) + value = self.compiler.get_define(element, prefix, self.environment, extra_args, deps) + mlog.log('Checking for value of define "%s": %s' % (element, value)) + return value + def compiles_method(self, args, kwargs): if len(args) != 1: raise InterpreterException('compiles method takes exactly one argument.') @@ -1615,25 +1630,29 @@ class Interpreter(InterpreterBase): def func_project(self, node, args, kwargs): if len(args) < 1: raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') + proj_name = args[0] + proj_langs = args[1:] + if ':' in proj_name: + raise InvalidArguments("Project name {!r} must not contain ':'".format(proj_name)) default_options = kwargs.get('default_options', []) if self.environment.first_invocation and (len(default_options) > 0 or len(self.default_project_options) > 0): self.parse_default_options(default_options) if not self.is_subproject(): - self.build.project_name = args[0] + self.build.project_name = proj_name if os.path.exists(self.option_file): oi = optinterpreter.OptionInterpreter(self.subproject, self.build.environment.cmd_line_options.projectoptions, ) oi.process(self.option_file) self.build.environment.merge_options(oi.options) - self.active_projectname = args[0] + self.active_projectname = proj_name self.project_version = kwargs.get('version', 'undefined') if self.build.project_version is None: self.build.project_version = self.project_version proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown')) - self.build.dep_manifest[args[0]] = {'version': self.project_version, - 'license': proj_license} + self.build.dep_manifest[proj_name] = {'version': self.project_version, + 'license': proj_license} if self.subproject in self.build.projects: raise InvalidCode('Second call to project().') if not self.is_subproject() and 'subproject_dir' in kwargs: @@ -1644,9 +1663,9 @@ class Interpreter(InterpreterBase): pv = kwargs['meson_version'] if not mesonlib.version_compare(cv, pv): raise InterpreterException('Meson version is %s but project requires %s.' % (cv, pv)) - self.build.projects[self.subproject] = args[0] - mlog.log('Project name: ', mlog.bold(args[0]), sep='') - self.add_languages(args[1:], True) + self.build.projects[self.subproject] = proj_name + mlog.log('Project name: ', mlog.bold(proj_name), sep='') + self.add_languages(proj_langs, True) langs = self.coredata.compilers.keys() if 'vala' in langs: if 'c' not in langs: @@ -1772,9 +1791,10 @@ class Interpreter(InterpreterBase): raise mlog.log('Native %s compiler: ' % lang, mlog.bold(' '.join(comp.get_exelist())), ' (%s %s)' % (comp.id, comp.version), sep='') if not comp.get_language() in self.coredata.external_args: - (ext_compile_args, ext_link_args) = environment.get_args_from_envvars(comp) - self.coredata.external_args[comp.get_language()] = ext_compile_args - self.coredata.external_link_args[comp.get_language()] = ext_link_args + (preproc_args, compile_args, link_args) = environment.get_args_from_envvars(comp) + self.coredata.external_preprocess_args[comp.get_language()] = preproc_args + self.coredata.external_args[comp.get_language()] = compile_args + self.coredata.external_link_args[comp.get_language()] = link_args self.build.add_compiler(comp) if need_cross_compiler: mlog.log('Cross %s compiler: ' % lang, mlog.bold(' '.join(cross_comp.get_exelist())), ' (%s %s)' % (cross_comp.id, cross_comp.version), sep='') diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 10b8fab37..f9e7f26e7 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -75,15 +75,16 @@ class OptionInterpreter: self.cmd_line_options = {} for o in command_line_options: if self.subproject != '': # Strip the beginning. + # Ignore options that aren't for this subproject if not o.startswith(self.sbprefix): continue - else: - if ':' in o: - continue try: (key, value) = o.split('=', 1) except ValueError: raise OptionException('Option {!r} must have a value separated by equals sign.'.format(o)) + # Ignore subproject options if not fetching subproject options + if self.subproject == '' and ':' in key: + continue self.cmd_line_options[key] = value def process(self, option_file): diff --git a/run_unittests.py b/run_unittests.py index 0656f8858..53abef75e 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -947,6 +947,21 @@ class AllPlatformTests(BasePlatformTests): m = re.search('build c-asm.*: c_LINKER', contents) self.assertIsNotNone(m, msg=contents) + def test_preprocessor_checks_CPPFLAGS(self): + ''' + Test that preprocessor compiler checks read CPPFLAGS but not CFLAGS + ''' + testdir = os.path.join(self.common_test_dir, '140 get define') + define = 'MESON_TEST_DEFINE_VALUE' + # NOTE: this list can't have \n, ' or " + # \n is never substituted by the GNU pre-processor via a -D define + # ' and " confuse shlex.split() even when they are escaped + # % and # confuse the MSVC preprocessor + value = 'spaces and fun!@$^&*()-=_+{}[]:;<>?,./~`' + os.environ['CPPFLAGS'] = '-D{}="{}"'.format(define, value) + os.environ['CFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define) + self.init(testdir, ['-D{}={}'.format(define, value)]) + class WindowsTests(BasePlatformTests): ''' diff --git a/test cases/common/140 get define/meson.build b/test cases/common/140 get define/meson.build new file mode 100644 index 000000000..7a0969fc2 --- /dev/null +++ b/test cases/common/140 get define/meson.build @@ -0,0 +1,28 @@ +project('get define', 'c', 'cpp') + +host_system = host_machine.system() + +foreach lang : ['c', 'cpp'] + cc = meson.get_compiler(lang) + if host_system == 'linux' + d = cc.get_define('__linux__') + assert(d == '1', '__linux__ value is @0@ instead of 1'.format(d)) + elif host_system == 'darwin' + d = cc.get_define('__APPLE__') + assert(d == '1', '__APPLE__ value is @0@ instead of 1'.format(d)) + elif host_system == 'windows' + d = cc.get_define('_WIN32') + assert(d == '1', '_WIN32 value is @0@ instead of 1'.format(d)) + else + error('Please report a bug and help us improve support for this platform') + endif + + # Check that an undefined value is empty. + have = cc.get_define('MESON_FAIL_VALUE') + assert(have == '', 'MESON_FAIL_VALUE value is "@0@" instead of ""'.format(have)) + + # This is used in the test_preprocessor_checks_CPPFLAGS() unit test. + have = cc.get_define('MESON_TEST_DEFINE_VALUE') + expect = get_option('MESON_TEST_DEFINE_VALUE') + assert(have == expect, 'MESON_TEST_DEFINE_VALUE value is "@0@" instead of "@1@"'.format(have, expect)) +endforeach diff --git a/test cases/common/140 get define/meson_options.txt b/test cases/common/140 get define/meson_options.txt new file mode 100644 index 000000000..a88cecdc4 --- /dev/null +++ b/test cases/common/140 get define/meson_options.txt @@ -0,0 +1 @@ +option('MESON_TEST_DEFINE_VALUE', type : 'string', default : '') diff --git a/test cases/failing/14 invalid option name/meson_options.txt b/test cases/failing/14 invalid option name/meson_options.txt index c65640256..aab6ae8de 100644 --- a/test cases/failing/14 invalid option name/meson_options.txt +++ b/test cases/failing/14 invalid option name/meson_options.txt @@ -1 +1 @@ -option('invalid/name', type : 'boolean', value : false) \ No newline at end of file +option('invalid:name', type : 'boolean', value : false) diff --git a/test cases/failing/43 project name colon/meson.build b/test cases/failing/43 project name colon/meson.build new file mode 100644 index 000000000..53e947ef2 --- /dev/null +++ b/test cases/failing/43 project name colon/meson.build @@ -0,0 +1 @@ +project('name with :')