diff mbox series

[RFC] bitbake-setup: add the proof of concept implementation

Message ID 20240502105745.3073153-1-alex.kanavin@gmail.com
State New
Headers show
Series [RFC] bitbake-setup: add the proof of concept implementation | expand

Commit Message

Alexander Kanavin May 2, 2024, 10:57 a.m. UTC
From: Alexander Kanavin <alex@linutronix.de>

For the rationale and design guidelines please see this message:
https://lists.openembedded.org/g/openembedded-architecture/message/1913

Left out for now but will be done later:
- config fragment support
- tests
- documentation
- official configuration repository

1. If you don't know where to start, list available configurations, and pick one:

===
alex@Zen2:/srv/work/alex/bitbake$ bin/bitbake-setup list
Available configurations:
poky-alex	Poky reference distribution, with alex fixes
poky-kirkstone	Poky reference distribution, kirkstone long term support release (supported until April 2026)
===

2. Then build is initialized this way:
===
alex@Zen2:/srv/work/alex/bitbake$ bin/bitbake-setup init poky-alex
Initializing build in /home/alex/builds/poky-alex

Run /home/alex/builds/poky-alex/build/build.sh to build using this configuration.
===

Note: 'init' sub-command can also take a path or a URL with a configuration file directly.
You can see how those files look like here:
https://github.com/kanavin/bitbake-setup-configurations

3. The above message refers to a one-liner shell script that would build the targets
specified in the chosen configuration:
===
alex@Zen2:/srv/work/alex/bitbake$ cat /home/alex/builds/poky-alex/build/build.sh
. /home/alex/builds/poky-alex/build/init-build-env && bitbake core-image-minimal
===

4. To check if the build configuration needs to be updated, run:
===
alex@Zen2:/srv/work/alex/bitbake$ bin/bitbake-setup status ~/builds/poky-alex/
Configuration has not changed.
===

If the configuration has changed, you will see the difference:
===
alex@Zen2:/srv/work/alex/bitbake$ bin/bitbake-setup status ~/builds/poky-alex/
Top level configuration has changed:

Comments

Richard Purdie June 10, 2024, 11:22 a.m. UTC | #1
On Thu, 2024-05-02 at 12:57 +0200, Alexander Kanavin via lists.openembedded.org wrote:
> From: Alexander Kanavin <alex@linutronix.de>
> 
> For the rationale and design guidelines please see this message:
> https://lists.openembedded.org/g/openembedded-architecture/message/1913
> 
> Left out for now but will be done later:
> - config fragment support
> - tests
> - documentation
> - official configuration repository

I finally got around to looking at bitbake-setup, sorry about the
delay. For some reason I've been struggling to want to start thinking
about this!

My initial impressions are really good, I think it is a start down the
right path for what we need. I've noted down my thoughts as they came
to me:

From the init command, it isn't clear what build.sh is for. I think it
should also mention the traditional build env file too, make it clear
where that is.

I struggled to find the --build-dir option to init, I tried to specify
a direction as the next parameter but that has to be named.

For the cloning of repos, I wondered if we should have some config file
in HOME with a pointer to a central downloads/clone cache? The
autobuilder will certainly need something like that. Whether we could
use a common cache with DL_DIR remains to be seen too.

I'm not sure I like the build config being moved out the way when
updating. The tmpdir "ABI" and conf file versioning should protect us
from the worst problems?

Also, once you're "in" a build environment, can we can bitbake-setup
without parameters, i.e "bitbake-setup status" and "bitbake-setup
update"? Effectively that means teaching init-build-env about the
environment it is in so that bitbake-setup can then read the right
config from the env.

For demo purposes it might help if we could add a "poky-ng" config,
which would be poky, but built using the config from the individual
bitbake/core/meta-yocto/docs components. That shouldn't be too hard to
add?

I'll be experimenting a bit more but I wanted to give some feedback now
I've taken a look.

Cheers,

Richard
Alexander Kanavin June 12, 2024, 8:46 a.m. UTC | #2
On Mon, 10 Jun 2024 at 13:22, Richard Purdie
<richard.purdie@linuxfoundation.org> wrote:
> From the init command, it isn't clear what build.sh is for. I think it
> should also mention the traditional build env file too, make it clear
> where that is.

The printed note is: "Run path/to/build.sh to build using this configuration."

Can you suggest a better wording (I have ideas, but want to hear how
you would put it)?

> I struggled to find the --build-dir option to init, I tried to specify
> a direction as the next parameter but that has to be named.

I wanted to keep build-dir as an optional argument, so that users
don't have to specify it and the tool will decide for them.

This can be re-done as an optional positional argument (with
nargs='?'), but then everything else would have to be done via named
options (e.g. picking one configuration out of several - note the json
nests the config in ['bitbake-setup']['default'] to allow for multiple
entries and alternative tools in the future).

> For the cloning of repos, I wondered if we should have some config file
> in HOME with a pointer to a central downloads/clone cache? The
> autobuilder will certainly need something like that. Whether we could
> use a common cache with DL_DIR remains to be seen too.

Having a tool config on disk crossed my mind several times, but I left
it out from the prototype due to uncertainty about how writing to it
should be handled by the tool. There is a spectrum of possibilities:
from a rich 'git config'-like UI to handling it as a strict read-only
item that needs to be manually created and edited. The config could
contain other entries which are currently hardcoded in the tool  and
can be overridden from command line, e.g. the location of the default
configuration repo.

> I'm not sure I like the build config being moved out the way when
> updating. The tmpdir "ABI" and conf file versioning should protect us
> from the worst problems?

Yes. The tool could back up the previous build/conf when that has to
change, but preserve everything else in build/.

> Also, once you're "in" a build environment, can we can bitbake-setup
> without parameters, i.e "bitbake-setup status" and "bitbake-setup
> update"? Effectively that means teaching init-build-env about the
> environment it is in so that bitbake-setup can then read the right
> config from the env.

Yes, I'll look into it.

> For demo purposes it might help if we could add a "poky-ng" config,
> which would be poky, but built using the config from the individual
> bitbake/core/meta-yocto/docs components. That shouldn't be too hard to
> add?

I think so :)

> I'll be experimenting a bit more but I wanted to give some feedback now
> I've taken a look.

Thanks :) I can do a v2 once we settle the above points.

Irrelevant but hopefully amusing implementation note: I've written
this with 'nano' like pretty much everything else over the past 25
years. I'm that first person in https://xkcd.com/378/ .

Don't be shocked, I have reasons:
- too lazy to learn a 'real' editor or install an IDE. Emacs in 1998
made a *really* bad first impression, sorry.
- lack of 'modern' features like syntax highlighting, code folding,
cross-referencing etc. forces me to be concise and get to the point,
and don't write more code than I can fit into a single brain (or that
can be read from a github webpage and fully understood that way).

I can fumble my way through vi when I'm forced to, but otherwise nano
all the way.

Cheers,
Alex
Richard Purdie Aug. 23, 2024, 10 a.m. UTC | #3
Sorry for the delay in replying to this. There are multiple reasons, I
partly wanted to try and give time for others to experiment with this.
I'm all too aware that when something merges, there will be a ton of
"complaints" about choices that were made. I'd hoped by giving people
time, they might be able to experiment with it but that clearly (and
sadly) isn't happening. We'll therefore have to make do with our best
guesses and instinct.

My first comment coming back to this is that I don't think we should
default to $HOME/builds as that is hard to discover (I got there via an
error message). I think we should error saying the user hadn't
configured a default and tell them how to, or to use --build-dir. That
implies the tool will need some user config file, but I suspect we're
going to need that anyway.

I'd also note that:

"bitbake-setup init poky-alex --build-dir /xxx/poky-alex"

crashes (i.e. when the dir == config name).

On Wed, 2024-06-12 at 10:46 +0200, Alexander Kanavin wrote:
> On Mon, 10 Jun 2024 at 13:22, Richard Purdie
> <richard.purdie@linuxfoundation.org> wrote:
> > From the init command, it isn't clear what build.sh is for. I think it
> > should also mention the traditional build env file too, make it clear
> > where that is.
> 
> The printed note is: "Run path/to/build.sh to build using this configuration."
> 
> Can you suggest a better wording (I have ideas, but want to hear how
> you would put it)?

I think the init process needs to tell the user what it is doing. Part
of our aim here is to shortcut things for the user but to also give
them the option of learning how/what to do for themselves.

At the moment you get a load of git clone output but not a real idea of
*what* it does. I'm thinking improving the output to something like:


**** Cloning OE-Core and layers ****

Cloning XXX to /dest/path

[git clone output]

Cloning XXX2 to /dest/path2

[git clone output]

**** Configuring build defaults ****

Adding layer XXXX

Setting up configuration in /dest/path/local.conf

**** Summary ****

Metadata setup in directory: XXX
Build directory configured as: YYY

Shortcut environment: ". /xxx/init-build-env"
Expanded environment setup: ". /xxx/yyy/oe-init-build-env <builddir>"

Run path/to/build.sh to execute the default build targets for this configuration
or source the environment to run builds from the commandline.


We can always have a quiet option for people who don't want some of
this but I think it should default to telling people what it is doing
and in particular, *where* on disk it is putting things which I think
is my real aim here - let people navigate.

> > I struggled to find the --build-dir option to init, I tried to specify
> > a direction as the next parameter but that has to be named.
> 
> I wanted to keep build-dir as an optional argument, so that users
> don't have to specify it and the tool will decide for them.
> 
> This can be re-done as an optional positional argument (with
> nargs='?'), but then everything else would have to be done via named
> options (e.g. picking one configuration out of several - note the json
> nests the config in ['bitbake-setup']['default'] to allow for multiple
> entries and alternative tools in the future).

I suspect leaving the option is probably the best option but we need to
resolve the "default" issue as mentioned above which is perhaps my
bigger concern.

> > For the cloning of repos, I wondered if we should have some config file
> > in HOME with a pointer to a central downloads/clone cache? The
> > autobuilder will certainly need something like that. Whether we could
> > use a common cache with DL_DIR remains to be seen too.
> 
> Having a tool config on disk crossed my mind several times, but I left
> it out from the prototype due to uncertainty about how writing to it
> should be handled by the tool. There is a spectrum of possibilities:
> from a rich 'git config'-like UI to handling it as a strict read-only
> item that needs to be manually created and edited. The config could
> contain other entries which are currently hardcoded in the tool  and
> can be overridden from command line, e.g. the location of the default
> configuration repo.

I think we're going to need some kind of config even to resolve the
issue above so this is going to be a must have.

> > I'm not sure I like the build config being moved out the way when
> > updating. The tmpdir "ABI" and conf file versioning should protect us
> > from the worst problems?
> 
> Yes. The tool could back up the previous build/conf when that has to
> change, but preserve everything else in build/.

I like the idea of backups and leaving it around. In theory we should
have compatibility.

> > Also, once you're "in" a build environment, can we can bitbake-setup
> > without parameters, i.e "bitbake-setup status" and "bitbake-setup
> > update"? Effectively that means teaching init-build-env about the
> > environment it is in so that bitbake-setup can then read the right
> > config from the env.
> 
> Yes, I'll look into it.
> 
> > For demo purposes it might help if we could add a "poky-ng" config,
> > which would be poky, but built using the config from the individual
> > bitbake/core/meta-yocto/docs components. That shouldn't be too hard to
> > add?
> 
> I think so :)
> 
> > I'll be experimenting a bit more but I wanted to give some feedback now
> > I've taken a look.
> 
> Thanks :) I can do a v2 once we settle the above points.

Hopefully my above answers make sense.

> Irrelevant but hopefully amusing implementation note: I've written
> this with 'nano' like pretty much everything else over the past 25
> years. I'm that first person in https://xkcd.com/378/ .
> 
> Don't be shocked, I have reasons:
> - too lazy to learn a 'real' editor or install an IDE. Emacs in 1998
> made a *really* bad first impression, sorry.
> - lack of 'modern' features like syntax highlighting, code folding,
> cross-referencing etc. forces me to be concise and get to the point,
> and don't write more code than I can fit into a single brain (or that
> can be read from a github webpage and fully understood that way).
> 
> I can fumble my way through vi when I'm forced to, but otherwise nano
> all the way.

I'm not shocked at all, I use mcedit a lot myself :)

Cheers,

Richard
Ryan Eatmon Sept. 5, 2024, 9:10 p.m. UTC | #4
On 5/2/2024 5:57 AM, Alexander Kanavin wrote:
> From: Alexander Kanavin <alex@linutronix.de>
> 
> For the rationale and design guidelines please see this message:
> https://lists.openembedded.org/g/openembedded-architecture/message/1913
> 
> Left out for now but will be done later:
> - config fragment support
> - tests
> - documentation
> - official configuration repository
> 


Hello.  I'm attempting to play around with this patch and test out 
bitbake-setup and how it works.  My testing it with trying to take one 
our existing setups and try to get it to work.  Here is some initial 
feedback written as I ran into issues.

I'm running in an ubuntu-22.04 docker container based on crops.


1) In order to use list it assumes you are fetching a git repo and that 
the git repo has a specific layout.  That seems a little problematic. 
What if the repo we want to point has all kinds of configs for different 
tools in it and we just want a sub directory under the git repo?  How do 
we specify that?


2) The uri for a sources entry in the json file automatically adds in 
protocol=git even though I specified protocol=https on the line.  We 
prefer to always use the https for git repos and not the git protocol. 
I modified the code to not do this so my testing could continue.


3) If we specify a unique key for each of the sources, why do we need to 
then also specify a path to place the downloaded sources into?  Why not 
just use the same key value to reduce what all is needed to specify.  We 
can still support "path" for those that want it, but if it is left out I 
would default it to the unique key for the repo.


4) The init step cannot be run multiple times.  Initial errors are 
because the build-dir already exists, but overall it's a good idea to 
allow for rerunning the setup to allow for fixing issues without needed 
to nuke the entire build dir.  An example of why you might this is if 
you want to add a new layer to your config, or change a commit, or 
whatever...  Rerunning the init should be able to apply the incremental 
changes since the last run.

In this case I was running into an error message from a file in oe-core, 
which I sent a patch for, and was trying to simply fix that file in the 
layers dir and rerun, but kept tripping over the fact that I cannot 
rerun init.


5) What is the purpose of creating the top level config git repo?


6) The code loops through the layers looking for scripts/oe-setup-build. 
  What would happen if I shipped a rogue layer with that file in it and 
included it as the last layer in the sources?


7) I'm getting an error when running the setup-build script:

   File "/scratch/builds/bitbake-setup-test/layers/setup-build", line 
107, in setup_build_env
     subprocess.run(cmd, shell=True, executable=os.environ['SHELL'])
   File "/usr/lib/python3.10/os.py", line 679, in __getitem__
     raise KeyError(key) from None
KeyError: 'SHELL'


SHELL is set to /bin/sh so I'm at a loss to see why this would be 
failing.  But because of this I never get a build.sh script.


I'm out of time today to keep testing this, but I'll get back to it next 
week and I'll likely have some more feedback/questions.
Alexander Kanavin Sept. 9, 2024, 9:33 a.m. UTC | #5
On Thu, 5 Sept 2024 at 23:10, Ryan Eatmon <reatmon@ti.com> wrote:
> Hello.  I'm attempting to play around with this patch and test out
> bitbake-setup and how it works.  My testing it with trying to take one
> our existing setups and try to get it to work.  Here is some initial
> feedback written as I ran into issues.
>
> I'm running in an ubuntu-22.04 docker container based on crops.

Thanks for the feedback and the fix. The plan now is to focus on
getting bitbake fragments setup to the point where it can be merged,
then go back to bitbake setup, but I wanted to address some points
right now.

> 1) In order to use list it assumes you are fetching a git repo and that
> the git repo has a specific layout.  That seems a little problematic.
> What if the repo we want to point has all kinds of configs for different
> tools in it and we just want a sub directory under the git repo?  How do
> we specify that?

The config repo is minimal by design, and isn't meant to store
anything except a reasonable amount of top level configurations files
in a directory structure (e.g. you can list them and not be
overwhelmed by the amount). The idea is that users don't even need to
know where it is, and what it contains, and it never needs to be
cloned separately either (which it would be once you start putting
'other stuff' in it).

> 2) The uri for a sources entry in the json file automatically adds in
> protocol=git even though I specified protocol=https on the line.  We
> prefer to always use the https for git repos and not the git protocol.
> I modified the code to not do this so my testing could continue.

This is not the case. If the entry in the json starts with https://
then the tool will use protocol=https. Basically it needs to convert
URI that can be given to git on the command line into an SRC_URI type
string that can be given to bitbake's git fetcher.

> 3) If we specify a unique key for each of the sources, why do we need to
> then also specify a path to place the downloaded sources into?  Why not
> just use the same key value to reduce what all is needed to specify.  We
> can still support "path" for those that want it, but if it is left out I
> would default it to the unique key for the repo.

Here I had simply followed the layer configuration schema that makes
path mandatory. Requiring a parameter has its advantages, you don't
have to ensure that the default is documented, standardized and all
tools follow the standard. You also don't lock yourself into the
default value that must be followed until the end of time.

> 4) The init step cannot be run multiple times.  Initial errors are
> because the build-dir already exists, but overall it's a good idea to
> allow for rerunning the setup to allow for fixing issues without needed
> to nuke the entire build dir.  An example of why you might this is if
> you want to add a new layer to your config, or change a commit, or
> whatever...  Rerunning the init should be able to apply the incremental
> changes since the last run.
> In this case I was running into an error message from a file in oe-core,
> which I sent a patch for, and was trying to simply fix that file in the
> layers dir and rerun, but kept tripping over the fact that I cannot
> rerun init.

There are two different scenarios described here:

1. 'init' failing halfway through (because of a bug or a config typo)
and leaving the directory in an incomplete, inconsistent state. I
don't think the tool can reasonably resume or increment anything here,
the best it can do is say that the directory already exists, and can
be forcibly re-initialized with --force (which would erase the content
and try again). Another option is to try erasing everything on exit in
an all-catch exception handler.

2. 'init' completing successfully, and the build-dir is then fine, but
it needs to be brought in sync with changes to the config or layers it
specifies. This is what the 'status' and 'update' operations are for.

In both cases the hints and error messages can certainly be improved,
but I'm not sure there's anything that needs to change in the command
flow itself.

> 5) What is the purpose of creating the top level config git repo?

1. To preserve the history of changes to the top level configuration.
So that if you run into trouble you can see what changed. And maybe
roll back to the previous version etc.

2. To make it possible to publish the configuration for others to use.
You can do it with a 'git push' or we could also add a separate
'publish' sub-command.

> 6) The code loops through the layers looking for scripts/oe-setup-build.
>   What would happen if I shipped a rogue layer with that file in it and
> included it as the last layer in the sources?

I guess it would run that instead? If you include a rogue layer into
the config, there's lots of other rogue things it can do when bitbake
starts, but maybe you have a suggestion?

> 7) I'm getting an error when running the setup-build script:
>
>    File "/scratch/builds/bitbake-setup-test/layers/setup-build", line
> 107, in setup_build_env
>      subprocess.run(cmd, shell=True, executable=os.environ['SHELL'])
>    File "/usr/lib/python3.10/os.py", line 679, in __getitem__
>      raise KeyError(key) from None
> KeyError: 'SHELL'
>
>
> SHELL is set to /bin/sh so I'm at a loss to see why this would be
> failing.  But because of this I never get a build.sh script.

For this I would really appreciate if you can get to the bottom of.

Thanks,
Alex
Ryan Eatmon Sept. 10, 2024, 3:02 p.m. UTC | #6
On 9/9/2024 4:33 AM, Alexander Kanavin wrote:
> On Thu, 5 Sept 2024 at 23:10, Ryan Eatmon <reatmon@ti.com> wrote:

>> 2) The uri for a sources entry in the json file automatically adds in
>> protocol=git even though I specified protocol=https on the line.  We
>> prefer to always use the https for git repos and not the git protocol.
>> I modified the code to not do this so my testing could continue.
> 
> This is not the case. If the entry in the json starts with https://
> then the tool will use protocol=https. Basically it needs to convert
> URI that can be given to git on the command line into an SRC_URI type
> string that can be given to bitbake's git fetcher.

You are correct.  I assumed that the string was the same format as the 
SRC_URI.  This might be a little confusing for users as that means we 
have two different interpretations for repo strings.


>> 3) If we specify a unique key for each of the sources, why do we need to
>> then also specify a path to place the downloaded sources into?  Why not
>> just use the same key value to reduce what all is needed to specify.  We
>> can still support "path" for those that want it, but if it is left out I
>> would default it to the unique key for the repo.
> 
> Here I had simply followed the layer configuration schema that makes
> path mandatory. Requiring a parameter has its advantages, you don't
> have to ensure that the default is documented, standardized and all
> tools follow the standard. You also don't lock yourself into the
> default value that must be followed until the end of time.

Then what good is the key value for?  The current patch does not use 
r_name except to look up the value under layers.  Should we just make 
the JSON have this as an array of repos instead of a named hash?

Or is there some planned thing coming later that might make use of this key?



>> 6) The code loops through the layers looking for scripts/oe-setup-build.
>>    What would happen if I shipped a rogue layer with that file in it and
>> included it as the last layer in the sources?
> 
> I guess it would run that instead? If you include a rogue layer into
> the config, there's lots of other rogue things it can do when bitbake
> starts, but maybe you have a suggestion?

Ideally this would be locked to oe-core as that is what is intended. 
But I'm not sure how best to do that.


Side topic, would it make sense to allow for the existence of a config 
file in the repos that would allow them to "register" features that can 
processed by bitbake-setup to configure various things that the layer 
requires/offers?  Right now we do this kind of thing for the 
oe-setup-build script.  But I could see the framework being useful for 
this kind of thing.


>> 7) I'm getting an error when running the setup-build script:
>>
>>     File "/scratch/builds/bitbake-setup-test/layers/setup-build", line
>> 107, in setup_build_env
>>       subprocess.run(cmd, shell=True, executable=os.environ['SHELL'])
>>     File "/usr/lib/python3.10/os.py", line 679, in __getitem__
>>       raise KeyError(key) from None
>> KeyError: 'SHELL'
>>
>>
>> SHELL is set to /bin/sh so I'm at a loss to see why this would be
>> failing.  But because of this I never get a build.sh script.
> 
> For this I would really appreciate if you can get to the bottom of.

I'll take a look today and try to track it down.


> Thanks,
> Alex
Alexander Kanavin Sept. 10, 2024, 3:46 p.m. UTC | #7
On Tue, 10 Sept 2024 at 17:02, Ryan Eatmon <reatmon@ti.com> wrote:
> You are correct.  I assumed that the string was the same format as the
> SRC_URI.  This might be a little confusing for users as that means we
> have two different interpretations for repo strings.

> Then what good is the key value for?  The current patch does not use
> r_name except to look up the value under layers.  Should we just make
> the JSON have this as an array of repos instead of a named hash?
>
> Or is there some planned thing coming later that might make use of this key?

Please keep in mind that the format for the set of layers in the
'sources' section follows precisely the json format used by layer
setup tooling, which was discussed to death, and agreed with great
difficulty quite some time ago, and then implemented in
'bitbake-layers create-layer-setup' that writes it out and
oe-setup-layers that consumes it and pulls the layers from the
network.

Then there's an additional ['configuration']['bitbake-setup'] section,
which is new, and specifies what to do after checking out the layers.

This answers both of these points: 'sources' should list git repos in
a way that can be given directly to git (so that we don't lock people
into bitbake's git fetcher if they don't want to use that). And the
key is used by oe-setup-layers as a 'name' of an entry to tell the
user what is going on (bitbake-setup could do the same to hide the
noisy git output, especially if we bolt on a nice interactive ncurses
UI to it).


> Ideally this would be locked to oe-core as that is what is intended.
> But I'm not sure how best to do that.

Indeed. Anyone can mimic oe-core in their rogue layer and be selected
instead of the real oe-core.

> Side topic, would it make sense to allow for the existence of a config
> file in the repos that would allow them to "register" features that can
> processed by bitbake-setup to configure various things that the layer
> requires/offers?  Right now we do this kind of thing for the
> oe-setup-build script.  But I could see the framework being useful for
> this kind of thing.

I suppose you are talking about config fragments, which are discussed
in a separate thread, and have their own prototype. Bitbake-setup will
certainly gain support for those, but they need to be agreed on and
implemented first.

As for allowing config entries that would direct bitbake-setup to run
freeform shell scripts, I lean towards not supporting that. You can
put them into 'targets' if you absolutely have to. We're trying to
move away from custom and usually undocumented scripts with this
effort.

Alex
Ryan Eatmon Sept. 10, 2024, 7:57 p.m. UTC | #8
On 9/10/2024 10:02 AM, Ryan Eatmon via lists.openembedded.org wrote:
> 
> 
> On 9/9/2024 4:33 AM, Alexander Kanavin wrote:
>> On Thu, 5 Sept 2024 at 23:10, Ryan Eatmon <reatmon@ti.com> wrote:
> 

>>> 7) I'm getting an error when running the setup-build script:
>>>
>>>     File "/scratch/builds/bitbake-setup-test/layers/setup-build", line
>>> 107, in setup_build_env
>>>       subprocess.run(cmd, shell=True, executable=os.environ['SHELL'])
>>>     File "/usr/lib/python3.10/os.py", line 679, in __getitem__
>>>       raise KeyError(key) from None
>>> KeyError: 'SHELL'
>>>
>>>
>>> SHELL is set to /bin/sh so I'm at a loss to see why this would be
>>> failing.  But because of this I never get a build.sh script.
>>
>> For this I would really appreciate if you can get to the bottom of.
> 
> I'll take a look today and try to track it down.


Ok...  I run this:

yoctouser@uda0214219:/workdir$ echo $SHELL
/bin/sh

It *looks* like SHELL is set...  but:

yoctouser@uda0214219:/workdir$ env | grep SHELL
yoctouser@uda0214219:/workdir$

which means it is, in fact, not set.  Thus does not get passed through 
to the bitbake-setup code to be read and processed.

So the issue is, why does my docker container not have it set?  No idea.


I can see in scripts/combo-layer that the command:

     shell = os.environ.get('SHELL', 'bash')

passes a default if SHELL is not set.  I'll send a patch for this in the 
scripts/oe-setup-build as well.  But there is a line in this patch for 
bitbake-setup that also needs to be tweaked if you send out another version.

Might be worth changing it from:

     shell = os.environ["SHELL"]
     with open(build_script,'w') as f:
         f.write("#!{}\n. {} && {}\n".format(shell, init_script, targets))


To:

     shell = os.environ.get("SHELL","bash")
     with open(build_script,'w') as f:
         f.write("#!/usr/bin/env {}\n. {} && {}\n".format(shell, 
init_script, targets))

Note the change to using /usr/bin/env in case the shell variable just 
gets set to "bash".

> 
>> Thanks,
>> Alex
> 
> 
> 
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#16550): https://lists.openembedded.org/g/bitbake-devel/message/16550
> Mute This Topic: https://lists.openembedded.org/mt/105860308/6551054
> Group Owner: bitbake-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/bitbake-devel/unsub [reatmon@ti.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
Alexander Kanavin Sept. 11, 2024, 10:25 a.m. UTC | #9
On Tue, 10 Sept 2024 at 17:46, Alexander Kanavin via
lists.openembedded.org <alex.kanavin=gmail.com@lists.openembedded.org>
wrote:
> Please keep in mind that the format for the set of layers in the
> 'sources' section follows precisely the json format used by layer
> setup tooling, which was discussed to death, and agreed with great
> difficulty quite some time ago, and then implemented in
> 'bitbake-layers create-layer-setup' that writes it out and
> oe-setup-layers that consumes it and pulls the layers from the
> network.
>
> Then there's an additional ['configuration']['bitbake-setup'] section,
> which is new, and specifies what to do after checking out the layers.

I forgot to add, there will be tooling to generate these json files,
possibly a simple extension to the existing tool that generates layer
config. Everything in the json can be obtained programmatically from
an active yocto build (except bitbake build targets maybe, but even
that could be added to config templates), so beginners don't have to
wrestle with json syntax or particularities of specific entries in it.

Alex
diff mbox series

Patch

--- /home/alex/builds/poky-alex/config/poky-alex.conf.json	2024-04-18 13:42:54.312460823 +0200
+++ /home/alex/builds/poky-alex/config-tmp-b413az6s/poky-alex.conf.json	2024-04-18 13:50:42.635433203 +0200
@@ -7,7 +7,7 @@ 
                         "uri": "git://git.yoctoproject.org/poky-contrib"
                     }
                 },
-                "rev": "akanavin/sstate-for-all"
+                "rev": "akanavin/sstate-for-all-and-everyone"
             },
             "path": "poky"
         }
===

If the configuration has not changed, but layer revisions referred to it have (for example
if the configuration specifies a tip of a branch), you will see that too:
===
alex@Zen2:/srv/work/alex/bitbake$ bin/bitbake-setup status ~/builds/poky-alex/
Layer repository git://git.yoctoproject.org/poky-contrib checked out into /home/alex/builds/poky-alex/layers/poky updated revision akanavin/sstate-for-all from 6b842ba55f996b27c900e3de78ceac8cb3b1c492 to aeb73e29379fe6007a8adc8d94c1ac18a93e68de
===

5. If the configuration has changed, you can bring it in sync with:
===
alex@Zen2:/srv/work/alex/bitbake$ bin/bitbake-setup update ~/builds/poky-alex/
Layer repository git://git.yoctoproject.org/poky-contrib checked out into /home/alex/builds/poky-alex/layers/poky updated revision akanavin/sstate-for-all from 6b842ba55f996b27c900e3de78ceac8cb3b1c492 to aeb73e29379fe6007a8adc8d94c1ac18a93e68de
... (skip git output)
Existing build directory renamed to /home/alex/builds/poky-alex/build-backup.20240418135458

Run /home/alex/builds/poky-alex/build/build.sh to build using this configuration.
===

Note that it will also rename the existing build directory, and print changes
in bitbake configuration (diff of content of build/conf/) if that has changed. I can't
at the moment think of anything more clever that is also not much more brittle or complex
to implement, but open to suggestions.

6. To make it easier to review the code, please also review the data it's operating on:
===
alex@Zen2:/srv/work/alex/bitbake$ ls ~/.bitbake-setup/
cache  configurations  downloads
alex@Zen2:/srv/work/alex/bitbake$ ls ~/builds/poky-alex/
build  build-backup.20240418135458  config  config-upstream.json  layers
===

Signed-off-by: Alexander Kanavin <alex@linutronix.de>
---
 bin/bitbake-setup | 310 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 310 insertions(+)
 create mode 100755 bin/bitbake-setup

diff --git a/bin/bitbake-setup b/bin/bitbake-setup
new file mode 100755
index 00000000..5baa0730
--- /dev/null
+++ b/bin/bitbake-setup
@@ -0,0 +1,310 @@ 
+#!/usr/bin/env python3
+
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+import logging
+import os
+import sys
+import argparse
+import warnings
+import json
+import shutil
+import time
+import stat
+import tempfile
+
+bindir = os.path.abspath(os.path.dirname(__file__))
+topdir = os.path.dirname(bindir)
+sys.path[0:0] = [os.path.join(topdir, 'lib')]
+
+import bb.msg
+import bb.process
+
+logger = bb.msg.logger_create('bitbake-setup', sys.stdout)
+
+def init_bb_cache(args):
+    dldir = os.path.join(args.cachedir, 'downloads')
+    persistdir = os.path.join(args.cachedir, 'cache')
+
+    d = bb.data.init()
+    d.setVar("DL_DIR", dldir)
+    d.setVar("PERSISTENT_DIR", persistdir)
+    d.setVar("__BBSRCREV_SEEN", "1")
+    if args.no_network:
+        d.setVar("BB_SRCREV_POLICY", "cache")
+    bb.fetch.fetcher_init(d)
+    return d
+
+def get_config_name(config):
+    return os.path.basename(config).split('.')[0]
+
+def copy_and_commit_config(config_path, dest_config_dir):
+    shutil.copy(config_path, dest_config_dir)
+
+    bb.process.run("git -C {} add .".format(dest_config_dir))
+    bb.process.run("git -C {} commit -a -m 'Configuration at {}'".format(dest_config_dir, time.asctime()))
+
+def _write_layer_list(dest, repodirs):
+    layers = []
+    for r in repodirs:
+        for root, dirs, files in os.walk(os.path.join(dest,r)):
+            if os.path.basename(root) == 'conf' and 'layer.conf' in files:
+                layers.append(os.path.relpath(os.path.dirname(root), dest))
+    layers_f = os.path.join(dest, ".oe-layers.json")
+    with open(layers_f, 'w') as f:
+        json.dump({"version":"1.0","layers":layers}, f, sort_keys=True, indent=4)
+
+def checkout_layers(layers, layerdir, d):
+    repodirs = []
+    oesetupbuild = None
+    for r_name in layers:
+        r_data = layers[r_name]
+        repodir = r_data["path"]
+        repodirs.append(repodir)
+
+        r_remote = r_data['git-remote']
+        rev = r_remote['rev']
+        remotes = r_remote['remotes']
+
+        for remote in remotes:
+            type,host,path,user,pswd,params = bb.fetch.decodeurl(remotes[remote]["uri"])
+            fetchuri = bb.fetch.encodeurl(('git',host,path,user,pswd,params))
+            fetcher = bb.fetch.Fetch(["{};protocol={};rev={};nobranch=1;destsuffix={}".format(fetchuri,type,rev,repodir)], d)
+            fetcher.download()
+            fetcher.unpack(layerdir)
+
+        if os.path.exists(os.path.join(layerdir, repodir, 'scripts/oe-setup-build')):
+            oesetupbuild = os.path.join(layerdir, repodir, 'scripts/oe-setup-build')
+
+    _write_layer_list(layerdir, repodirs)
+
+    if oesetupbuild:
+        oesetupbuild_symlink = os.path.join(layerdir, 'setup-build')
+        if os.path.exists(oesetupbuild_symlink):
+            os.remove(oesetupbuild_symlink)
+        os.symlink(os.path.relpath(oesetupbuild,layerdir),oesetupbuild_symlink)
+
+def setup_build(config, layerdir, builddir):
+    oesetupbuild = os.path.join(layerdir, 'setup-build')
+    if not os.path.exists(oesetupbuild):
+        print("Cannot complete setting up a build directory as oe-setup-build was not found; please use oe-init-build-env manually.")
+        return
+    template = config["template"]
+    backup_builddir = None
+    if os.path.exists(builddir):
+        backup_builddir = builddir + "-backup.{}".format(time.strftime("%Y%m%d%H%M%S"))
+        os.rename(builddir, backup_builddir)
+        print("Existing build directory renamed to {}".format(backup_builddir))
+    bb.process.run("{} setup -c {} -b {} --no-shell".format(oesetupbuild, template, builddir))
+    if backup_builddir:
+        config_diff_stdout, config_diff_stderr = bb.process.run("diff -uNr {} {}".format(os.path.join(backup_builddir, 'conf'), os.path.join(builddir, 'conf')))
+        if config_diff_stdout:
+            print("The bitbake configuration has changed:")
+            print(config_diff_stdout)
+    build_script = os.path.join(builddir, "build.sh")
+    init_script = os.path.join(builddir, "init-build-env")
+    targets = " && ".join(config["targets"])
+    shell = os.environ["SHELL"]
+    with open(build_script,'w') as f:
+        f.write("#!{}\n. {} && {}\n".format(shell, init_script, targets))
+    st = os.stat(build_script)
+    os.chmod(build_script, st.st_mode | stat.S_IEXEC)
+    print("\nRun {} to build using this configuration.".format(build_script))
+
+def get_registry_config(registry_path, id, dest_dir):
+    for root, dirs, files in os.walk(registry_path):
+        for f in files:
+            if f.endswith('.conf.json') and id == get_config_name(f):
+                shutil.copy(os.path.join(root, f), dest_dir)
+                return f
+    raise Exception("Unable to find {} in available configurations; use 'list' sub-command to see what is available".format(id))
+
+def obtain_config(upstream_config, dest_dir, args, d):
+    if upstream_config["type"] == 'local':
+        shutil.copy(upstream_config['path'], dest_dir)
+        basename = os.path.basename(upstream_config['path'])
+    elif upstream_config["type"] == 'network':
+        bb.process.run("wget {}".format(upstream_config["uri"]), cwd=dest_dir)
+        basename = os.path.basename(upstream_config['uri'])
+    elif upstream_config["type"] == 'registry':
+        registry_path = update_registry(upstream_config["registry"], args.cachedir, d)
+        basename = get_registry_config(registry_path, upstream_config["id"], dest_dir)
+    else:
+        raise Exception("Unknown configuration type: {}".format(upstream_config["type"]))
+    return os.path.join(dest_dir, basename)
+
+def update_build_config(config_path, confdir, builddir, layerdir, d, update_layers_only=False):
+    build_config = json.load(open(config_path))["configuration"]["bitbake-setup"]["default"]
+    layer_config = json.load(open(config_path))["sources"]
+    if not update_layers_only:
+        copy_and_commit_config(config_path, confdir)
+    checkout_layers(layer_config, layerdir, d)
+    setup_build(build_config, layerdir, builddir)
+
+def init_config(args, d):
+    topbuilddir = args.build_dir or os.path.join(os.path.expanduser('~'), 'builds', get_config_name(args.config))
+    os.makedirs(topbuilddir)
+    print("Initializing build in {}".format(topbuilddir))
+
+    if os.path.exists(args.config):
+        upstream_config = {'type':'local','path':os.path.abspath(args.config)}
+    elif args.config.startswith("http://") or args.config.startswith("https://"):
+        upstream_config = {'type':'network','uri':args.config}
+    else:
+        upstream_config = {'type':'registry','registry':args.registry,'id':args.config}
+
+    with open(os.path.join(topbuilddir, "config-upstream.json"),'w') as s:
+        json.dump(upstream_config, s, sort_keys=True, indent=4)
+
+    confdir = os.path.join(topbuilddir, "config")
+    builddir = os.path.join(topbuilddir, "build")
+    layerdir = os.path.join(topbuilddir, "layers")
+
+    os.makedirs(confdir)
+    os.makedirs(layerdir)
+
+    bb.process.run("git -C {} init -b main".format(confdir))
+    bb.process.run("git -C {} commit --allow-empty -m 'Initial commit'".format(confdir))
+
+    with tempfile.TemporaryDirectory(dir=topbuilddir, prefix='config-tmp-') as tmpdirname:
+        config_path = obtain_config(upstream_config, tmpdirname, args, d)
+        update_build_config(config_path, confdir, builddir, layerdir, d)
+
+def print_diff(file1, file2):
+    try:
+        bb.process.run('diff -u {} {}'.format(file1, file2))
+    except bb.process.ExecutionError as e:
+        if e.exitcode == 1:
+            print(e.stdout)
+        else:
+            raise e
+
+def are_layers_changed(layers, layerdir, d):
+    changed = False
+    for r_name in layers:
+        r_data = layers[r_name]
+        repodir = r_data["path"]
+
+        r_remote = r_data['git-remote']
+        rev = r_remote['rev']
+        remotes = r_remote['remotes']
+
+        for remote in remotes:
+            type,host,path,user,pswd,params = bb.fetch.decodeurl(remotes[remote]["uri"])
+            fetchuri = bb.fetch.encodeurl(('git',host,path,user,pswd,params))
+            fetcher = bb.fetch.FetchData("{};protocol={};rev={};nobranch=1;destsuffix={}".format(fetchuri,type,rev,repodir), d)
+            upstream_revision = fetcher.method.latest_revision(fetcher, d, 'default')
+            rev_parse_result = bb.process.run('git -C {} rev-parse HEAD'.format(os.path.join(layerdir, repodir)))
+            local_revision = rev_parse_result[0].strip()
+            if upstream_revision != local_revision:
+                changed = True
+                print('Layer repository {} checked out into {} updated revision {} from {} to {}'.format(remotes[remote]["uri"], os.path.join(layerdir, repodir), rev, local_revision, upstream_revision))
+
+    return changed
+
+def build_status(args, d, update=False):
+    topbuilddir = args.build_dir
+
+    confdir = os.path.join(topbuilddir, "config")
+    builddir = os.path.join(topbuilddir, "build")
+    layerdir = os.path.join(topbuilddir, "layers")
+
+    upstream_config = json.load(open(os.path.join(topbuilddir, "config-upstream.json")))
+
+    with tempfile.TemporaryDirectory(dir=topbuilddir, prefix='config-tmp-') as tmpdirname:
+        current_config_path = obtain_config(upstream_config, tmpdirname, args, d)
+
+        current_config = open(current_config_path).read()
+        build_config_path = os.path.join(confdir, os.path.basename(current_config_path))
+        build_config = open(build_config_path).read()
+        if current_config != build_config:
+            print('Top level configuration has changed:')
+            print_diff(build_config_path, current_config_path)
+            if update:
+                update_build_config(current_config_path, confdir, builddir, layerdir, d)
+            return
+
+    if are_layers_changed(json.loads(build_config)["sources"], layerdir, d):
+        if update:
+            update_build_config(build_config_path, confdir, builddir, layerdir, d, update_layers_only=True)
+        return
+
+    print("Configuration has not changed.")
+
+def build_update(args, d):
+    build_status(args, d, update=True)
+
+def update_registry(registry, cachedir, d):
+    registrydir = 'configurations'
+    fetcher = bb.fetch.Fetch(["{};destsuffix={}".format(registry, registrydir)], d)
+    fetcher.download()
+    fetcher.unpack(cachedir)
+    return os.path.join(cachedir, registrydir)
+
+def list_registry(registry_path):
+    print("Available configurations:")
+    for root, dirs, files in os.walk(registry_path):
+        for f in files:
+            if f.endswith('.conf.json'):
+                config_name = get_config_name(f)
+                config_desc = json.load(open(os.path.join(root, f)))["description"]
+                print("{}\t{}".format(config_name, config_desc))
+
+def list_configs(args, d):
+    registry_path = update_registry(args.registry, args.cachedir, d)
+    list_registry(registry_path)
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="BitBake setup utility",
+        epilog="Use %(prog)s <subcommand> --help to get help on a specific command"
+        )
+    parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
+    parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true')
+    parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR')
+    parser.add_argument('--registry', default='git://github.com/kanavin/bitbake-setup-configurations.git;protocol=https;branch=main;rev=main', help='Git repository with configuration files (in bitbake SRC_URI format)')
+    parser.add_argument('--cachedir', default=os.path.join(os.path.expanduser('~'), '.bitbake-setup'), help='Directory where downloaded configurations and layers are cached for reproducibility and offline builds')
+    parser.add_argument('--no-network', action='store_true', help='Do not check whether configuration repositories and layer repositories have been updated; use only the local cache.')
+
+    subparsers = parser.add_subparsers()
+
+    parser_init = subparsers.add_parser('init', help='Initialize a configuration')
+    parser_init.add_argument('config', help="path/URL/id to a configuration file")
+    parser_init.add_argument('--build-dir', help="Where to initialize the build", required=False)
+    parser_init.set_defaults(func=init_config)
+
+    parser_status = subparsers.add_parser('status', help='Check if the build configuration needs to be updated')
+    parser_status.add_argument('build_dir', help="Path to the build")
+    parser_status.set_defaults(func=build_status)
+
+    parser_status = subparsers.add_parser('update', help='Update a build configuration')
+    parser_status.add_argument('build_dir', help="Path to the build")
+    parser_status.set_defaults(func=build_update)
+
+    parser_list = subparsers.add_parser('list', help='List available configurations')
+    parser_list.set_defaults(func=list_configs)
+
+    args = parser.parse_args()
+
+    logging.basicConfig(stream=sys.stdout)
+    if args.debug:
+        logger.setLevel(logging.DEBUG)
+    elif args.quiet:
+        logger.setLevel(logging.ERROR)
+
+    # Need to re-run logger_create with color argument
+    # (will be the same logger since it has the same name)
+    bb.msg.logger_create('bitbake-setup', output=sys.stdout,
+                         color=args.color,
+                         level=logger.getEffectiveLevel())
+
+    d = init_bb_cache(args)
+    if 'func' in args:
+        args.func(args, d)
+    else:
+        from argparse import Namespace
+        parser.print_help()
+
+main()