Skip to content
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

Allow conditional possible values in variants #29530

Merged

Conversation

alalazo
Copy link
Member

@alalazo alalazo commented Mar 15, 2022

Allow declaring possible values for variants with an associated condition. If the variant takes one of those values, the condition is imposed as a further constraint.

The idea of this PR is to implement part of the mechanisms needed for modeling packages with multiple build-systems. After this PR the build-system directive can be implemented as:

variant(
    'build-system',
    default='cmake',
    values=(
        'autotools',
        conditional('cmake', when='@X.Y:')
    ), 
    description='...',
)

Modifications:

  • Allow conditional possible values in variants
  • Add a unit-test for the feature
  • Add documentation

Copy link
Contributor

@hainest hainest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this. Huge thumbs up!

I freely admit I didn't put my braintime into it, but is this composable with conditional variants (#24858)?

I'm curious if there would be any utility in extending this to depends_on and conflicts. With those, it would really be more of a convenience function so that a package author could aggregate those calls for a given dependency (we have lots of those in Dyninst). I'm not sure. I can't really make a strong use case for it. Of course, there would need to be a mechanism to pass args that aren't when.

@alalazo
Copy link
Member Author

alalazo commented Mar 16, 2022

I freely admit I didn't put my braintime into it, but is this composable with conditional variants (#24858)?

It is. I added an additional unit test to ensure compatibility with this feature is tested too, see b25ca92

@alalazo
Copy link
Member Author

alalazo commented Mar 16, 2022

I'm curious if there would be any utility in extending this to depends_on and conflicts.

Not sure what you are suggesting here. I don't see how this can be extended beyond variant values, but was wondering what is your use case.

@spackbot-app spackbot-app bot added the documentation Improvements or additions to documentation label Mar 16, 2022
@alalazo alalazo marked this pull request as ready for review March 16, 2022 17:14
@hainest
Copy link
Contributor

hainest commented Mar 16, 2022

I'm curious if there would be any utility in extending this to depends_on and conflicts.

Not sure what you are suggesting here. I don't see how this can be extended beyond variant values, but was wondering what is your use case.

In Dyninst, we have

depends_on('elfutils@0.186:', type='link', when='@12.0.1:')
depends_on('elfutils@0.178:', type='link', when='@10.2.0:')
depends_on('elfutils', type='link', when='@9.3.0:10.1')
depends_on('libelf', type='link', when='@:9.2')

Using the new Value type, we could have

depends_on(
  Value('elfutils@0.186:', when='@12.0.1:', type='link'),
  Value('elfutils@0.178:', when='@10.2.0:', type='link'),
  Value('elfutils', when='@9.3.0:10.1', type='link'),
  Value('libelf', when='@:9.2', type='link')
)

As I noted, this is really more of a convenience than a new behavior. I'm still not sure if it would be useful.

hainest
hainest previously approved these changes Mar 16, 2022
haampie
haampie previously approved these changes Mar 21, 2022
values=(
'98', '11', '14',
# C++17 is not supported by Boost < 1.63.0.
value('17', when='@1.63.0:'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that nobody starts asking for this:

when(
    value('17'),
    value('18'),
    value('19'),
    value('20'),
    when='@1.63.0:',
)

we should probably allow this:

    value('17', '18', '19', '20', when='@1.63.0:')

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to check what is needed for this modification. Can we use another name though instead of value if we have to handle lists?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, value is singular, it's looks odd when it takes multiple values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

values / value_list?

Copy link
Member Author

@alalazo alalazo Mar 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about conditional:

variant(
    'cxxstd', default='98',
    values=(
        '98', '11', '14',
        # C++17 is not supported by Boost < 1.63.0.
        conditional('17', when='@1.63.0:'),
        # C++20/2a is not support by Boost < 1.73.0
        conditional('2a', '2b', when='@1.73.0:')
    )
)

?

class value(object):
"""Conditional value that might be used in variants."""
def __init__(self, v, when):
self.variant_value = v
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above -- v should be *values

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented it in in different way:

  1. The Value class above is a single conditional value and remained the same
  2. The conditional directive is a factory of lists of these values.
  3. The list of conditional values is flattened at Variant creation

Allow declaring possible values for variants which have
a condition associated with them. If the variant takes
one of those values, the associated condition is imposed
as a further constraint.
@alalazo alalazo force-pushed the features/conditional_possible_values branch from 150dd8d to 7513e8a Compare March 24, 2022 14:44
@alalazo alalazo requested a review from tgamblin March 24, 2022 16:53
@alalazo
Copy link
Member Author

alalazo commented Mar 24, 2022

@tgamblin Ready for another review

@tgamblin tgamblin merged commit f2fc4ee into spack:develop Apr 5, 2022
@alalazo alalazo deleted the features/conditional_possible_values branch April 5, 2022 07:03
iarspider pushed a commit to iarspider/spack that referenced this pull request Apr 6, 2022
Allow declaring possible values for variants with an associated condition. If the variant takes one of those values, the condition is imposed as a further constraint.

The idea of this PR is to implement part of the mechanisms needed for modeling [packages with multiple build-systems]( spack/seps#3). After this PR the build-system directive can be implemented as:
```python
variant(
    'build-system',
    default='cmake',
    values=(
        'autotools',
        conditional('cmake', when='@X.Y:')
    ), 
    description='...',
)
```

Modifications:
- [x] Allow conditional possible values in variants
- [x] Add a unit-test for the feature
- [x] Add documentation
RikkiButler20 pushed a commit to RikkiButler20/spack that referenced this pull request Apr 6, 2022
Allow declaring possible values for variants with an associated condition. If the variant takes one of those values, the condition is imposed as a further constraint.

The idea of this PR is to implement part of the mechanisms needed for modeling [packages with multiple build-systems]( spack/seps#3). After this PR the build-system directive can be implemented as:
```python
variant(
    'build-system',
    default='cmake',
    values=(
        'autotools',
        conditional('cmake', when='@X.Y:')
    ), 
    description='...',
)
```

Modifications:
- [x] Allow conditional possible values in variants
- [x] Add a unit-test for the feature
- [x] Add documentation
@adamjstewart
Copy link
Member

Does this allow for conditional defaults? One of the most common questions I see on Slack is "how do I make the default of variant A be True only when variant B is true".

@adamjstewart
Copy link
Member

Also, if I have a MVV, is there a way to set the default to include a value that is conditional on version? Will that be automatically removed for versions where it isn't supported or will that break?

@adamjstewart
Copy link
Member

Also also, what's the best way to get a list of all possible values? I was using self.variants['foo'][0].values in cmake_args but that includes conditional values that shouldn't be in that version (not sure if that's a bug or not since self.variants['foo'][0].values isn't really documented anywhere).

@alalazo
Copy link
Member Author

alalazo commented May 30, 2022

Does this allow for conditional defaults? One of the most common questions I see on Slack is "how do I make the default of variant A be True only when variant B is true"

No, this doesn't allow conditional defaults mainly to avoid complicating the logic for dealing with variant weights (the weight of a variant with conditional defaults will become dependent on other choices the concretizer could make). Can you point to a real use case to get a better understanding of the question? I think we might be able to solve slightly different variations of what you say in a couple of ways.

@alalazo
Copy link
Member Author

alalazo commented May 30, 2022

Also, if I have a MVV, is there a way to set the default to include a value that is conditional on version? Will that be automatically removed for versions where it isn't supported or will that break?

That should work. I didn't check explicitly MVV, but in case it's a bug.

Also also, what's the best way to get a list of all possible values? I was using self.variants['foo'][0].values in cmake_args but that includes conditional values that shouldn't be in that version (not sure if that's a bug or not since self.variants['foo'][0].values isn't really documented anywhere)

Hmm, we don't have a clear API for that - meaning we can work out some.

@skosukhin
Copy link
Member

Can you point to a real use case to get a better understanding of the question? I think we might be able to solve slightly different variations of what you say in a couple of ways.

@alalazo I think I have an example.

I would like to replace

variant(
"omp_as_runtime",
default=True,
when="+clang @12:",
description="Build OpenMP runtime via ENABLE_RUNTIME by just-built Clang",
)

with

    variant(
        "omp",
        values=("project", conditional("runtime", when="+clang @12:")),
        default="runtime",
        description="Build OpenMP either as a project (with the compiler in use) "
        "or as a runtime (with just-build Clang)",
    )

The reason for that is to make it consistent with another change that I would like to make, namely, replace

variant(
"libcxx",
default=True,
when="+clang",
description="Build the LLVM C++ standard library",
)

with

    variant(
        "libcxx",
        values=("none", "project", conditional("runtime", when="+clang @12:")),
        default="runtime",
        description="Build the LLVM C++ standard library "
        "either as a project (with the compiler in use) "
        "or as a runtime (with just-build Clang)",
    )

The problem is that the first modification seems to revert #30782 and might reintroduce #30700 (I'm not sure though). It would be nice to be able to set default="none" when ~clang, default="project" when +clang @:11 and default="runtime" when +clang @12:.

@alalazo
Copy link
Member Author

alalazo commented Aug 26, 2022

@skosukhin We might either list those preferences in packages.yaml under llvm or mark the variant sticky - in which case using an old clang without being explicit on those variant should fail.

@skosukhin
Copy link
Member

Thanks. Another solution I had in mind for this case was to create a single multi-value variant runtimes with the default auto (disjoint set). I would then implement the logic for auto in the package:

    requested_runtimes = self.spec["runtimes"].value
    if "auto" in requested_runtimes:
        runtimes = []
        if self.spec.satisfies("@12:"):
            runtimes.append("omp")
            if "+libcxx" in self.spec:
                runtimes.append("libcxx")
    else:
        runtimes = list(requested_runtimes)

Would that be better?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
build-systems documentation Improvements or additions to documentation new-variant new-version tests General test capability(ies) update-package utilities
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants