Merge pull request #1457 from mesonbuild/overrides

Add MVP implementation of overriding options.
pull/1440/merge
Jussi Pakkanen 8 years ago committed by GitHub
commit 8b73d80792
  1. 66
      mesonbuild/backend/backends.py
  2. 31
      mesonbuild/backend/ninjabackend.py
  3. 8
      mesonbuild/backend/vs2010backend.py
  4. 17
      mesonbuild/build.py
  5. 47
      mesonbuild/coredata.py
  6. 2
      mesonbuild/mesonlib.py
  7. 21
      run_unittests.py
  8. 9
      test cases/common/139 override options/four.c
  9. 8
      test cases/common/139 override options/meson.build
  10. 3
      test cases/common/139 override options/one.c
  11. 7
      test cases/common/139 override options/three.c
  12. 6
      test cases/common/139 override options/two.c
  13. 10
      test cases/unit/6 std override/meson.build
  14. 6
      test cases/unit/6 std override/prog03.cpp
  15. 6
      test cases/unit/6 std override/prog11.cpp
  16. 6
      test cases/unit/6 std override/progp.cpp

@ -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()

@ -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.
@ -1884,6 +1886,11 @@ rule FORTRAN_DEP_HACK
"""
Compiles C/C++, ObjC/ObjC++, Fortran, and D sources
"""
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))
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'):
@ -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,

@ -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 = {}

@ -49,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
@ -272,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
@ -284,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):
@ -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):

@ -17,6 +17,7 @@ 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']
@ -31,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)
@ -44,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])
@ -71,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)
@ -87,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
@ -204,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:

@ -121,6 +121,8 @@ 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)

@ -1328,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):

@ -0,0 +1,9 @@
int func();
static int duplicate_func() {
return -4;
}
int main(int argc, char **argv) {
return duplicate_func() + func();
}

@ -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'])

@ -0,0 +1,3 @@
static int hidden_func() {
return 0;
}

@ -0,0 +1,7 @@
static int duplicate_func() {
return 4;
}
int func() {
return duplicate_func();
}

@ -0,0 +1,6 @@
/*
* Requires a Unity build. Otherwise hidden_func is not specified.
*/
int main(int argc, char **argv) {
return hidden_func();
}

@ -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')

@ -0,0 +1,6 @@
#include<iostream>
int main(int argc, char **argv) {
std::cout << "I am a c++03 test program.\n";
return 0;
}

@ -0,0 +1,6 @@
#include<iostream>
int main(int argc, char **argv) {
std::cout << "I am a C++11 test program.\n";
return 0;
}

@ -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;
}
Loading…
Cancel
Save