-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Utility methods for feature objects #8512
Conversation
3be6f51
to
ad0adc6
Compare
mesonbuild/interpreter.py
Outdated
return 'disabled' if not self.held_object else self.held_object.value | ||
|
||
def as_disabled(self): | ||
return FeatureOptionHolder(None, self.name, None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better pass a UserFeatureOption() instead of None, so you can remove the value property and all those changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Further, you're breaking the type annotations, they don't allow this to be None
. So you either need to fix the type annotations, or use a UserFeatureOption()
. I'd also prefer the latter.
- `require(value, error_message: '')` *(since 0.58.0)*: returns | ||
the object itself if the value is true; an error if the object is | ||
`'enabled'` and the value is false; a disabled feature if the object | ||
is `'auto'` or `'disabled'` and the value is false. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is the same as may_disable_if()
except that it is an error if the user set that feature to enabled when it cannot be supported, right?
This makes me wonder if we even need may_disable_if()
, the way I see feature options is that if user enable one it's should be a guarantee that if for any reason that cannot be satisfied they get an error and not a silently disabled feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may_disable_if()
does not degrade enabled
to disabled
, only auto
. This is why it has a slightly more complicated name than disabled_if()
.
The design should cover exactly what you mention here. The idea is that you use
libone = dependency('one', requires: get_option('one'))
libtwo = dependency('two',
requires: get_option('two').may_disable_if(not libone.found())
This way:
- with
-Dtwo=auto
libtwo won't even be searched unless libone was found - with
-Dtwo=enabled
it will be an error if libtwo is absent, even if the build will end up not using it because libone is absent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm not really convinced by may_disable_if()
tbh, do you have real use-case in qemu for that? It feels like .require()
is easy and intuitive, but may_disable_if()
is for subtle corner case that I'm not even sure to really grasp.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about renaming value
to condition
here for clarity?
Could also change the error_message kwarg to be simply an optional 2nd positional argument, just like assert(condition, 'optional message')
?
Maybe add working like "This describes a condition required to allow enabling this feature" ?
error_message: 'DirectX only available on Windows').allowed() then | ||
src += ['directx.c'] | ||
config.set10('HAVE_DIRECTX', 1) | ||
endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it more readable when not packing everything in the condition:
directx_opt = get_option('directx').require(host_machine.os == 'windows',
error_message: 'DirectX only available on Windows'
)
if directx_opt.allowed()
...
endif
mesonbuild/interpreter.py
Outdated
if not isinstance(args[0], bool): | ||
raise InvalidArguments('boolean argument expected.') | ||
error_message = kwargs.pop('error_message', '') | ||
if error_message and not isinstance(error_message, str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpicking
if error_message and not isinstance(error_message, str): | |
if not isinstance(error_message, str): |
mesonbuild/interpreter.py
Outdated
|
||
@noPosargs | ||
@permittedKwargs({}) | ||
def allowed_method(self, args, kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@FeatureNew
mesonbuild/interpreter.py
Outdated
return self.value == 'auto' | ||
|
||
@permittedKwargs({'error_message'}) | ||
def require_method(self, args, kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@FeatureNew
mesonbuild/interpreter.py
Outdated
return self.as_disabled() | ||
|
||
@noKwargs | ||
def may_disable_if_method(self, args, kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@FeatureNew
and @typed_pos_args
mesonbuild/interpreter.py
Outdated
|
||
if self.value == 'enabled': | ||
prefix = 'Feature {} cannot be enabled'.format(self.name) | ||
prefix = prefix + ': ' if error_message else '' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do a smart default error message, see how assert()
uses AstPrinter to print the condition.
get_option('directx').require(host_machine.os == 'windows')
that would print Feature directx cannot be enabled: host_machine.os == 'windows'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it, but it's the opposite, in this case host_machine.os != 'windows'
. It's also much more user-oriented than assert()
, so an expression might not be very friendly.
mesonbuild/interpreter.py
Outdated
raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), )) | ||
if not isinstance(args[0], bool): | ||
raise InvalidArguments('boolean argument expected.') | ||
error_message = kwargs.pop('error_message', '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't pop
here, use get
, Let's avoid mutation if possible.
I don't really grasp why this is the way it is. You have to have had looked up opt_video_x11 = get_option('video_x11', ..., if_enabled: opt_video) |
I like it, what about:
|
The condition can be put on get_option() indeed, that would make sense. I'm personally still not convinced by the enabled->enabled case, IMHO if a feature can only be used under certain condition and you set that feature enabled, you always should get an error if the condition is not met. That way we could even support boolean options, if condition is false true->error? |
Yeah, the semantics of |
The enabled->enabled case is not for feature that can only be used under certain conditions; it's for dependencies that But this is very different from degrading |
@bonzini OOC do you have an actual use-case for may_disable_if() in qemu? I would prefer to leave that out of scope for now if you did that just because you think it could be useful. Do I understand it correctly that it would be for example if an optional feature is not recommended on certain platforms. For example on Windows you could prefer directx over opengl, unless user explicitly enable opengl?
|
Could be as well |
Yes, I have a bunch of
which would become
That's another usecase that I hadn't thought of. |
On 2nd thought, I think I prefer having those methods on FeatureOptionHolder rather than in get_option() because it is more flexible. Doing it in get_option() restricts us to kwargs that only apply to certain type of options, would make no sense on string options... Doing it on FeatureOptionHolder it's easier to add as many methods and arguments we want in the future. Doing it in get_option() does not really fix @jpakkane concern anyway, because you could still do We have been discussing ways to combine feature options together since feature options have been introduced. This is the best proposal I've seen so far. The biggest difference with previous attemps is here we combine feature with boolean, instead of feature with feature, that makes a lot more sense IMHO. |
I'm in agreement, especially because it could allow you to make complex conditional logic more readable, imagine (something pretty similar exists in mesa): with_glx = get_option('glx', disable_if : host_machine.system() in ['windows', 'darwin'] or not (any_gl or any any_vk or any_video) and dep_x11.found() and dep_xlib.found() and dep_xcb.found() and dep_xcb_x11.found()) to with_glx = get_option('glx').disable_if(host_machine.system() in ['windows', 'darwin'])'
with_glx = with_glx.disable_if(not (any_gl or any_vk or any_video))
with_glx = with_glx.disable_if(not (dep_x11.found() or dep_xcb.found() or dep_x11_xcb.found())) or even: with_glx = get_option('glx') \
.disable_if(host_machine.system() in ['windows', 'darwin'])' \
.disable_if(not (any_gl or any_vk or any_video)) \
.disable_if(not (dep_x11.found() or dep_xcb.found() or dep_x11_xcb.found())) I also have a real world use case today that I found that this would be awesome for. I have a project where all my tests use gtest, so I have this in my meson_options: option('tests', type : 'feature') and my meson.build dep_gtest = dependency('gtest', disabler : true, required : get_option('tests'))
test_exe = executable(
...
dependencies : dep_gtest
) but this doesn't do exactly what I want because in a cross compile I only want to build tests by default if they'll actually run, so I could replace this with: dep_gtest = dependency('gtest', disabler : true, required : get_option('tests').disable_if(not meson.can_run_host_binaries())) So definately +1 from me for the feature. Also, on the color of the bikeshed, I would |
That one pretty much matches mine.
I added the "may" because it doesn't turn enabled into disabled, so disable_if is misleading. But disable_auto_if works for me too. I won't get back to this too soon, but it'll remain on my todo list. |
I like |
opt_foo = get_option('foo')
opt_bar = get_option('bar').enable_auto_if(opt_foo.enabled()) maybe? Although that case having something like: opt_foo = get_option('foo')
opt_bar = get_option('bar').auto_to(opt_foo) might be more useful. Though that doesn't need to happen in this PR, just throwing ideas out. |
I'd love to make use of this for what it's worth. Use case 1: https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/159/diffs Use case 2: flipping auto defaults for GStreamer plugins based on some kind of "group" options (e.g. an option to enable/disable gpl plugins, or video plugins, or all external plugins). |
5f0a818
to
60620f1
Compare
7c382d8
to
8eaa3e0
Compare
This method simplifies the conversion of Feature objects to booleans. Often, one has to use the "not" operator in order to treat "auto" and "enabled" the same way. "allowed()" also works well in conjunction with the require method that is introduced in the next patch. For example, if get_option('foo').require(host_machine.system() == 'windows').allowed() then src += ['foo.c'] config.set10('HAVE_FOO', 1) endif can be used instead of if host_machine.system() != 'windows' if get_option('foo').enabled() error('...') endif endif if not get_option('foo').disabled() then src += ['foo.c'] config.set10('HAVE_FOO', 1) endif Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This will allow adding "forced-off" Feature objects in the next patch. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
OK, looks like I can also reproduce this error on master locally in my Windows 10 VM. Seems like a VS update broke something... |
Looks like this is definitely an MSBuild bug... msbuild -target:mylib_sha '.\build\shared library linking test.sln' # Works
msbuild -target:mycpplib_sha '.\build\shared library linking test.sln' # Fails Part of the Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2019
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mylib@sha", "mylib@sha.vcxproj", "{83B210AF-6664-4E8D-9B48-900E158BA88B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "prog@exe", "prog@exe.vcxproj", "{88597C1C-B700-472D-BD78-DA98CF318CBC}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mycpplib@sha", "mycpplib@sha.vcxproj", "{A6B275FC-5B0C-4B4E-BBA9-B1314796B91B}"
EndProject Here, |
Ping @jpakkane for review. |
docs/markdown/Reference-manual.md
Outdated
for example based on other features or on properties of the host machine: | ||
|
||
``` | ||
if get_option('directx').require(host_machine.os == 'windows', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should probably be host_machine.system()
.
Add a method to perform a logical AND on a feature object. The method also takes care of raising an error if 'enabled' is ANDed with false. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Add a method to downgrade an option to disabled if it is not used. This is useful to avoid unnecessary search for dependencies; for example dep = dependency('dep', required: get_option('feature').disable_auto_if(not foo)) can be used instead of the more verbose and complex if get_option('feature').auto() and not foo then dep = dependency('', required: false) else dep = dependency('dep', required: get_option('feature')) endif or to avoid unnecessary dependency searches: dep1 = dependency('dep1', required: get_option('foo')) # dep2 is only used together with dep1 dep2 = dependency('dep2', required: get_option('foo').disable_auto_if(not dep1.found())) ``` Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Fix the following Python error: D:\a\1\s\run_unittests.py:6654: DeprecationWarning: invalid escape sequence \ self.assertEqual(libhello_nolib.get_pkgconfig_variable(escaped_var, {}), hello world) Use a raw string literal.
Forgotten in mesonbuild#8512.
Forgotten in mesonbuild#8512.
This pull request adds utility methods to operate on feature objects. These methods should cover most needs for manual creation of feature objects (as in #5206). For example:
can be changed to this:
The other method I am adding,
may_disable_if
, is useful to skip possibly expensive checks when it's known that a library will not be used.