Revert "Clarify mutable objects usage"

This reverts commit 9f02d0a3e5.

It turns out that this does introduce a behavioral change in existing
users of ConfigurationData, which it wasn't supposed to (it was supposed
to preserve behavior there, and add a new *warning* for
EnvironmentVariables).

This breaks projects such as pulseaudio, libvirt, and probably more.
Roll back the change and try again after 1.5.0 is released.

Fixes: #13372
pull/13386/head
Eli Schwartz 8 months ago
parent 1ca002a78a
commit f2112d0baa
No known key found for this signature in database
GPG Key ID: CEB167EFB5722BD6
  1. 2
      docs/markdown/Configuration.md
  2. 12
      docs/yaml/objects/cfg_data.yaml
  3. 13
      docs/yaml/objects/env.yaml
  4. 26
      mesonbuild/interpreter/interpreterobjects.py
  5. 4
      mesonbuild/interpreterbase/_unholder.py
  6. 21
      mesonbuild/interpreterbase/baseobjects.py
  7. 7
      test cases/common/113 interpreter copy mutable var on assignment/check_env.py
  8. 34
      test cases/common/113 interpreter copy mutable var on assignment/meson.build
  9. 0
      test cases/failing/70 configuration immutable/input
  10. 12
      test cases/failing/70 configuration immutable/meson.build
  11. 7
      test cases/failing/70 configuration immutable/test.json

@ -39,7 +39,7 @@ use a single `configuration_data` object as many times as you like,
but it becomes immutable after being passed to the `configure_file`
function. That is, after it has been used once to generate output the
`set` function becomes unusable and trying to call it causes an error.
*Since 1.5.0* Copy of immutable `configuration_data` is however mutable.
Copy of immutable `configuration_data` is still immutable.
For more complex configuration file generation Meson provides a second
form. To use it, put a line like this in your configuration file.

@ -1,14 +1,10 @@
name: cfg_data
long_name: Configuration data object
description: |
This object encapsulates configuration values to be used for generating
configuration files. A more in-depth description can be found in the
[the configuration page](Configuration.md).
This object becomes immutable after first use. This means that
calling set() or merge_from() will cause an error if this object has
already been used in any function arguments. However, assignment creates a
mutable copy.
This object encapsulates
configuration values to be used for generating configuration files. A
more in-depth description can be found in the [the configuration wiki
page](Configuration.md).
methods:
- name: set

@ -9,11 +9,6 @@ description: |
on the same `varname`. Earlier Meson versions would warn and only the last
operation took effect.
*Since 1.5.0* This object becomes immutable after first use. This means that
calling append(), prepend() or set() will cause a deprecation warning if this
object has already been used in any function arguments. However, assignment
creates a mutable copy.
example: |
```meson
env = environment()
@ -23,14 +18,6 @@ example: |
env.append('MY_PATH', '2')
env.append('MY_PATH', '3')
env.prepend('MY_PATH', '0')
# Deprecated since 1.5.0
run_command('script.py', env: env)
env.append('MY_PATH', '4')
# Allowed and only env2 is modified
env2 = env
env2.append('MY_PATH', '4')
```
methods:

@ -284,7 +284,6 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu
def __init__(self, obj: mesonlib.EnvironmentVariables, interpreter: 'Interpreter'):
super().__init__(obj, interpreter)
MutableInterpreterObject.__init__(self)
self.methods.update({'set': self.set_method,
'unset': self.unset_method,
'append': self.append_method,
@ -309,14 +308,12 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu
@typed_kwargs('environment.set', ENV_SEPARATOR_KW)
def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None:
name, values = args
self.check_used(self.interpreter, fatal=False)
self.held_object.set(name, values, kwargs['separator'])
@FeatureNew('environment.unset', '1.4.0')
@typed_pos_args('environment.unset', str)
@noKwargs
def unset_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> None:
self.check_used(self.interpreter, fatal=False)
self.held_object.unset(args[0])
@typed_pos_args('environment.append', str, varargs=str, min_varargs=1)
@ -324,7 +321,6 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu
def append_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None:
name, values = args
self.warn_if_has_name(name)
self.check_used(self.interpreter, fatal=False)
self.held_object.append(name, values, kwargs['separator'])
@typed_pos_args('environment.prepend', str, varargs=str, min_varargs=1)
@ -332,7 +328,6 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu
def prepend_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None:
name, values = args
self.warn_if_has_name(name)
self.check_used(self.interpreter, fatal=False)
self.held_object.prepend(name, values, kwargs['separator'])
@ -343,7 +338,6 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte
def __init__(self, obj: build.ConfigurationData, interpreter: 'Interpreter'):
super().__init__(obj, interpreter)
MutableInterpreterObject.__init__(self)
self.methods.update({'set': self.set_method,
'set10': self.set10_method,
'set_quoted': self.set_quoted_method,
@ -355,31 +349,32 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte
})
def __deepcopy__(self, memo: T.Dict) -> 'ConfigurationDataHolder':
obj = ConfigurationDataHolder(copy.deepcopy(self.held_object), self.interpreter)
return ConfigurationDataHolder(copy.deepcopy(self.held_object), self.interpreter)
def is_used(self) -> bool:
return self.held_object.used
def __check_used(self) -> None:
if self.is_used():
# Copies of used ConfigurationData used to be immutable. It is now
# allowed but we need this flag to print a FeatureNew warning if
# that happens.
obj.mutable_feature_new = True
return obj
raise InterpreterException("Can not set values on configuration object that has been used.")
@typed_pos_args('configuration_data.set', str, (str, int, bool))
@typed_kwargs('configuration_data.set', _CONF_DATA_SET_KWS)
def set_method(self, args: T.Tuple[str, T.Union[str, int, bool]], kwargs: 'kwargs.ConfigurationDataSet') -> None:
self.check_used(self.interpreter)
self.__check_used()
self.held_object.values[args[0]] = (args[1], kwargs['description'])
@typed_pos_args('configuration_data.set_quoted', str, str)
@typed_kwargs('configuration_data.set_quoted', _CONF_DATA_SET_KWS)
def set_quoted_method(self, args: T.Tuple[str, str], kwargs: 'kwargs.ConfigurationDataSet') -> None:
self.check_used(self.interpreter)
self.__check_used()
escaped_val = '\\"'.join(args[1].split('"'))
self.held_object.values[args[0]] = (f'"{escaped_val}"', kwargs['description'])
@typed_pos_args('configuration_data.set10', str, (int, bool))
@typed_kwargs('configuration_data.set10', _CONF_DATA_SET_KWS)
def set10_method(self, args: T.Tuple[str, T.Union[int, bool]], kwargs: 'kwargs.ConfigurationDataSet') -> None:
self.check_used(self.interpreter)
self.__check_used()
# bool is a subclass of int, so we need to check for bool explicitly.
# We already have typed_pos_args checking that this is either a bool or
# an int.
@ -442,7 +437,6 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte
@noKwargs
def merge_from_method(self, args: T.Tuple[build.ConfigurationData], kwargs: TYPE_kwargs) -> None:
from_object = args[0]
self.check_used(self.interpreter)
self.held_object.values.update(from_object.values)

@ -5,7 +5,7 @@ from __future__ import annotations
import typing as T
from .baseobjects import InterpreterObject, MesonInterpreterObject, ObjectHolder, HoldableTypes, MutableInterpreterObject
from .baseobjects import InterpreterObject, MesonInterpreterObject, ObjectHolder, HoldableTypes
from .exceptions import InvalidArguments
from ..mesonlib import HoldableObject, MesonBugException
@ -13,8 +13,6 @@ if T.TYPE_CHECKING:
from .baseobjects import TYPE_var
def _unholder(obj: InterpreterObject) -> TYPE_var:
if isinstance(obj, MutableInterpreterObject):
obj.mark_used()
if isinstance(obj, ObjectHolder):
assert isinstance(obj.held_object, HoldableTypes)
return obj.held_object

@ -120,27 +120,6 @@ class MesonInterpreterObject(InterpreterObject):
class MutableInterpreterObject:
''' Dummy class to mark the object type as mutable '''
def __init__(self) -> None:
self.used = False
self.mutable_feature_new = False
def mark_used(self) -> None:
self.used = True
def is_used(self) -> bool:
return self.used
def check_used(self, interpreter: Interpreter, fatal: bool = True) -> None:
from .decorators import FeatureDeprecated, FeatureNew
if self.is_used():
if fatal:
raise InvalidArguments('Can not modify object after it has been used.')
FeatureDeprecated.single_use('Modify object after it has been used', '1.5.0',
interpreter.subproject, location=interpreter.current_node)
elif self.mutable_feature_new:
FeatureNew.single_use('Modify a copy of an immutable object', '1.5.0',
interpreter.subproject, location=interpreter.current_node)
self.mutable_feature_new = False
HoldableTypes = (HoldableObject, int, bool, str, list, dict)
TYPE_HoldableTypes = T.Union[TYPE_elementary, HoldableObject]

@ -1,7 +0,0 @@
#!/usr/bin/env python3
import os
import sys
if sys.argv[1] not in os.environ:
exit(42)

@ -1,4 +1,4 @@
project('foo', meson_version: '>=1.5')
project('foo')
a = configuration_data()
a.set('HELLO', 1)
@ -10,15 +10,6 @@ assert(b.has('HELLO'), 'Original config data should be set on copy')
configure_file(output : 'b.h', configuration : b)
testcase expect_error('Can not modify object after it has been used.')
b.set('WORLD', 1)
endtestcase
# A copy of immutable object is mutable. This should print FeatureNew warning
# if meson_version is lower than 1.5.
c = b
c.set('WORLD', 1)
# This should still work, as we didn't use the original above but a copy!
a.set('WORLD', 1)
@ -26,26 +17,3 @@ assert(a.has('WORLD'), 'New config data should have been set')
assert(not b.has('WORLD'), 'New config data set should not affect var copied earlier')
configure_file(output : 'a.h', configuration : a)
env1 = environment()
env1.set('FOO', '1')
env2 = env1
env1.set('BAR', '1')
# FOO should be in env1 and env2
run_command('check_env.py', 'FOO', env: env1, check: true)
run_command('check_env.py', 'FOO', env: env2, check: true)
# BAR should be only in env1
run_command('check_env.py', 'BAR', env: env1, check: true)
assert(run_command('check_env.py', 'BAR', env: env2, check: false).returncode() == 42)
# This should print deprecation warning but still work
env1.set('PLOP', '1')
run_command('check_env.py', 'PLOP', env: env1, check: true)
# A copy of used env should be mutable and not print warning
env3 = env1
env3.set('BAZ', '1')
run_command('check_env.py', 'PLOP', env: env3, check: true)
run_command('check_env.py', 'BAZ', env: env3, check: true)

@ -0,0 +1,12 @@
project('configuration_data is immutable')
a = configuration_data()
configure_file(
configuration : a,
input : 'input',
output : 'output',
)
still_immutable = a
still_immutable.set('hello', 'world')

@ -0,0 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/70 configuration immutable/meson.build:12:16: ERROR: Can not set values on configuration object that has been used."
}
]
}
Loading…
Cancel
Save