diff options
35 files changed, 461 insertions, 72 deletions
diff --git a/authors.txt b/authors.txt index 5bd0ba9..d982dfa 100644 --- a/authors.txt +++ b/authors.txt @@ -72,4 +72,6 @@ Aaron Small Joe Baldino Peter Harris Roger Boerdijk +melak47 +Philipp Ittershagen Dylan Baker diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 99be172..5517fbd 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -76,6 +76,24 @@ class TestSerialisation: self.workdir = workdir self.extra_paths = extra_paths +class OptionProxy: + def __init__(self, name, value): + self.name = name + self.value = value + +class OptionOverrideProxy: + '''Mimic an option list but transparently override + selected option values.''' + def __init__(self, overrides, options): + self.overrides = overrides + self.options = options + + def __getitem__(self, option_name): + base_opt = self.options[option_name] + if option_name in self.overrides: + return OptionProxy(base_opt.name, base_opt.validate_value(self.overrides[option_name])) + return base_opt + # This class contains the basic functionality that is needed by all backends. # Feel free to move stuff in and out of it as you see fit. class Backend: @@ -103,6 +121,12 @@ class Backend: def get_target_filename_abs(self, target): return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) + def get_option_for_target(self, option_name, target): + if option_name in target.option_overrides: + override = target.option_overrides[option_name] + return self.environment.coredata.validate_option_value(option_name, override) + return self.environment.coredata.get_builtin_option(option_name) + def get_target_filename_for_linking(self, target): # On some platforms (msvc for instance), the file that is used for # dynamic linking is not the same as the dynamic library itself. This @@ -152,8 +176,10 @@ class Backend: compsrcs = classify_unity_sources(target.compilers.values(), unity_src) def init_language_file(suffix): - outfilename = os.path.join(self.get_target_private_dir_abs(target), - self.get_unity_source_filename(target, suffix)) + unity_src_name = self.get_unity_source_filename(target, suffix) + unity_src_subdir = self.get_target_private_dir_abs(target) + outfilename = os.path.join(unity_src_subdir, + unity_src_name) outfileabs = os.path.join(self.environment.get_build_dir(), outfilename) outfileabs_tmp = outfileabs + '.tmp' @@ -161,7 +187,7 @@ class Backend: outfileabs_tmp_dir = os.path.dirname(outfileabs_tmp) if not os.path.exists(outfileabs_tmp_dir): os.makedirs(outfileabs_tmp_dir) - result.append(outfilename) + result.append(mesonlib.File(True, unity_src_subdir, unity_src_name)) return open(outfileabs_tmp, 'w') # For each language, generate a unity source file and return the list @@ -186,7 +212,7 @@ class Backend: elif isinstance(obj, mesonlib.File): obj_list.append(obj.rel_to_builddir(self.build_to_src)) elif isinstance(obj, build.ExtractedObjects): - obj_list += self.determine_ext_objs(obj, proj_dir_to_build_root) + obj_list += self.determine_ext_objs(target, obj, proj_dir_to_build_root) else: raise MesonException('Unknown data type in object list.') return obj_list @@ -261,32 +287,34 @@ class Backend: raise MesonException(m.format(target.name)) return l - def object_filename_from_source(self, target, source): + def object_filename_from_source(self, target, source, is_unity): if isinstance(source, mesonlib.File): source = source.fname # foo.vala files compile down to foo.c and then foo.c.o, not foo.vala.o if source.endswith('.vala'): + if is_unity: + return source[:-5] + '.c.' + self.environment.get_object_suffix() source = os.path.join(self.get_target_private_dir(target), source[:-5] + '.c') return source.replace('/', '_').replace('\\', '_') + '.' + self.environment.get_object_suffix() - def determine_ext_objs(self, extobj, proj_dir_to_build_root): + def determine_ext_objs(self, target, extobj, proj_dir_to_build_root): result = [] targetdir = self.get_target_private_dir(extobj.target) # With unity builds, there's just one object that contains all the # sources, and we only support extracting all the objects in this mode, # so just return that. - if self.environment.coredata.get_builtin_option('unity'): + if self.get_option_for_target('unity', target): comp = get_compiler_for_source(extobj.target.compilers.values(), extobj.srclist[0]) - # The unity object name uses the full absolute path of the source file - osrc = os.path.join(self.get_target_private_dir_abs(extobj.target), - self.get_unity_source_filename(extobj.target, - comp.get_default_suffix())) - objname = self.object_filename_from_source(extobj.target, osrc) + # There is a potential conflict here, but it is unlikely that + # anyone both enables unity builds and has a file called foo-unity.cpp. + osrc = self.get_unity_source_filename(extobj.target, + comp.get_default_suffix()) + objname = self.object_filename_from_source(extobj.target, osrc, True) objpath = os.path.join(proj_dir_to_build_root, targetdir, objname) return [objpath] for osrc in extobj.srclist: - objname = self.object_filename_from_source(extobj.target, osrc) + objname = self.object_filename_from_source(extobj.target, osrc, False) objpath = os.path.join(proj_dir_to_build_root, targetdir, objname) result.append(objpath) return result @@ -338,6 +366,8 @@ class Backend: # various sources in the order in which they must override each other # starting from hard-coded defaults followed by build options and so on. commands = CompilerArgs(compiler) + + copt_proxy = OptionOverrideProxy(target.option_overrides, self.environment.coredata.compiler_options) # First, the trivial ones that are impossible to override. # # Add -nostdinc/-nostdinc++ if needed; can't be overriden @@ -348,19 +378,19 @@ class Backend: # we weren't explicitly asked to not emit warnings (for Vala, f.ex) if no_warn_args: commands += compiler.get_no_warn_args() - elif self.environment.coredata.get_builtin_option('buildtype') != 'plain': - commands += compiler.get_warn_args(self.environment.coredata.get_builtin_option('warning_level')) + elif self.get_option_for_target('buildtype', target) != 'plain': + commands += compiler.get_warn_args(self.get_option_for_target('warning_level', target)) # Add -Werror if werror=true is set in the build options set on the # command-line or default_options inside project(). This only sets the # action to be done for warnings if/when they are emitted, so it's ok # to set it after get_no_warn_args() or get_warn_args(). - if self.environment.coredata.get_builtin_option('werror'): + if self.get_option_for_target('werror', target): commands += compiler.get_werror_args() # Add compile args for c_* or cpp_* build options set on the # command-line or default_options inside project(). - commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options) + commands += compiler.get_option_compile_args(copt_proxy) # Add buildtype args: optimization level, debugging, etc. - commands += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + commands += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) # Add compile args added using add_project_arguments() commands += self.build.get_project_args(compiler, target.subproject) # Add compile args added using add_global_arguments() diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index f29a7be..bc51ace 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -336,7 +336,7 @@ int dummy; outname = self.get_target_filename(target) obj_list = [] use_pch = self.environment.coredata.base_options.get('b_pch', False) - is_unity = self.environment.coredata.get_builtin_option('unity') + is_unity = self.get_option_for_target('unity', target) if use_pch and target.has_pch(): pch_objects = self.generate_pch(target, outfile) else: @@ -433,7 +433,7 @@ int dummy; obj_list += self.flatten_object_list(target) if is_unity: for src in self.generate_unity_files(target, unity_src): - obj_list.append(self.generate_single_compile(target, outfile, RawFilename(src), True, unity_deps + header_deps)) + obj_list.append(self.generate_single_compile(target, outfile, src, True, unity_deps + header_deps)) linker = self.determine_linker(target) elem = self.generate_link(target, outfile, outname, obj_list, linker, pch_objects) self.generate_shlib_aliases(target, self.get_target_dir(target)) @@ -627,9 +627,9 @@ int dummy; pickle.dump(d, ofile) def generate_target_install(self, d): - should_strip = self.environment.coredata.get_builtin_option('strip') for t in self.build.get_targets().values(): if t.should_install(): + should_strip = self.get_option_for_target('strip', t) # Find the installation directory. FIXME: Currently only one # installation directory is supported for each target outdir = t.get_custom_install_dir() @@ -843,7 +843,7 @@ int dummy; return args, deps def generate_cs_target(self, target, outfile): - buildtype = self.environment.coredata.get_builtin_option('buildtype') + buildtype = self.get_option_for_target('buildtype', target) fname = target.get_filename() outname_rel = os.path.join(self.get_target_dir(target), fname) src_list = target.get_sources() @@ -877,7 +877,7 @@ int dummy; def generate_single_java_compile(self, src, target, compiler, outfile): args = [] - args += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + args += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) args += self.build.get_global_args(compiler) args += self.build.get_project_args(compiler, target.subproject) args += target.get_java_args() @@ -1010,7 +1010,7 @@ int dummy; args = [] args += self.build.get_global_args(valac) args += self.build.get_project_args(valac, target.subproject) - args += valac.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + args += valac.get_buildtype_args(self.get_option_for_target('buildtype', target)) # Tell Valac to output everything in our private directory. Sadly this # means it will also preserve the directory components of Vala sources # found inside the build tree (generated sources). @@ -1033,7 +1033,7 @@ int dummy; girname = os.path.join(self.get_target_dir(target), target.vala_gir) args += ['--gir', os.path.join('..', target.vala_gir)] valac_outputs.append(girname) - if self.environment.coredata.get_builtin_option('werror'): + if self.get_option_for_target('werror', target): args += valac.get_werror_args() for d in target.get_external_deps(): if isinstance(d, dependencies.PkgConfigDependency): @@ -1088,7 +1088,7 @@ int dummy; else: raise InvalidArguments('Unknown target type for rustc.') args.append(cratetype) - args += rustc.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + args += rustc.get_buildtype_args(self.get_option_for_target('buildtype', target)) depfile = os.path.join(target.subdir, target.name + '.d') args += ['--emit', 'dep-info={}'.format(depfile), '--emit', 'link'] args += ['-o', os.path.join(target.subdir, target.get_filename())] @@ -1810,13 +1810,15 @@ rule FORTRAN_DEP_HACK return incs def _generate_single_compile(self, target, compiler, is_generated=False): + base_proxy = backends.OptionOverrideProxy(target.option_overrides, + self.environment.coredata.base_options) # Create an empty commands list, and start adding arguments from # various sources in the order in which they must override each other commands = CompilerArgs(compiler) # Add compiler args for compiling this target derived from 'base' build # options passed on the command-line, in default_options, etc. # These have the lowest priority. - commands += compilers.get_base_compile_args(self.environment.coredata.base_options, + commands += compilers.get_base_compile_args(base_proxy, compiler) # The code generated by valac is usually crap and has tons of unused # variables and such, so disable warnings for Vala C sources. @@ -1888,6 +1890,11 @@ rule FORTRAN_DEP_HACK raise AssertionError('BUG: sources should not contain headers {!r}'.format(src)) if isinstance(src, RawFilename) and src.fname.endswith('.h'): raise AssertionError('BUG: sources should not contain headers {!r}'.format(src.fname)) + + if isinstance(src, str) and src.endswith('.h'): + raise AssertionError('BUG: sources should not contain headers {!r}'.format(src)) + if isinstance(src, RawFilename) and src.fname.endswith('.h'): + raise AssertionError('BUG: sources should not contain headers {!r}'.format(src.fname)) compiler = get_compiler_for_source(target.compilers.values(), src) key = (target, compiler, is_generated) if key in self.target_arg_cache: @@ -1905,6 +1912,10 @@ rule FORTRAN_DEP_HACK abs_src = src.fname else: abs_src = os.path.join(self.environment.get_build_dir(), src.fname) + elif isinstance(src, mesonlib.File): + rel_src = src.rel_to_builddir(self.build_to_src) + abs_src = src.absolute_path(self.environment.get_source_dir(), + self.environment.get_build_dir()) elif is_generated: raise AssertionError('BUG: broken generated source file handling for {!r}'.format(src)) else: @@ -2145,7 +2156,7 @@ rule FORTRAN_DEP_HACK # Add things like /NOLOGO; usually can't be overriden commands += linker.get_linker_always_args() # Add buildtype linker args: optimization level, etc. - commands += linker.get_buildtype_linker_args(self.environment.coredata.get_builtin_option('buildtype')) + commands += linker.get_buildtype_linker_args(self.get_option_for_target('buildtype', target)) # Add /DEBUG and the pdb filename when using MSVC commands += self.get_link_debugfile_args(linker, target, outname) # Add link args specific to this BuildTarget type, such as soname args, diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 8c0cce6..7afbc0d 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -87,7 +87,7 @@ class Vs2010Backend(backends.Backend): self.vs_version = '2010' self.windows_target_platform_version = None - def object_filename_from_source(self, target, source): + def object_filename_from_source(self, target, source, is_unity=False): basename = os.path.basename(source.fname) filename_without_extension = '.'.join(basename.split('.')[:-1]) if basename in self.sources_conflicts[target.get_id()]: @@ -601,6 +601,8 @@ class Vs2010Backend(backends.Backend): # Prefix to use to access the source tree's subdir from the vcxproj dir proj_to_src_dir = os.path.join(proj_to_src_root, target.subdir) (sources, headers, objects, languages) = self.split_sources(target.sources) + if self.get_option_for_target('unity', target): + sources = self.generate_unity_files(target, sources) compiler = self._get_cl_compiler(target) buildtype_args = compiler.get_buildtype_args(self.buildtype) buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype) @@ -690,7 +692,7 @@ class Vs2010Backend(backends.Backend): elif '/Od' in o_flags: ET.SubElement(type_config, 'Optimization').text = 'Disabled' # Warning level - warning_level = self.environment.coredata.get_builtin_option('warning_level') + warning_level = self.get_option_for_target('warning_level', target) ET.SubElement(type_config, 'WarningLevel').text = 'Level' + warning_level # End configuration ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.props') @@ -844,7 +846,7 @@ class Vs2010Backend(backends.Backend): ET.SubElement(clconf, 'MinimalRebuild').text = 'true' ET.SubElement(clconf, 'FunctionLevelLinking').text = 'true' pch_node = ET.SubElement(clconf, 'PrecompiledHeader') - if self.environment.coredata.get_builtin_option('werror'): + if self.get_option_for_target('werror', target): ET.SubElement(clconf, 'TreatWarningAsError').text = 'true' # Note: SuppressStartupBanner is /NOLOGO and is 'true' by default pch_sources = {} diff --git a/mesonbuild/backend/vs2017backend.py b/mesonbuild/backend/vs2017backend.py index 8301790..35d56f3 100644 --- a/mesonbuild/backend/vs2017backend.py +++ b/mesonbuild/backend/vs2017backend.py @@ -24,4 +24,4 @@ class Vs2017Backend(Vs2010Backend): self.platform_toolset = 'v141' self.vs_version = '2017' # WindowsSDKVersion should be set by command prompt. - self.windows_target_platform_version = os.getenv('WindowsSDKVersion', None) + self.windows_target_platform_version = os.getenv('WindowsSDKVersion', None).rstrip('\\') diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 41e21e3..1246f3e 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -12,15 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy, os, re +from collections import OrderedDict + from . import environment from . import dependencies from . import mlog -import copy, os, re from .mesonlib import File, MesonException from .mesonlib import flatten, stringlistify, classify_unity_sources from .mesonlib import get_filenames_templates_dict, substitute_values from .environment import for_windows, for_darwin -from .compilers import is_object, clike_langs, lang_suffixes +from .compilers import is_object, clike_langs, sort_clike, lang_suffixes known_basic_kwargs = {'install': True, 'c_pch': True, @@ -47,6 +49,7 @@ known_basic_kwargs = {'install': True, 'objects': True, 'native': True, 'build_by_default': True, + 'override_options': True, } # These contain kwargs supported by both static and shared libraries. These are @@ -270,6 +273,7 @@ class Target: self.build_by_default = build_by_default self.install = False self.build_always = False + self.option_overrides = {} def get_basename(self): return self.name @@ -282,6 +286,20 @@ class Target: self.build_by_default = kwargs['build_by_default'] if not isinstance(self.build_by_default, bool): raise InvalidArguments('build_by_default must be a boolean value.') + self.option_overrides = self.parse_overrides(kwargs) + + def parse_overrides(self, kwargs): + result = {} + overrides = stringlistify(kwargs.get('override_options', [])) + for o in overrides: + if '=' not in o: + raise InvalidArguments('Overrides must be of form "key=value"') + k, v = o.split('=', 1) + k = k.strip() + v = v.strip() + result[k] = v + return result + class BuildTarget(Target): def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): @@ -291,7 +309,7 @@ class BuildTarget(Target): self.is_unity = environment.coredata.get_builtin_option('unity') self.environment = environment self.sources = [] - self.compilers = {} + self.compilers = OrderedDict() self.objects = [] self.external_deps = [] self.include_dirs = [] @@ -391,13 +409,6 @@ class BuildTarget(Target): raise InvalidArguments(msg) @staticmethod - def can_compile_sources(compiler, sources): - for s in sources: - if compiler.can_compile(s): - return True - return False - - @staticmethod def can_compile_remove_sources(compiler, sources): removed = False for s in sources[:]: @@ -442,13 +453,18 @@ class BuildTarget(Target): if not s.endswith(lang_suffixes['vala']): sources.append(s) if sources: - # Add compilers based on the above sources - for lang, compiler in compilers.items(): - # We try to be conservative because sometimes people add files - # in the list of sources that we can't determine the type based - # just on the suffix. - if self.can_compile_sources(compiler, sources): - self.compilers[lang] = compiler + # For each source, try to add one compiler that can compile it. + # It's ok if no compilers can do so, because users are expected to + # be able to add arbitrary non-source files to the sources list. + for s in sources: + for lang, compiler in compilers.items(): + if compiler.can_compile(s): + if lang not in self.compilers: + self.compilers[lang] = compiler + break + # Re-sort according to clike_langs + self.compilers = OrderedDict(sorted(self.compilers.items(), + key=lambda t: sort_clike(t[0]))) else: # No source files, target consists of only object files of unknown # origin. Just add the first clike compiler that we have and hope @@ -1237,6 +1253,7 @@ class CustomTarget(Target): 'depend_files': True, 'depfile': True, 'build_by_default': True, + 'override_options': True, } def __init__(self, name, subdir, kwargs, absolute_paths=False): diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index f16a05f..5e7db24 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -57,6 +57,17 @@ clike_suffixes += ('h', 'll', 's') # All these are only for C-like languages; see `clike_langs` above. +def sort_clike(lang): + ''' + Sorting function to sort the list of languages according to + reversed(compilers.clike_langs) and append the unknown langs in the end. + The purpose is to prefer C over C++ for files that can be compiled by + both such as assembly, C, etc. Also applies to ObjC, ObjC++, etc. + ''' + if lang not in clike_langs: + return 1 + return -clike_langs.index(lang) + def is_header(fname): if hasattr(fname, 'fname'): fname = fname.fname @@ -514,6 +525,11 @@ class Compiler: self.version = version self.base_options = [] + def __repr__(self): + repr_str = "<{0}: v{1} `{2}`>" + return repr_str.format(self.__class__.__name__, self.version, + ' '.join(self.exelist)) + def can_compile(self, src): if hasattr(src, 'fname'): src = src.fname diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 9562211..67516e7 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -14,8 +14,10 @@ import pickle, os, uuid from pathlib import PurePath +from collections import OrderedDict from .mesonlib import MesonException, commonpath from .mesonlib import default_libdir, default_libexecdir, default_prefix +import ast version = '0.40.0.dev1' backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode'] @@ -30,6 +32,12 @@ class UserOption: def parse_string(self, valuestring): return valuestring + # Check that the input is a valid value and return the + # "cleaned" or "native" version. For example the Boolean + # option could take the string "true" and return True. + def validate_value(self, value): + raise RuntimeError('Derived option class did not override validate_value.') + class UserStringOption(UserOption): def __init__(self, name, description, value, choices=None): super().__init__(name, description, choices) @@ -43,6 +51,10 @@ class UserStringOption(UserOption): self.validate(newvalue) self.value = newvalue + def validate_value(self, value): + self.validate(value) + return value + class UserBooleanOption(UserOption): def __init__(self, name, description, value): super().__init__(name, description, [True, False]) @@ -70,6 +82,9 @@ class UserBooleanOption(UserOption): def __bool__(self): return self.value + def validate_value(self, value): + return self.tobool(value) + class UserComboOption(UserOption): def __init__(self, name, description, choices, value): super().__init__(name, description, choices) @@ -86,22 +101,36 @@ class UserComboOption(UserOption): raise MesonException('Value "%s" for combo option "%s" is not one of the choices. Possible choices are: %s.' % (newvalue, self.name, optionsstring)) self.value = newvalue + def validate_value(self, value): + if value not in self.choices: + raise MesonException('Value %s not one of accepted values.' % value) + return value + class UserStringArrayOption(UserOption): def __init__(self, name, description, value, **kwargs): super().__init__(name, description, kwargs.get('choices', [])) self.set_value(value) - def set_value(self, newvalue): - if isinstance(newvalue, str): - if not newvalue.startswith('['): - raise MesonException('Valuestring does not define an array: ' + newvalue) - newvalue = eval(newvalue, {}, {}) # Yes, it is unsafe. + def validate(self, value): + if isinstance(value, str): + if not value.startswith('['): + raise MesonException('Valuestring does not define an array: ' + value) + newvalue = ast.literal_eval(value) + else: + newvalue = value if not isinstance(newvalue, list): raise MesonException('"{0}" should be a string array, but it is not'.format(str(newvalue))) for i in newvalue: if not isinstance(i, str): raise MesonException('String array element "{0}" is not a string.'.format(str(newvalue))) - self.value = newvalue + return newvalue + + def set_value(self, newvalue): + self.value = self.validate(newvalue) + + def validate_value(self, value): + self.validate(value) + return value # This class contains all data that must persist over multiple # invocations of Meson. It is roughly the same thing as @@ -128,8 +157,8 @@ class CoreData: else: self.cross_file = None self.wrap_mode = options.wrap_mode - self.compilers = {} - self.cross_compilers = {} + self.compilers = OrderedDict() + self.cross_compilers = OrderedDict() self.deps = {} self.modules = {} # Only to print a warning if it changes between Meson invocations. @@ -203,6 +232,13 @@ class CoreData: raise RuntimeError('Tried to set unknown builtin option %s.' % optname) self.builtins[optname].set_value(value) + def validate_option_value(self, option_name, override_value): + for opts in (self.builtins, self.base_options, self.compiler_options, self.user_options): + if option_name in opts: + opt = opts[option_name] + return opt.validate_value(override_value) + raise MesonException('Tried to validate unknown option %s.' % option_name) + def load(filename): load_fail_msg = 'Coredata file {!r} is corrupted. Try with a fresh build tree.'.format(filename) try: diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 7ee4bb9..af2c17d 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1613,6 +1613,8 @@ class Interpreter(InterpreterBase): @stringArgs def func_project(self, node, args, kwargs): + if len(args) < 1: + raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') default_options = kwargs.get('default_options', []) if self.environment.first_invocation and (len(default_options) > 0 or len(self.default_project_options) > 0): @@ -1625,8 +1627,6 @@ class Interpreter(InterpreterBase): ) oi.process(self.option_file) self.build.environment.merge_options(oi.options) - if len(args) < 2: - raise InvalidArguments('Not enough arguments to project(). Needs at least the project name and one language') self.active_projectname = args[0] self.project_version = kwargs.get('version', 'undefined') if self.build.project_version is None: @@ -1755,7 +1755,7 @@ class Interpreter(InterpreterBase): def add_languages(self, args, required): success = True need_cross_compiler = self.environment.is_cross_build() and self.environment.cross_info.need_cross_compiler() - for lang in args: + for lang in sorted(args, key=compilers.sort_clike): lang = lang.lower() if lang in self.coredata.compilers: comp = self.coredata.compilers[lang] @@ -2290,7 +2290,11 @@ class Interpreter(InterpreterBase): inputfile = inputfile[0] if not isinstance(inputfile, (str, mesonlib.File)): raise InterpreterException('Input must be a string or a file') - ifile_abs = os.path.join(self.environment.source_dir, self.subdir, inputfile) + if isinstance(inputfile, str): + inputfile = os.path.join(self.subdir, inputfile) + else: + inputfile = inputfile.relative_name() + ifile_abs = os.path.join(self.environment.source_dir, inputfile) elif 'command' in kwargs and '@INPUT@' in kwargs['command']: raise InterpreterException('@INPUT@ used as command argument, but no input file specified.') # Validate output @@ -2309,7 +2313,7 @@ class Interpreter(InterpreterBase): if inputfile is not None: # Normalize the path of the conffile to avoid duplicates # This is especially important to convert '/' to '\' on Windows - conffile = os.path.normpath(os.path.join(self.subdir, inputfile)) + conffile = os.path.normpath(inputfile) if conffile not in self.build_def_files: self.build_def_files.append(conffile) os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index c7368d5..ba52219 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -121,16 +121,18 @@ class File: self.is_built = is_built self.subdir = subdir self.fname = fname + assert(isinstance(self.subdir, str)) + assert(isinstance(self.fname, str)) def __str__(self): - return os.path.join(self.subdir, self.fname) + return self.relative_name() def __repr__(self): ret = '<File: {0}' if not self.is_built: ret += ' (not built)' ret += '>' - return ret.format(os.path.join(self.subdir, self.fname)) + return ret.format(self.relative_name()) @staticmethod def from_source_file(source_root, subdir, fname): @@ -148,15 +150,15 @@ class File: def rel_to_builddir(self, build_to_src): if self.is_built: - return os.path.join(self.subdir, self.fname) + return self.relative_name() else: return os.path.join(build_to_src, self.subdir, self.fname) def absolute_path(self, srcdir, builddir): + absdir = srcdir if self.is_built: - return os.path.join(builddir, self.subdir, self.fname) - else: - return os.path.join(srcdir, self.subdir, self.fname) + absdir = builddir + return os.path.join(absdir, self.relative_name()) def endswith(self, ending): return self.fname.endswith(ending) diff --git a/mesonbuild/modules/python3.py b/mesonbuild/modules/python3.py index 53e28c4..9f01043 100644 --- a/mesonbuild/modules/python3.py +++ b/mesonbuild/modules/python3.py @@ -13,11 +13,13 @@ # limitations under the License. import sys +import sysconfig from .. import mesonlib, dependencies from . import ExtensionModule from mesonbuild.modules import ModuleReturnValue + class Python3Module(ExtensionModule): def __init__(self): super().__init__() @@ -45,5 +47,25 @@ class Python3Module(ExtensionModule): py3 = dependencies.ExternalProgram('python3', sys.executable, silent=True) return ModuleReturnValue(py3, [py3]) + def language_version(self, state, args, kwargs): + if args or kwargs: + raise mesonlib.MesonException('language_version() takes no arguments.') + return ModuleReturnValue(sysconfig.get_python_version(), []) + + def sysconfig_path(self, state, args, kwargs): + if len(args) != 1: + raise mesonlib.MesonException('sysconfig_path() requires passing the name of path to get.') + if kwargs: + raise mesonlib.MesonException('sysconfig_path() does not accept keywords.') + path_name = args[0] + valid_names = sysconfig.get_path_names() + if path_name not in valid_names: + raise mesonlib.MesonException('{} is not a valid path name {}.'.format(path_name, valid_names)) + + # Get a relative path without a prefix, e.g. lib/python3.6/site-packages + path = sysconfig.get_path(path_name, vars={'base': ''})[1:] + return ModuleReturnValue(path, []) + + def initialize(): return Python3Module() diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index fcacc16..67e4700 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -39,7 +39,7 @@ def build_ssl_context(): return ctx def quiet_git(cmd): - pc = subprocess.Popen(['git'] + cmd, stdout=subprocess.PIPE) + pc = subprocess.Popen(['git'] + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = pc.communicate() if pc.returncode != 0: return False, err diff --git a/run_tests.py b/run_tests.py index 5025057..02aa701 100755 --- a/run_tests.py +++ b/run_tests.py @@ -21,6 +21,12 @@ import subprocess import platform from mesonbuild import mesonlib +def using_vs_backend(): + for arg in sys.argv[1:]: + if arg.startswith('--backend=vs'): + return True + return False + if __name__ == '__main__': returncode = 0 # Running on a developer machine? Be nice! @@ -32,7 +38,10 @@ if __name__ == '__main__': units += ['LinuxlikeTests'] elif mesonlib.is_windows(): units += ['WindowsTests'] - returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v'] + units) + # Unit tests always use the Ninja backend, so just skip them if we're + # testing the VS backend + if not using_vs_backend(): + returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v'] + units) # Ubuntu packages do not have a binary without -6 suffix. if shutil.which('arm-linux-gnueabihf-gcc-6') and not platform.machine().startswith('arm'): print('Running cross compilation tests.\n') diff --git a/run_unittests.py b/run_unittests.py index 66f8205..0656f88 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -880,6 +880,73 @@ class AllPlatformTests(BasePlatformTests): self.assertEqual(wcc.get_exelist(), wrappercc) self.assertEqual(wlinker.get_exelist(), wrapperlinker) + def test_always_prefer_c_compiler_for_asm(self): + testdir = os.path.join(self.common_test_dir, '141 c cpp and asm') + # Skip if building with MSVC + env = Environment(testdir, self.builddir, self.meson_command, + get_fake_options(self.prefix), []) + if env.detect_c_compiler(False).get_id() == 'msvc': + raise unittest.SkipTest('MSVC can\'t compile assembly') + self.init(testdir) + commands = {'c-asm': {}, 'cpp-asm': {}, 'cpp-c-asm': {}, 'c-cpp-asm': {}} + for cmd in self.get_compdb(): + # Get compiler + split = shlex.split(cmd['command']) + if split[0] == 'ccache': + compiler = split[1] + else: + compiler = split[0] + # Classify commands + if 'Ic-asm' in cmd['command']: + if cmd['file'].endswith('.S'): + commands['c-asm']['asm'] = compiler + elif cmd['file'].endswith('.c'): + commands['c-asm']['c'] = compiler + else: + raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command'])) + elif 'Icpp-asm' in cmd['command']: + if cmd['file'].endswith('.S'): + commands['cpp-asm']['asm'] = compiler + elif cmd['file'].endswith('.cpp'): + commands['cpp-asm']['cpp'] = compiler + else: + raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command'])) + elif 'Ic-cpp-asm' in cmd['command']: + if cmd['file'].endswith('.S'): + commands['c-cpp-asm']['asm'] = compiler + elif cmd['file'].endswith('.c'): + commands['c-cpp-asm']['c'] = compiler + elif cmd['file'].endswith('.cpp'): + commands['c-cpp-asm']['cpp'] = compiler + else: + raise AssertionError('{!r} found in c-cpp-asm?'.format(cmd['command'])) + elif 'Icpp-c-asm' in cmd['command']: + if cmd['file'].endswith('.S'): + commands['cpp-c-asm']['asm'] = compiler + elif cmd['file'].endswith('.c'): + commands['cpp-c-asm']['c'] = compiler + elif cmd['file'].endswith('.cpp'): + commands['cpp-c-asm']['cpp'] = compiler + else: + raise AssertionError('{!r} found in cpp-c-asm?'.format(cmd['command'])) + else: + raise AssertionError('Unknown command {!r} found'.format(cmd['command'])) + # Check that .S files are always built with the C compiler + self.assertEqual(commands['c-asm']['asm'], commands['c-asm']['c']) + self.assertEqual(commands['c-asm']['asm'], commands['cpp-asm']['asm']) + self.assertEqual(commands['cpp-asm']['asm'], commands['c-cpp-asm']['c']) + self.assertEqual(commands['c-cpp-asm']['asm'], commands['c-cpp-asm']['c']) + self.assertEqual(commands['cpp-c-asm']['asm'], commands['cpp-c-asm']['c']) + self.assertNotEqual(commands['cpp-asm']['asm'], commands['cpp-asm']['cpp']) + self.assertNotEqual(commands['c-cpp-asm']['c'], commands['c-cpp-asm']['cpp']) + self.assertNotEqual(commands['cpp-c-asm']['c'], commands['cpp-c-asm']['cpp']) + # Check that the c-asm target is always linked with the C linker + build_ninja = os.path.join(self.builddir, 'build.ninja') + with open(build_ninja, 'r', encoding='utf-8') as f: + contents = f.read() + m = re.search('build c-asm.*: c_LINKER', contents) + self.assertIsNotNone(m, msg=contents) + class WindowsTests(BasePlatformTests): ''' @@ -1261,6 +1328,27 @@ class LinuxlikeTests(BasePlatformTests): # The chown failed nonfatally if we're not root self.assertEqual(0, statf.st_uid) + def test_cpp_std_override(self): + testdir = os.path.join(self.unit_test_dir, '6 std override') + self.init(testdir) + compdb = self.get_compdb() + for i in compdb: + if 'prog03' in i['file']: + c03_comp = i['command'] + if 'prog11' in i['file']: + c11_comp = i['command'] + if 'progp' in i['file']: + plain_comp = i['command'] + self.assertNotEqual(len(plain_comp), 0) + self.assertIn('-std=c++03', c03_comp) + self.assertNotIn('-std=c++11', c03_comp) + self.assertIn('-std=c++11', c11_comp) + self.assertNotIn('-std=c++03', c11_comp) + self.assertNotIn('-std=c++03', plain_comp) + self.assertNotIn('-std=c++11', plain_comp) + # Now werror + self.assertIn('-Werror', plain_comp) + self.assertNotIn('-Werror', c03_comp) class RewriterTests(unittest.TestCase): diff --git a/test cases/common/139 override options/four.c b/test cases/common/139 override options/four.c new file mode 100644 index 0000000..54f8491 --- /dev/null +++ b/test cases/common/139 override options/four.c @@ -0,0 +1,9 @@ +int func(); + +static int duplicate_func() { + return -4; +} + +int main(int argc, char **argv) { + return duplicate_func() + func(); +} diff --git a/test cases/common/139 override options/meson.build b/test cases/common/139 override options/meson.build new file mode 100644 index 0000000..0db0513 --- /dev/null +++ b/test cases/common/139 override options/meson.build @@ -0,0 +1,8 @@ +project('option override', 'c', + default_options : 'unity=true') + +executable('mustunity', 'one.c', 'two.c') +executable('notunity', 'three.c', 'four.c', + override_options : ['unity=false']) + + diff --git a/test cases/common/139 override options/one.c b/test cases/common/139 override options/one.c new file mode 100644 index 0000000..14fe9d6 --- /dev/null +++ b/test cases/common/139 override options/one.c @@ -0,0 +1,3 @@ +static int hidden_func() { + return 0; +} diff --git a/test cases/common/139 override options/three.c b/test cases/common/139 override options/three.c new file mode 100644 index 0000000..305a575 --- /dev/null +++ b/test cases/common/139 override options/three.c @@ -0,0 +1,7 @@ +static int duplicate_func() { + return 4; +} + +int func() { + return duplicate_func(); +} diff --git a/test cases/common/139 override options/two.c b/test cases/common/139 override options/two.c new file mode 100644 index 0000000..04b1d3f --- /dev/null +++ b/test cases/common/139 override options/two.c @@ -0,0 +1,6 @@ +/* + * Requires a Unity build. Otherwise hidden_func is not specified. + */ +int main(int argc, char **argv) { + return hidden_func(); +} diff --git a/test cases/common/141 c cpp and asm/main.c b/test cases/common/141 c cpp and asm/main.c new file mode 100644 index 0000000..8976723 --- /dev/null +++ b/test cases/common/141 c cpp and asm/main.c @@ -0,0 +1,8 @@ +#include <stdio.h> + +int get_retval(void); + +int main(int argc, char **argv) { + printf("C seems to be working.\n"); + return get_retval(); +} diff --git a/test cases/common/141 c cpp and asm/main.cpp b/test cases/common/141 c cpp and asm/main.cpp new file mode 100644 index 0000000..c089870 --- /dev/null +++ b/test cases/common/141 c cpp and asm/main.cpp @@ -0,0 +1,11 @@ +#include <iostream> + +extern "C" { + int get_retval(void); + int get_cval(void); +} + +int main(int argc, char **argv) { + std::cout << "C++ seems to be working." << std::endl; + return get_retval(); +} diff --git a/test cases/common/141 c cpp and asm/meson.build b/test cases/common/141 c cpp and asm/meson.build new file mode 100644 index 0000000..2c3610e --- /dev/null +++ b/test cases/common/141 c cpp and asm/meson.build @@ -0,0 +1,23 @@ +project('c cpp and asm', 'c', 'cpp') + +cpu = host_machine.cpu_family() +cc = meson.get_compiler('c') + +supported_cpus = ['arm', 'x86', 'x86_64'] + +if not supported_cpus.contains(cpu) + error('MESON_SKIP_TEST unsupported cpu:' + cpu) +endif + +if meson.get_compiler('c').get_id() == 'msvc' + error('MESON_SKIP_TEST MSVC can\'t compile assembly') +endif + +if cc.symbols_have_underscore_prefix() + add_project_arguments('-DMESON_TEST__UNDERSCORE_SYMBOL', language: 'c') +endif + +test('test-c-asm', executable('c-asm', ['main.c', 'retval-' + cpu + '.S'])) +test('test-cpp-asm', executable('cpp-asm', ['main.cpp', 'retval-' + cpu + '.S'])) +test('test-c-cpp-asm', executable('c-cpp-asm', ['somelib.c', 'main.cpp', 'retval-' + cpu + '.S'])) +test('test-cpp-c-asm', executable('cpp-c-asm', ['main.cpp', 'somelib.c', 'retval-' + cpu + '.S'])) diff --git a/test cases/common/141 c cpp and asm/retval-arm.S b/test cases/common/141 c cpp and asm/retval-arm.S new file mode 100644 index 0000000..8b37197 --- /dev/null +++ b/test cases/common/141 c cpp and asm/retval-arm.S @@ -0,0 +1,8 @@ +#include "symbol-underscore.h" + +.text +.globl SYMBOL_NAME(get_retval) + +SYMBOL_NAME(get_retval): + mov r0, #0 + mov pc, lr diff --git a/test cases/common/141 c cpp and asm/retval-x86.S b/test cases/common/141 c cpp and asm/retval-x86.S new file mode 100644 index 0000000..06bd75c --- /dev/null +++ b/test cases/common/141 c cpp and asm/retval-x86.S @@ -0,0 +1,8 @@ +#include "symbol-underscore.h" + +.text +.globl SYMBOL_NAME(get_retval) + +SYMBOL_NAME(get_retval): + xorl %eax, %eax + retl diff --git a/test cases/common/141 c cpp and asm/retval-x86_64.S b/test cases/common/141 c cpp and asm/retval-x86_64.S new file mode 100644 index 0000000..638921e --- /dev/null +++ b/test cases/common/141 c cpp and asm/retval-x86_64.S @@ -0,0 +1,8 @@ +#include "symbol-underscore.h" + +.text +.globl SYMBOL_NAME(get_retval) + +SYMBOL_NAME(get_retval): + xorl %eax, %eax + retq diff --git a/test cases/common/141 c cpp and asm/somelib.c b/test cases/common/141 c cpp and asm/somelib.c new file mode 100644 index 0000000..e585b8e --- /dev/null +++ b/test cases/common/141 c cpp and asm/somelib.c @@ -0,0 +1,3 @@ +int get_cval (void) { + return 0; +} diff --git a/test cases/common/141 c cpp and asm/symbol-underscore.h b/test cases/common/141 c cpp and asm/symbol-underscore.h new file mode 100644 index 0000000..d0f3ef9 --- /dev/null +++ b/test cases/common/141 c cpp and asm/symbol-underscore.h @@ -0,0 +1,5 @@ +#if defined(MESON_TEST__UNDERSCORE_SYMBOL) +# define SYMBOL_NAME(name) _##name +#else +# define SYMBOL_NAME(name) name +#endif diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index 8271ca3..8ffc28c 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -22,6 +22,11 @@ e = executable('inctest', 'prog.c', cfile) test('inctest', e) +# Test if we can also pass files() as input +configure_file(input : files('config.h.in'), + output : 'config2.h', + configuration : conf) + # Now generate a header file with an external script. genprog = import('python3').find_python() scriptfile = '@0@/generator.py'.format(meson.current_source_dir()) diff --git a/test cases/common/9 header install/meson.build b/test cases/common/9 header install/meson.build index 7f3ce51..7dfbddb 100644 --- a/test cases/common/9 header install/meson.build +++ b/test cases/common/9 header install/meson.build @@ -1,4 +1,4 @@ -project('header install', 'c') +project('header install') as_array = ['subdir.h'] diff --git a/test cases/python3/1 basic/meson.build b/test cases/python3/1 basic/meson.build index 9d5f874..111b717 100644 --- a/test cases/python3/1 basic/meson.build +++ b/test cases/python3/1 basic/meson.build @@ -3,6 +3,16 @@ project('python sample', 'c') py3_mod = import('python3') py3 = py3_mod.find_python() +py3_version = py3_mod.language_version() +if py3_version.version_compare('< 3.2') + error('Invalid python version!?') +endif + +py3_purelib = py3_mod.sysconfig_path('purelib') +if not py3_purelib.endswith('site-packages') + error('Python3 purelib path seems invalid?') +endif + main = files('prog.py') test('toplevel', py3, args : main) diff --git a/test cases/unit/5 compiler detection/meson.build b/test cases/unit/5 compiler detection/meson.build index 5491c64..8b47bd4 100644 --- a/test cases/unit/5 compiler detection/meson.build +++ b/test cases/unit/5 compiler detection/meson.build @@ -3,6 +3,6 @@ project('trivial test', meson_version : '>=0.27.0') executable('trivialc', 'trivial.c') -executable('trivialcpp', 'trivial.cpp') +executable('trivialcpp', 'trivial.cc') executable('trivialobjc', 'trivial.m') executable('trivialobjcpp', 'trivial.mm') diff --git a/test cases/unit/6 std override/meson.build b/test cases/unit/6 std override/meson.build new file mode 100644 index 0000000..ef2baac --- /dev/null +++ b/test cases/unit/6 std override/meson.build @@ -0,0 +1,10 @@ +project('cpp std override', 'cpp', + default_options : ['cpp_std=c++03', + 'werror=true']) + +executable('plain', 'progp.cpp', + override_options : 'cpp_std=none') +executable('v03', 'prog03.cpp', + override_options : 'werror=false') +executable('v11', 'prog11.cpp', + override_options : 'cpp_std=c++11') diff --git a/test cases/unit/6 std override/prog03.cpp b/test cases/unit/6 std override/prog03.cpp new file mode 100644 index 0000000..d30abc9 --- /dev/null +++ b/test cases/unit/6 std override/prog03.cpp @@ -0,0 +1,6 @@ +#include<iostream> + +int main(int argc, char **argv) { + std::cout << "I am a c++03 test program.\n"; + return 0; +} diff --git a/test cases/unit/6 std override/prog11.cpp b/test cases/unit/6 std override/prog11.cpp new file mode 100644 index 0000000..dde1fc0 --- /dev/null +++ b/test cases/unit/6 std override/prog11.cpp @@ -0,0 +1,6 @@ +#include<iostream> + +int main(int argc, char **argv) { + std::cout << "I am a C++11 test program.\n"; + return 0; +} diff --git a/test cases/unit/6 std override/progp.cpp b/test cases/unit/6 std override/progp.cpp new file mode 100644 index 0000000..b9bd97f --- /dev/null +++ b/test cases/unit/6 std override/progp.cpp @@ -0,0 +1,6 @@ +#include<iostream> + +int main(int argc, char **argv) { + std::cout << "I am a test program of undefined C++ standard.\n"; + return 0; +} |