Add general override functionality. Closes #3001.
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index 49b84b9..55b52d4 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -374,6 +374,22 @@
     def clear(self) -> None:
         self.__cache.clear()
 
+
+class OptionOverrides:
+    def __init__(self):
+        self.subproject_overrides = {} # Overrides option value in all subprojects but not the main project.
+        # FIXME add per-project overrides here
+
+    def add_subprojects_override(self, key, value):
+        self.subproject_overrides[key] = value
+
+    def value_if_overridden(self, key, subproject): # FIXME add target as an argument?
+        if subproject == '':
+            return None
+        if key not in self.subproject_overrides:
+            return None
+        return self.subproject_overrides[key]
+
 # Can't bind this near the class method it seems, sadly.
 _V = T.TypeVar('_V')
 
@@ -400,6 +416,7 @@
         self.options: 'KeyedOptionDictType' = {}
         self.cross_files = self.__load_config_files(options, scratch_dir, 'cross')
         self.compilers = PerMachine(OrderedDict(), OrderedDict())  # type: PerMachine[T.Dict[str, Compiler]]
+        self.overrides = OptionOverrides()
 
         # Set of subprojects that have already been initialized once, this is
         # required to be stored and reloaded with the coredata, as we don't
@@ -415,6 +432,7 @@
         self.config_files = self.__load_config_files(options, scratch_dir, 'native')
         self.builtin_options_libdir_cross_fixup()
         self.init_builtins('')
+        self.init_overrides()
 
     @staticmethod
     def __load_config_files(options: argparse.Namespace, scratch_dir: str, ftype: str) -> T.List[str]:
@@ -541,6 +559,11 @@
             for key, opt in BUILTIN_OPTIONS_PER_MACHINE.items():
                 self.add_builtin_option(self.options, key.evolve(subproject=subproject, machine=for_machine), opt)
 
+    def init_overrides(self) -> None:
+        sections = MachineFileParser(self.config_files).sections
+        for k, v in sections.get('override_subprojects', {}).items():
+            self.overrides.add_subprojects_override(k, v)
+
     @staticmethod
     def add_builtin_option(opts_map: 'KeyedOptionDictType', key: OptionKey,
                            opt: 'BuiltinOption') -> None:
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index c67e840..a4c94ed 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -858,6 +858,10 @@
     def get_option_internal(self, optname: str):
         key = OptionKey.from_string(optname).evolve(subproject=self.subproject)
 
+        maybe_v = self.coredata.overrides.value_if_overridden(optname, self.subproject)
+        if maybe_v is not None:
+            return maybe_v
+
         if not key.is_project():
             for opts in [self.coredata.options, compilers.base_options]:
                 v = opts.get(key)
diff --git a/run_unittests.py b/run_unittests.py
index 89b00f6..efa49a7 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -5702,6 +5702,11 @@
                 link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language)
                 self.assertEqual(sorted(link_args), sorted(['-flto']))
 
+    def test_overrides(self):
+        testdir = os.path.join(self.unit_test_dir, '96 overriding')
+        native_file = os.path.join(testdir, 'native_overrides.txt')
+        self.init(testdir, extra_args=['--native-file', native_file])
+
 class FailureTests(BasePlatformTests):
     '''
     Tests that test failure conditions. Build files here should be dynamically
diff --git a/test cases/unit/96 overriding/meson.build b/test cases/unit/96 overriding/meson.build
new file mode 100644
index 0000000..cbed903
--- /dev/null
+++ b/test cases/unit/96 overriding/meson.build
@@ -0,0 +1,4 @@
+project('mainprog', 'c')
+
+assert(get_option('buildtype') == 'debug')
+subproject('somesub')
diff --git a/test cases/unit/96 overriding/native_overrides.txt b/test cases/unit/96 overriding/native_overrides.txt
new file mode 100644
index 0000000..e40a2b2
--- /dev/null
+++ b/test cases/unit/96 overriding/native_overrides.txt
@@ -0,0 +1,2 @@
+[override_subprojects]
+buildtype = 'debugoptimized'
diff --git a/test cases/unit/96 overriding/subprojects/somesub/meson.build b/test cases/unit/96 overriding/subprojects/somesub/meson.build
new file mode 100644
index 0000000..f513bff
--- /dev/null
+++ b/test cases/unit/96 overriding/subprojects/somesub/meson.build
@@ -0,0 +1,3 @@
+project('somesub', 'c')
+
+assert(get_option('buildtype') == 'debugoptimized')