On 29/07/2020 23:07, Sander Striker wrote:
Hi,

Took me a bit to get back to this, apologies.

TL;DR in general I find myself supportive, but I'm not convinced of the UI,
which turns out quite verbose.  As a selfish motivation I'm also looking
for ways to identify elements that do or do not match the same kind as the
plugin.  If this is an implicit annotation that could work.

More inline.

On Sun, Jul 26, 2020 at 9:14 AM Tristan Van Berkom <
[email protected]> wrote:

Hi all,

     This is a proposal for extending the `dependency` attributes on
element declarations such that elements can extend the dependency
attributes and participate in parsing them.

This is a proposal aimed at reconciling a few topics which we've been
looking into recently.

   * Plugins accessing private API[0]

     In Chandan's announcement we underlined the importance of being
     able to reduce redundancy in element declarations which stage
     elements into different sysroots.

     Any element which needs to parse an element name in it's
     configuration and later use Element.search() to find that element
     and treat it specially, is likely causing the user to enter
     redundant information by requiring the user to specify the element
     both as a dependency and also in the element configuration.

Indeed.  I'd like to be able to distinguish between types of dependencies,
e.g. toolchain vs libraries, to be able to stage them in different
locations.


   * Dependencies in a sysroot[1]

     This is a proposal from Valentin from two years ago, which
     underlines the usefulness of being able to stage elements in
     multiple locations (which BuildStream does already, except that it
     appears to not be an obvious thing for users).

     This proposal would not solve Valentin's proposal by itself, but it
     would provide a more convenient API for Valentin to go ahead with
     extending the BuildElement with a feature to satisfy his
     requirements.

     The current way to implement this in BuildStream would be to add
     new configuration to BuildElement allowing BuildElement to pick and
     choose which dependencies it decides to stage at which paths.

     After this proposal, it would allow the BuildElement abstract class
     to add syntactic sugar to the dependency objects in the `depends`,
     `build-depends` and `runtime-depends` lists, making the final user
     facing feature a bit more user friendly.

This sounds good.


   * Replace Element.search() completely.

     The Element.search() API has been broken[2] for a very long time,
     as it does not support addressing elements across junction
     boundaries.

     I created this merge request[3] this weekend to address this, since
     we now have a clearer story about project relative element paths,
     however there are some problems with the patch.

     Namely, in order to make Element.search() performant and avoid
     redundantly reloading .bst files from the disk later on, we need to
     cache a lot of data which we usually discard from the loader,
     increasing our memory footprint by around 12%.

     Considering that we've been wanting to provide a solution that is
     more tightly coupled to the dependency lists, I think it is best if
     we just let Element.search() die completely, as any utility it
     provides can be replaced by this proposal.

Ideally there is no need for Element.search(), as it seems to mostly be
used for supporting "depends"-like functionality outside of core depends.

Proposed format changes
=======================
The Dependency dictionary[4] would gain a single new `config`
attribute, which would be a dictionary who's semantics would be defined
by the owning element.

   kind: foo

   build-depends:
   - filename: payload.bst
     config:
       #
       # The plugin loaded as the `foo` kind can
       # now be told anything specific about this
       # dependency `payload.bst`.
       #
       frob-me: True

   # Configuration that is parsed by the plugin
   # loaded as the `foo` kind in this project
   # is specified already on the toplevel `config`
   # dictionary.
   #
   config:
     some-config: "Yes please"

I'm not sure I follow.  Maybe this is more relatable with a less abstract
example?
How about a plugin that takes input elements, and outputs them as compressed archives?

(Perhaps not entirely realistic or necessary, but it works for a semi-concrete example).

  kind: multi_archiverbuild-depends: - filename: foo.bst config: compression: tar.gz outputname: foo.tar.gz - filename: bar.bst config: compression: zip outputname: bar.zip


Proposed plugin API changes
===========================
The element would gain a new method for parsing dependency
configurations with a similar contract as Plugin.configure().

   * This new method would be called after Plugin.configure() and before
     Plugin.preflight()

   * This new method would be given tuples of (Element, MappingNode) for
     each direct dependency, regardless of whether it had specified a
     `config` node (so the MappingNode is allowed to be `None` for some
     entries).

     This allows the plugin to make a judgement about dependencies which
     no configuration was specified for.

     For example: an element like x86Image might require 2 build
     dependencies, one for the tooling to stage at `/` and another for
     the payload to deploy at `%{build-root}`, so it could raise an
     error if there is a third dependency specified which would be
     ignored.

   * This new method would be optional to implement, however it will be
     mandatory if the user ever specifies a `config` dictionary for any
     of the given plugin instance's dependencies.

     This is to say that BuildStream core would take care of simply
     raising a nice LoadError informing the user that `config` is an
     invalid configuration for the said element, if the said element
     does not implement any special parsing of dependency
     configurations.

Why?  I see no reason for BuildStream core to raise concerns when users
make anticipatory changes to an element definition to work with a (future)
version of a plugin.  Or when a user switches an element from one kind to a
similar kind that doesn't support these opaque configurations.  What am I
missing?
Example of Valentin's BuildElement proposal[1]

Example of Valentin's BuildElement proposal[1]

==============================================
I thought it would be good to include a small example of what the
BuildElement sysroot example might look like in YAML, based on the
`readline.bst` example in his original email:

   depends:
   - filename: glibc.bst
     config:
       sysroot: "%{sysroot}"
   - filename: ncurses.bst
     config:
       sysroot: "%{sysroot}"
   - filename: system-image.bst
     type: build
   - filename: sysroot-compiler.bst
     type: build

In this example, the BuildElement class would implement
Element.configure_dependencies() in such a way as to collect a list of
all of the dependencies which are supposed to go in specific sysroots,
and stage those in those sysroots instead of `/`.

I find the example a bit confusing, as what does it mean to have a
different sysroot config on a runtime dependency?  Does that only affect
these dependencies in their capacity as build-depends?  What is the
expected effect here?

Other than that, it's pretty verbose.  I was envisioning something like:
   build-depends:
   - liba.bst
   - compiler.bst [toolchain]
   - platform.bst [platform]

That would be a pretty useful format. It preserves the option of adding dependencies as a single line. (I'm assuming that multiple tags could be added on the same row, to one dependency.)

It's much less powerful than having a dictionary though. A dict gives the plugin designer the flexibility to make config as complex or as simple as it needs to be.

If we implemented both formats, then I image that "compiler.bst [toolchain]" would be a shorthand for:

  - filename: compiler.bst
    config:
      toolchain: true


And have these elements essentially get a list of tags associated, so that
one has a way to filter based on tags present/absent when iterating (or
have a specialized function for it).

Now, maybe something similar can be achieved if we would allow something
like the following, which avoids duplication of the repetitive verbose
lines:
   depends:
   - filenames:
     - glibc.bst
     - ncurses.bst
     config:
       sysroot: "%{sysroot}"
   - filenames:
     - system-image.bst
     - sysroot-compiler.bst
     type: build

I like the idea of being able to group dependencies like this. In a large element declaration with a lot of dependencies, this could save a lot of time and space. There are some concerns though:

First: 'filename' and 'filenames' are such similar keys. There's a lot of room there for typos. I'd suggest making 'filename' work with both formats, single scalars or lists of scalars. If we add 'filenames', then it should just be a synonym for filename.

Second: would grouping matter? For instance, would there be a difference between:

  depends:
  - filenames:
    - foo.bst
    - bar.bst
    config:
      compression: zip

and:

  depends:
  - filename: foo.bst
    config:
      compression: zip
  - filename: bar.bst
    config:
      compression: zip

Those two formats look quite different, semantically. If we allow both formats, plugin-writers might expect to be able to use them to mean different things. For instance the first option fight be the format to produce a single zip file, while the second option might be the format to produce two zip files.

If we want to give configure_dependencies() the ability to distinguish between the two formats, then would that be easy to implement? is it compatible with the data patterns that we currently use? If we don't want to, then will it be intuitive that these two different formats mean the same thing?

I've sadly stalled on progress on a bazel plugin[5] that I started for
research/discovery reasons as outlined in Chandan's summary message[0].
However, even in its current very WIP form, it raises some questions on how
to do certain things.  Like filter all dependencies of the same kind, to
stage them differently.  I imagine that some of this work would end up
being split between stage() and configure_dependencies() following your
proposal?

Implementation plan
===================
To implement this change I would go through 3 phases of implementation.


   * Implement the new format and method

     This would be done in the BuildStream core repository and
     would introduce bare bones plugins in the `tests/` directory
     for the purpose of testing the correct behaviors of the
     feature.

     At the same time, this would result in deprecation of
     Element.search().

   * Migrate existing plugins

     In this phase, we would update the ScriptElement, base
     class, the `script` convenience element, and any plugins
     in `bst-plugins-experimental` which use `Element.search()`
     to use the new `Element.configure_dependencies()` instead.

To support layout, right?


   * Remove Element.search()

     Once plugins have been migrated, we would remove
     Element.search() entirely.

That seems fair.

Assuming we can agree on the UI this seems reasonable :).

Any thoughts ? Please discuss :)
Cheers,

Sander


Cheers,
     -Tristan

[0]:
https://lists.apache.org/thread.html/r057a6fa1ed275fcbc8d92972753bf32ce1e16ab3740580da69be3f3c%40%3Cdev.buildstream.apache.org%3E
[1]:
https://mail.gnome.org/archives/buildstream-list/2018-August/msg00009.html
[2]: https://gitlab.com/BuildStream/buildstream/-/issues/931
[3]: https://gitlab.com/BuildStream/buildstream/-/merge_requests/1957
[4]:
https://docs.buildstream.build/master/format_declaring.html#expressing-dependencies

[5]:
https://github.com/sstriker/bst-plugins-bazel/blob/wip/src/bst_plugins_bazel/elements/bazel.py

Reply via email to