mconf: Reload the options files if they have changed

This fixes issues where a new option is added, an option is removed, the
constraints of an option are changed, an option file is added where one
didn't previously exist, an option file is deleted, or it is renamed
between meson_options.txt and meson.options

There is one case that is known to not work, but it's probably a less
common case, which is setting options for an unconfigured subproject.
We could probably make that work in some cases, but I don't think it
makes sense to download a wrap during meson configure.
pull/13000/head
Dylan Baker 9 months ago
parent c6875305f3
commit 2d7b7c3aaf
  1. 12
      docs/markdown/snippets/meson_configure_options_changes.md
  2. 4
      mesonbuild/interpreter/interpreter.py
  3. 31
      mesonbuild/mconf.py
  4. 10
      unittests/platformagnostictests.py

@ -0,0 +1,12 @@
## Meson configure handles changes to options in more cases
Meson configure now correctly handles updates to the options file without a full
reconfigure. This allows making a change to the `meson.options` or
`meson_options.txt` file without a reconfigure.
For example, this now works:
```sh
meson setup builddir
git pull
meson configure builddir -Doption-added-by-pull=value
```

@ -1190,8 +1190,8 @@ class Interpreter(InterpreterBase, HoldableObject):
option_file = old_option_file option_file = old_option_file
if os.path.exists(option_file): if os.path.exists(option_file):
with open(option_file, 'rb') as f: with open(option_file, 'rb') as f:
# We want fast, not cryptographically secure, this is just to see of # We want fast not cryptographically secure, this is just to
# the option file has changed # see if the option file has changed
self.coredata.options_files[self.subproject] = (option_file, hashlib.sha1(f.read()).hexdigest()) self.coredata.options_files[self.subproject] = (option_file, hashlib.sha1(f.read()).hexdigest())
oi = optinterpreter.OptionInterpreter(self.subproject) oi = optinterpreter.OptionInterpreter(self.subproject)
oi.process(option_file) oi.process(option_file)

@ -1,10 +1,11 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# Copyright 2014-2016 The Meson development team # Copyright 2014-2016 The Meson development team
# Copyright © 2023 Intel Corporation # Copyright © 2023-2024 Intel Corporation
from __future__ import annotations from __future__ import annotations
import itertools import itertools
import hashlib
import shutil import shutil
import os import os
import textwrap import textwrap
@ -19,6 +20,7 @@ from . import mintro
from . import mlog from . import mlog
from .ast import AstIDGenerator, IntrospectionInterpreter from .ast import AstIDGenerator, IntrospectionInterpreter
from .mesonlib import MachineChoice, OptionKey from .mesonlib import MachineChoice, OptionKey
from .optinterpreter import OptionInterpreter
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from typing_extensions import Protocol from typing_extensions import Protocol
@ -77,6 +79,33 @@ class Conf:
self.source_dir = self.build.environment.get_source_dir() self.source_dir = self.build.environment.get_source_dir()
self.coredata = self.build.environment.coredata self.coredata = self.build.environment.coredata
self.default_values_only = False self.default_values_only = False
# if the option file has been updated, reload it
# This cannot handle options for a new subproject that has not yet
# been configured.
for sub, options in self.coredata.options_files.items():
if options is not None and os.path.exists(options[0]):
opfile = options[0]
with open(opfile, 'rb') as f:
ophash = hashlib.sha1(f.read()).hexdigest()
if ophash != options[1]:
oi = OptionInterpreter(sub)
oi.process(opfile)
self.coredata.update_project_options(oi.options, sub)
self.coredata.options_files[sub] = (opfile, ophash)
else:
opfile = os.path.join(self.source_dir, 'meson.options')
if not os.path.exists(opfile):
opfile = os.path.join(self.source_dir, 'meson_options.txt')
if os.path.exists(opfile):
oi = OptionInterpreter(sub)
oi.process(opfile)
self.coredata.update_project_options(oi.options, sub)
with open(opfile, 'rb') as f:
ophash = hashlib.sha1(f.read()).hexdigest()
self.coredata.options_files[sub] = (opfile, ophash)
else:
self.coredata.update_project_options({}, sub)
elif os.path.isfile(os.path.join(self.build_dir, environment.build_filename)): elif os.path.isfile(os.path.join(self.build_dir, environment.build_filename)):
# Make sure that log entries in other parts of meson don't interfere with the JSON output # Make sure that log entries in other parts of meson don't interfere with the JSON output
with mlog.no_logging(): with mlog.no_logging():

@ -10,7 +10,7 @@ import tempfile
import subprocess import subprocess
import textwrap import textwrap
import shutil import shutil
from unittest import expectedFailure, skipIf, SkipTest from unittest import skipIf, SkipTest
from pathlib import Path from pathlib import Path
from .baseplatformtests import BasePlatformTests from .baseplatformtests import BasePlatformTests
@ -318,7 +318,6 @@ class PlatformAgnosticTests(BasePlatformTests):
out = self.init(testdir, extra_args=['--wipe', f'-D{option}=1'], allow_fail=True) out = self.init(testdir, extra_args=['--wipe', f'-D{option}=1'], allow_fail=True)
self.assertIn(f'ERROR: Unknown options: "{option}"', out) self.assertIn(f'ERROR: Unknown options: "{option}"', out)
@expectedFailure
def test_configure_new_option(self) -> None: def test_configure_new_option(self) -> None:
"""Adding a new option without reconfiguring should work.""" """Adding a new option without reconfiguring should work."""
testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options')) testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options'))
@ -328,7 +327,6 @@ class PlatformAgnosticTests(BasePlatformTests):
self.setconf('-Dnew_option=true') self.setconf('-Dnew_option=true')
self.assertEqual(self.getconf('new_option'), True) self.assertEqual(self.getconf('new_option'), True)
@expectedFailure
def test_configure_removed_option(self) -> None: def test_configure_removed_option(self) -> None:
"""Removing an options without reconfiguring should still give an error.""" """Removing an options without reconfiguring should still give an error."""
testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options')) testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options'))
@ -344,7 +342,6 @@ class PlatformAgnosticTests(BasePlatformTests):
self.setconf('-Dneg_int_opt=0') self.setconf('-Dneg_int_opt=0')
self.assertIn('Unknown options: "neg_int_opt"', e.exception.stdout) self.assertIn('Unknown options: "neg_int_opt"', e.exception.stdout)
@expectedFailure
def test_configure_option_changed_constraints(self) -> None: def test_configure_option_changed_constraints(self) -> None:
"""Changing the constraints of an option without reconfiguring should work.""" """Changing the constraints of an option without reconfiguring should work."""
testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options')) testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options'))
@ -360,7 +357,6 @@ class PlatformAgnosticTests(BasePlatformTests):
self.setconf('-Dneg_int_opt=-10') self.setconf('-Dneg_int_opt=-10')
self.assertEqual(self.getconf('neg_int_opt'), -10) self.assertEqual(self.getconf('neg_int_opt'), -10)
@expectedFailure
def test_configure_meson_options_txt_to_meson_options(self) -> None: def test_configure_meson_options_txt_to_meson_options(self) -> None:
"""Changing from a meson_options.txt to meson.options should still be detected.""" """Changing from a meson_options.txt to meson.options should still be detected."""
testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options')) testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options'))
@ -377,7 +373,6 @@ class PlatformAgnosticTests(BasePlatformTests):
self.setconf('-Dneg_int_opt=-10') self.setconf('-Dneg_int_opt=-10')
self.assertEqual(self.getconf('neg_int_opt'), -10) self.assertEqual(self.getconf('neg_int_opt'), -10)
@expectedFailure
def test_configure_options_file_deleted(self) -> None: def test_configure_options_file_deleted(self) -> None:
"""Deleting all option files should make seting a project option an error.""" """Deleting all option files should make seting a project option an error."""
testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options')) testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options'))
@ -387,7 +382,6 @@ class PlatformAgnosticTests(BasePlatformTests):
self.setconf('-Dneg_int_opt=0') self.setconf('-Dneg_int_opt=0')
self.assertIn('Unknown options: "neg_int_opt"', e.exception.stdout) self.assertIn('Unknown options: "neg_int_opt"', e.exception.stdout)
@expectedFailure
def test_configure_options_file_added(self) -> None: def test_configure_options_file_added(self) -> None:
"""A new project option file should be detected.""" """A new project option file should be detected."""
testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '1 trivial')) testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '1 trivial'))
@ -397,7 +391,6 @@ class PlatformAgnosticTests(BasePlatformTests):
self.setconf('-Dnew_option=bar') self.setconf('-Dnew_option=bar')
self.assertEqual(self.getconf('new_option'), 'bar') self.assertEqual(self.getconf('new_option'), 'bar')
@expectedFailure
def test_configure_options_file_added_old(self) -> None: def test_configure_options_file_added_old(self) -> None:
"""A new project option file should be detected.""" """A new project option file should be detected."""
testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '1 trivial')) testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '1 trivial'))
@ -407,7 +400,6 @@ class PlatformAgnosticTests(BasePlatformTests):
self.setconf('-Dnew_option=bar') self.setconf('-Dnew_option=bar')
self.assertEqual(self.getconf('new_option'), 'bar') self.assertEqual(self.getconf('new_option'), 'bar')
@expectedFailure
def test_configure_new_option_subproject(self) -> None: def test_configure_new_option_subproject(self) -> None:
"""Adding a new option to a subproject without reconfiguring should work.""" """Adding a new option to a subproject without reconfiguring should work."""
testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '43 subproject options')) testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '43 subproject options'))

Loading…
Cancel
Save