Re: Project options and other format enhancements (and dropping "variants")



Oh my, I quite recently underwent a 15+ hour full body procedure of
transportation... which was commencing around when this email was sent,
sorry for missing this email :)

Regarding your PS, I should just mention for your own benefit that you
have forked this thread, this might mean that you did not group reply
this email originally; and also caused it to fall behind in my message
history (actually I found this message because my iPhone did something
weird and associated the two threads together using some kind of extra
sensory heuristics, or I might have missed this message entirely).

Anyway it's really not a big deal, probably you initially subscribed to
the list and didnt have an email to reply to; just letting you know the
effect that can have :)

On to the email !

On Fri, 2017-09-15 at 16:05 +0100, Angelos Evripiotis wrote:
Thanks for the quick response!

On (1) Building particular elements for debugging, when you say this:

Enabling or disabling options for a specific element only is tricky; because
it's also very important to be able to express option conditionals in
project.conf (because you want to make sweeping statements which optionally
apply to the whole project as well).

I think you're saying that 'bst build --option mylib.bst:debug=true' is overly
simplistic, because there may be other things listening for 'debug=true' in
project.conf that would affect things on a global level.

I wouldnt say overly simplistic but...

  o If I have per element options, I can use them to apply only an
    option to a specific element, but I can't make sweeping statements
    about how to build, all the autotools elements in the the entire
    project for example.

  o If I have project wide options, I can use them to apply a project
    wide sweeping statement.

    And, I can *also* use them to make element specific statements.

In fact, I would say that per-element options introduce more
complexity, both in terms of implementation and in terms of using
buildstream.

Taking the debug example; if you have a few hundred elements, and they
all have to declare their own individual 'debug' booleans; you suddenly
have to *declare* a few hundred debug booleans right ?

Seems to me that a simple (contains debug_elements "myelement.bst")
will cover that use case with less hassle.

In other words, setting 'debug=true' for just mylib.bst may make it
incompatible with the rest of it's pipeline.

Does that sound right? If not then the rest of this reply might not be as
relevant.

I hadn't thought of it that way, but depending on what you're doing,
that could also certainly be true depending on the option.

That said, incompatibilities between elements is certainly not handled
the way it is with variants, and either approaches present this
difficulty (whether you're using project wide lists, or element centric
option declarations). 

(?):
condition: (contains debug_elements "mylib.bst")
value:
random-key: The value I would apply if mylib is on the debug list

Just to make sure we're thinking the same thing with this example, here's my
attempt to expand it more:

    project.conf:
    ...
    variables:
    - '??':
        condition: (ifeq 'debug' 'on')
        value:
          debug-flags: --enable-debug
    - '??':
        condition: (and (ifeq 'debug' 'off') (nonempty debug_elements))
        value:
          some-key: Important thing to do because of mixed debug / non-debug.
    ...

    mylib.bst:
    ...
    '??':
      condition: (or (ifeq 'debug' 'on') (contains debug_elements "mylib.bst"))
      value:
        random-key: The value I would apply if mylib is on the debug list
    ...

Then at the command-line, something like:

    bst build --option debug_elements=mylib.bst

Maybe even appending to the list / inserting into the set:

    bst build --option debug_elements+=mylib.bst

If my understanding is correct, I feel I need to make some more examples before
I can agree that the 'inconsistent value of "debug"' thing is unsupportable.

I think the help the .bst and project.conf authors would need from the
BuildStream format would be along the lines of advertising what is supported
and asserting that unsupported things aren't used. This seems possible if
there's some namespacing of options, e.g. introducing 'this.debug' and
'global.debug' in elements (not fully baked yet :).

Right, I am trying to veer away from element centric options for now
unless (or until) we can really make a case that we need that level of
complexity.

As far as configurability goes, project centric options are enough,
beyond that; asserting matters of compatible options is unfortunately
not as automated and implicit as variants was, one would need to write
conditions which produce assertions.

One could compare this with a state machine in a C program, or a
horribly written kernel driver with a dozen boolean checks on each
entry point of it's file handle (seen it before...).

The analogy suggests that one should instead write many smaller,
coherent C programs which work together - this is where we are going
with inter project dependencies (recursive pipelines); and is another
opportunity to have projects actually dictate how projects they depend
on are built.

On (2) Allowing elements to be more specific:

Yes, this is really what I like so much about variants.

The problems I have with moving forward for a full blown variant
solution with competing orthogonal variants, are mostly that:

o It's going to be very, very tricky to implement.

We already have an imperfect algorithm for resolving variants when
there can only be one variant per element. The solution which works
exactly to spec takes several minutes to solve (so already we need
work to get it right with a proper constraint solving algorithm or
engine, which still wont work in linear time but should be usable).

o While it may be easy enough to explain the rules of constraint
resolution to the user, it will often be difficult for the user
to easily predict what variant of what element will be chosen.

Now that I've read the tricky.bst[1] test case for variants, I realise they do
much more than I thought. Automatic resolution is cool, I like the maximum
ambivalence. I see what you mean that it leads to implementation difficulty and
sometimes user confusion.

I think they'd still be very usable without automatic resolution. Maybe I need
to read more of those test cases :)

In the case of tricky.bst, as a user I'd be happy with getting an error message
for that and resolving the conflict myself. I'd be ok with fixing tricky.bst to
depend on the second variant of tricky-first, adding a comment as to why it's
there so it might go away if tricky-first changes. If we added some sort of
semantic tag in the comment about the resolution, then we might be able to
automate some of that outside of BuildStream.

Okay, I think I follow, but I dont believe it's practically workable.

The tricky.bst is the smallest test case I could put together which
causes the engine to choose:

             tricky
             /    \
            /      \
           /        \
   tricky-first   tricky-second(second)

Where:

   tricky-first(first) -> tricky-second(first)


Things will get more complex, once you add a bit of depth to your build
graph:

                   tricky
                  /     \
                 /   
   \
             pizza      buffalo
        (all dressed)   (naked)
     
        /             \
             /               \
            /     
           \
   tricky-first        tricky-second(second)

Where again:

   tricky-first(first) -> tricky-second(first)


Now tricky.bst doesnt want to care about how a naked buffalo can
prepare an all dressed pizza; yet the same condition arises.

While one *could* give tricky explicit knowledge about how a pizza gets
dressed using a 'tricky-first' and suchlike; but I think things get
even more hectic once you consider that tricky.bst is not necessarily a
toplevel target, but only sometimes a target, or sometimes used in the
context of another target.

Let's try another:

            target A    target B
                  \      /     \
                   \    /       \
                    \  /         \
                   tricky         \
                   /   \           \
                  /     \           \
                 /       \        
  \
             pizza      buffalo       \
        (all dressed)  
(naked)        \
              /             \        tricky-
second(second)
             /               \
            /              
  \
   tricky-first          tricky-second

Still:

   tricky-first(first) -> tricky-second(first)


Depending on the context in which tricky.bst was built, we'll get
different variants of tricky-first and tricky-second.

I guess in this case we've just shifted the responsibility up the tree
to target B, still I'm not sure how workable this would be in a larger
project with a few variants in play.

Thinking about what the user has to do in order to say; add a new
element or build a new target, it looks like when one introduces a new
low level dependency with variants; then all of the toplevels which
indirectly depend on it need to be explicit (which kind of leaves the
main advantages of ambivalence dead in the water).


e.g.

    tricky.bst:
    ...
    depends:
      - filename: tricky-first.bst
        variant: second  #!resolves: tricky-second.bst:second
      - filename: tricky-second.bst
        variant: second

However, what we dont have is the element declaring the value of an option on
an element it depends on; depending on how the element itself was configured;
this all leads back down the variant path where an agreement between elements
must be reached.

When you say this about options in project.conf, I think something similar
about options in elements for large projects:

These options should have some metadata which can be used to declare the
defaults, assert valid values of the options, and also a description string
which the CLI can use to communicate the meaning of project options to
buildstream users (not all users building a project wrote the project.conf).

It feels like in addition to the top-level choices of project.conf and the CLI,
elements could encapsulate some config complexity and provide an interface to
things that depend on them. I think that's another thing that draws me (and I'm
guessing you) to variants.

Here is a similar example to the one I brought for my 'point (2)' before, now
a bit fuller:

    app.bst:
    ...
    depends:
      - lib1.bst
      - filename: lib2.bst
        variants: flying-ponies
    ...

    lib1.bst:
    ...
    depends:
      - filename: lib2.bst
        variants: dancing-badgers
    ...

    lib2.bst:
    ...
    variants:
      - name: flying-ponies
      - name: dancing-badgers
        description: Summons a clan of gyrating badgers for a short
                     period. The top speed of a badger is 30km/h, fact.
    variables:
    - conf-extra:
      '??':
        - condition: (ifvariant "flying-ponies")
          value: --enable-flying-ponies
        - condition: (ifvariant "dancing-badgers")
          value: --enable-dancing-badgers
        - condition: >-
            (and
              (ifvariant "dancing-badgers")
              (ifvariant "flying-ponies"))
          value: --enable-flying-ponies --enable-dancing-badgers
    ...

If the ponies / badgers combo is no good then we can do:

    lib2.bst:
    ...
    variables:
    - conf-extra:
      '??':
        - condition: (ifvariant "flying-ponies")
          value: --enable-flying-ponies
        - condition: (ifvariant "dancing-badgers")
          value: --enable-dancing-badgers
        - condition: >-
            (and
              (ifvariant "dancing-badgers")
              (ifvariant "flying-ponies"))
          value: '!!': Sorry, it's badgers or ponies, not both.
    ...

Some criticism of my own example:

- You can see I've mangled the syntax of variants in with conditions, I'm not
  very attached to my choices there, it's more to show that it's not a big step
  away from your proposal. It also shows it doesn't need automatic constraint
  resolution, it's all explicit.

- Even with only two variants (they could have been options), I can see it is
  harder to read. I at least need to factor in architecture, debug, etc. too.

- It's a contrived example, I need to relate it back to actual problems I have
  in the BuildStream adoption POC I'm working on.

I'm torn, of course I wish variants was going to work perfectly. On the other
hand I have to concede that the implicit nature of how things resolved was
already a little bit complex for the user to digest, and increasing that
complexity by making them orthogonal (both to implement and to use) seems to
be unwise.

Would you consider dropping the automatic resolution, or is that maybe one of
the main attractions for you?

The ambivalence is rather the main attraction, but for that you need
resolution.

For reference (you might or might not want to read through it, but it
has some examples of what I intended to use variants for), here is the
email of my original proposal for variants... from almost 2 years ago
now when we were talking about baserock:

  https://listmaster.pepperfish.net/pipermail/baserock-dev-baserock.org/2015-November/013337.html

So "the point" of variants was to reduce duplication and make it easy
to define full systems with least redundancies possible, and without
opening the door on the combinatorial explosion too much (or naturally
limiting the possible combinations).

Consider a project that describes a small OS, now you want to build the
whole thing but swap out one of the low level dependencies for another;
ambivalence let's us proceed with the following workflow:

  o Take tls.bst, which never previously had variants, and now
    add 2 variants to it

  o The first (default) variant will be called 'openssl', and should
    result in no change, in cache key or anything; so by default
    tls.bst has not changed.

  o The second variant of tls.bst is 'nss'

  o Lets say you have a stack element that is bootable-os.bst, lets
    add a new stack which depends on it, called bootable-alt-os.bst

  o Lets now make bootable-alt-os.bst now explicitly ask for the 'nss'
    variant. The explicit dependency from the toplevel to the bottom
    layers is made only for the sake of tailoring that system build
    output; but all of your hundreds of elements in between remain
    unchanged and ambivalent and reusable for other systems you might
    want to deploy based on the same code bases.

These are my motivations for variants.


To be honest; while I think that for now the prudent thing to do is to
introduce options *at the project level only*, I wonder if any kind of
element specific configuration be added in the future, if it would make
sense to just bring back variants as is in that case.


The rationale for this ran like:

  o We need to specify multiple orthogonal settings

  o Variants is only one setting, per element

  o Variants would be way too complex if an element was allowed
    to list more than one named *group* of variants (i.e. allow
    them to be parallel/orthogonal)

  o So we'll need something additional in order to cater to those
    typical use cases; use cases which are not really about *what*
    we're building; but rather *how* we're building it.

  o If we're going to have something else, it makes little sense to
    keep variants around.


It could be that I faltered on that last 5th point, it could very well
be that it makes perfect sense to have variants as a "one-variant-per-
element" thing, which helps you dictate *what* you build...

...where *what* you build includes different configurations of systemd
or a GTK+(wayland) vs GTK+(x11) vs GTK+(both).

And then the project centric options would be there to augment things
for *how* you build what you're building (and this is were we could do
debugging and profiling and lots of stuff; using sweeping project wide
statements or options that are lists; ala debug_elements = "this.bst").


I feel like this would not be so horrible API wise, but we still need
to optimize and perfect the variant resolution algo. But in any case I
still think for right now it's prudent to stick with project centric
options and then explore the additional element variants approach
whenever/ifever we run out of road with what we have.


Cheers,
    -Tristan



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]